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 useENV
variable likeENV['FILEMAKER_ACCOUNT']
password
- Please useENV
variable likeENV['FILEMAKER_PASSWORD']
ssl
- Use{ verify: false }
if you are using FileMaker's unsigned certificate.ssl_verifypeer
- Default tofalse
ssl_verifyhost
- Default to0
log
- A choice ofsimple
,curl
andcurl_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-find
api.findany()
for-findany
api.findquery()
for-findquery
api.new()
for-new
api.edit()
for-edit
api.delete()
for-delete
api.dup()
for-dup
api.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::Text
text
-Filemaker::Model::Types::Text
integer
-Filemaker::Model::Types::Integer
number
-Filemaker::Model::Types::BigDecimal
money
-Filemaker::Model::Types::BigDecimal
date
-Filemaker::Model::Types::Date
datetime
-Filemaker::Model::Types::Time
email
-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: true
You 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
end
Model.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
in
andnot_in
found 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 # => 50
Filemaker::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.