A Ruby wrapper to FileMaker XML API.
At your Gemfile:
gem 'filemaker'
Ensure you have Web Publishing Engine (XML Publishing) enabled. Please turn on SSL or credential won't be protected. Remember to also set the "Extended Privileges" to: fmxml.
Configuration for initializing a server:
host- IP or hostnameaccount- Please useENVvariable likeENV['FILEMAKER_ACCOUNT']password- Please useENVvariable likeENV['FILEMAKER_PASSWORD']ssl- Use{ verify: false }if you are using FileMaker's unsigned certificate.ssl_verifypeer- Default tofalsessl_verifyhost- Default to0log- A choice ofsimple,curlandcurl_auth.
server = Filemaker::Server.new do |config|
config.host = ENV['FILEMAKER_HOST']
config.account_name = ENV['FILEMAKER_ACCOUNT_NAME']
config.password = ENV['FILEMAKER_PASSWORD']
config.ssl = { verify: false }
config.log = curl
end
server.databases.all # Using -dbnames
server.database['candidates'].layouts.all # Using -layoutnames and -db=candidates
server.database['candidates'].scripts.all # Using -scriptnames and -db=candidates
api = server.db['candidates'].lay['profile']
api = server.db['candidates']['profile']
api = server.database['candidates'].layout['profile']
api.find(...)Once you are able to grab the api, you are golden and can make requests to read/write to FileMaker API.
Filemaker::Api::QueryCommands is the main modules to use the API.
api.find()for-findapi.findany()for-findanyapi.findquery()for-findqueryapi.new()for-newapi.edit()for-editapi.delete()for-deleteapi.dup()for-dupapi.view()for-view
Most API will be smart enough to reject invalid query parameters if passed in incorrectly.
If you want ActiveModel-like access with a decent query DSL like where, find, in, you can include Filemaker::Model to your model. Your Rails form will work as well as JSON serialization.
The following data type mappings can be used to register the fields:
string-Filemaker::Model::Types::Texttext-Filemaker::Model::Types::Textinteger-Filemaker::Model::Types::Integernumber-Filemaker::Model::Types::BigDecimalmoney-Filemaker::Model::Types::BigDecimaldate-Filemaker::Model::Types::Datedatetime-Filemaker::Model::Types::Timeemail-Filemaker::Model::Types::Email
You can create your own custom type by providing these 3 class methods:
__filemaker_cast_to_ruby_object__filemaker_serialize_for_update__filemaker_serialize_for_query
And register it with:
Filemaker::Model::Type.register(:fast_string, FastStringType)If the field name has spaces, you can use fm_name to identify the real FileMaker field name.
string :job_id, fm_name: 'JobOrderID', identity: trueYou can also use 3 relations: has_many, belongs_to and has_portal.
has_many will refer to the model's own identity as the reference key while belongs_to will append _id for the reference key unless being overridden by reference_key.
class Job
include Filemaker::Model
database :jobs
layout :job
paginates_per 50
# Taken from filemaker.yml config file, default to :default
# Only use registry if you have multiple FileMaker servers you want to connect
registry :read_slave
string :job_id, fm_name: 'JobOrderID', identity: true
string :title, :requirements
datetime :created_at
datetime :published_at, fm_name: 'ModifiedDate'
money :salary
validates :title, presence: true
belongs_to :company
has_many :applicants, class_name: 'JobApplication', reference_key: 'job_id'
end# filemaker.yml
development:
default:
host: <%= ENV['FILEMAKER_HOSTNAME'] %>
account_name: <%= ENV['FILEMAKER_ACCOUNT_NAME'] %>
password: <%= ENV['FILEMAKER_PASSWORD'] %>
ssl: true
ssl_verifypeer: false
ssl_verifyhost: 0
log: curl
read_slave:
host: ...
ssl: { verify: false }
production:
default:
host: example.com
ssl: { ca_path: '/secret/path' }#!/usr/bin/env ruby
require 'bundler/setup'
Bundler.require
Filemaker.registry['default'] = Filemaker::Server.new do |config|
config.host = ENV['FILEMAKER_HOST']
config.account_name = ENV['FILEMAKER_ACCOUNT_NAME']
config.password = ENV['FILEMAKER_PASSWORD']
end
class Invoice
include Filemaker::Model
string :invoice_id, identity: true
date :paid_at
money :amount
end
invoices = Invoice.where(paid_at: '2017')
invoices.each do |invoice|
import_invoice_to_s3
endModel.where(gender: 'male', age: '< 50') # Default -lop=and
Model.where(gender: 'male').or(age: '< 50') # -lop=or
Model.where(gender: 'male').not(age: 40) # age.op=neq
# Supply a block to configure additional options like
# -script, -script.prefind, -lay.response, etc
Model.where(gender: 'male').or(age: '< 50') do |option|
option[:script] = ['RemoveDuplicates', 20]
end
Model.where(gender: 'male').or(name: 'Lee').not(age: '=40')
# DateTime range example
Model.where(timestamp: "10/17/2015 00:00:00...10/20/2015 23:59:59")
# Comparison operator
Model.equals(candidate_id: '123') # { candidate_id: '=123' }
Model.contains(name: 'Chong') # { name: '*Chong*' }
Model.begins_with(salary: '2000...4000') # ??
Model.ends_with(name: 'Yong') # { name: '*Yong' }
Model.gt(age: 20)
Model.gte(age: 20)
Model.lt(age: 20)
Model.lte(age: 20)
Model.not(name: 'Bob')OR broadens the found set and AND narrows it
# (q0);(q1)
# (Singapore) OR (Malaysia)
Model.in(nationality: %w[Singapore Malaysia])
# (q0,q1)
# (nationality AND age)
# Essentially the same as:
# Model.where(nationality: 'Singapore', age: 30)
Model.in(nationality: 'Singapore', age: 30)
# (q0);(q1);(q2);(q3)
Model.in({ nationality: %w[Singapore Malaysia] }, { age: [20, 30] })
# (q0,q2);(q1,q2)
# (Singapore AND male) OR (Malaysia AND male)
Model.in(nationality: %w[Singapore Malaysia], gender: 'male')
# !(q0);!(q1)
# NOT(Singapore) OR NOT(Malaysia)
Model.not_in(nationality: %w[Singapore Malaysia])
# !(q0,q1)
Model.not_in(name: 'Lee', age: '< 40')
# !(q0);!(q1)
# Must be within an array of hashes
Model.not_in([{ name: 'Lee' }, { age: '< 40' }])
# (q0);(q1);!(q2,q3)
Model.in(nationality: %w(Singapore Malaysia)).not_in(name: 'Lee', age: '< 40')Note: It is vitally important that you get the order right for mixing in the use of in with not_in. Likely you will want to do an in first to be inclusive and later omit using not_in.
- Please test the above query with real data to ensure correctness!
- Please test the comparison operators with keyword as well as applied to value.
- Test serialization of BigDecimal and other types.
- Caching of relation models.
- Dirty checking API for model.
- Test the order for
inandnot_infound set accuracy.
If you have kaminari in your project's Gemfile, Filemaker::Model will use it to page through the returned collection.
Job.where(title: 'admin').per(50) # default to page(1)
Job.where(title: 'admin').page(5) # default to per(25)
Job.where(title: 'admin').page(2).per(35)
# In your model, you can customize the per_page
class Job
include Filemaker::Model
database :jobs
layout :job
paginates_per 50
end
Job.per_page # => 50Filemaker::Model include Filemaker::Model::Findable which create Criteria and execute() to return Filemaker::Resultset to be built by Filemaker::Model::Builder.
This project is heavily inspired by the following Filemaker Ruby effort and several other ORM gems.
We welcome pull request with specs.
- Fork it ( https://github.com/mech/filemaker-ruby/fork )
- Create your feature branch (
git checkout -b my-new-feature) - Commit your changes (
git commit -am 'Add some feature') - Push to the branch (
git push origin my-new-feature) - Create a new Pull Request
Do run rubocop -D -f simple before committing.