filemaker 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +129 -17
- data/filemaker.gemspec +1 -0
- data/lib/filemaker.rb +47 -0
- data/lib/filemaker/api/query_commands/findquery.rb +20 -3
- data/lib/filemaker/configuration.rb +1 -1
- data/lib/filemaker/core_ext/hash.rb +19 -15
- data/lib/filemaker/error.rb +5 -0
- data/lib/filemaker/metadata/field.rb +21 -5
- data/lib/filemaker/model.rb +132 -0
- data/lib/filemaker/model/builder.rb +52 -0
- data/lib/filemaker/model/components.rb +25 -0
- data/lib/filemaker/model/criteria.rb +101 -0
- data/lib/filemaker/model/field.rb +38 -0
- data/lib/filemaker/model/fields.rb +80 -0
- data/lib/filemaker/model/findable.rb +35 -0
- data/lib/filemaker/model/optional.rb +69 -0
- data/lib/filemaker/model/pagination.rb +41 -0
- data/lib/filemaker/model/persistable.rb +96 -0
- data/lib/filemaker/model/relations.rb +72 -0
- data/lib/filemaker/model/relations/belongs_to.rb +30 -0
- data/lib/filemaker/model/relations/has_many.rb +79 -0
- data/lib/filemaker/model/relations/proxy.rb +35 -0
- data/lib/filemaker/model/selectable.rb +146 -0
- data/lib/filemaker/railtie.rb +17 -0
- data/lib/filemaker/record.rb +25 -0
- data/lib/filemaker/resultset.rb +12 -4
- data/lib/filemaker/server.rb +7 -5
- data/lib/filemaker/version.rb +1 -1
- data/spec/filemaker/api/query_commands/compound_find_spec.rb +13 -1
- data/spec/filemaker/configuration_spec.rb +23 -0
- data/spec/filemaker/layout_spec.rb +0 -1
- data/spec/filemaker/model/criteria_spec.rb +304 -0
- data/spec/filemaker/model/relations_spec.rb +85 -0
- data/spec/filemaker/model_spec.rb +73 -0
- data/spec/filemaker/record_spec.rb +12 -1
- data/spec/spec_helper.rb +1 -0
- data/spec/support/filemaker.yml +13 -0
- data/spec/support/models.rb +38 -0
- metadata +44 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2d84b6982396ee09d4e78ae0d12be8fbb4ca3b69
|
4
|
+
data.tar.gz: 8a758a58c2b0462e8b34ec246b6aae68b70ca750
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cd8c34c33807e773c6f3bff976e1b095e35dd18e89e877da8f905af484d3d78b3765f6ac32c8770048f24ed993a3f24428b86a6de342cd79e439d377e586516f
|
7
|
+
data.tar.gz: cf8aa8ec4943ff7497387d5aa5f61c0a29b598dfa15a1d85f3cdbbdfd935ef732a75b9ac9b2110467c5f2afff09c671dd80464efee01856d637335e3f8ffbe53
|
data/README.md
CHANGED
@@ -21,28 +21,30 @@ Ensure you have Web Publishing Engine (XML Publishing) enabled. Please turn on S
|
|
21
21
|
Configuration for initializing a server:
|
22
22
|
|
23
23
|
* `host` - IP or hostname
|
24
|
-
* `ssl` - `{ verify: false }` if you are using FileMaker's unsigned certificate. You can also pass a hash which will be forwarded to Faraday directly like `ssl: { client_cert: '', client_key: '', ca_file: '', ca_path: '/path/to/certs', cert_store: '' }`. See [Setting up SSL certificates](https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates)
|
25
24
|
* `account` - Please use `ENV` variable like `ENV['FILEMAKER_ACCOUNT']`
|
26
25
|
* `password` - Please use `ENV` variable like `ENV['FILEMAKER_PASSWORD']`
|
26
|
+
* `ssl` - Use `{ verify: false }` if you are using FileMaker's unsigned certificate. You can also pass a hash which will be forwarded to Faraday directly like `ssl: { client_cert: '', client_key: '', ca_file: '', ca_path: '/path/to/certs', cert_store: '' }`. See [Setting up SSL certificates on the Faraday wiki](https://github.com/lostisland/faraday/wiki/Setting-up-SSL-certificates)
|
27
|
+
* `log` - A choice of `:simple`, `:curl` and `:curl_auth`.
|
27
28
|
|
28
29
|
```ruby
|
29
30
|
server = Filemaker::Server.new do |config|
|
30
|
-
config.host
|
31
|
-
config.
|
32
|
-
config.password
|
33
|
-
config.ssl
|
34
|
-
config.log
|
31
|
+
config.host = ENV['FILEMAKER_HOST']
|
32
|
+
config.account_name = ENV['FILEMAKER_ACCOUNT_NAME']
|
33
|
+
config.password = ENV['FILEMAKER_PASSWORD']
|
34
|
+
config.ssl = { verify: false }
|
35
|
+
config.log = :curl
|
35
36
|
end
|
36
37
|
|
37
|
-
server.databases.all
|
38
|
-
server.database['candidates'].layouts
|
38
|
+
server.databases.all # Using -dbnames
|
39
|
+
server.database['candidates'].layouts.all # Using -layoutnames and -db=candidates
|
40
|
+
server.database['candidates'].scripts.all # Using -scriptnames and -db=candidates
|
39
41
|
|
40
42
|
api = server.db['candidates'].lay['profile']
|
41
43
|
api = server.db['candidates']['profile']
|
42
44
|
api = server.database['candidates'].layout['profile']
|
43
45
|
```
|
44
46
|
|
45
|
-
Once you are able to grab the `api`, you are golden and can make
|
47
|
+
Once you are able to grab the `api`, you are golden and can make requests to read/write to FileMaker API.
|
46
48
|
|
47
49
|
## Using the API
|
48
50
|
|
@@ -61,25 +63,31 @@ Most API will be smart enough to reject invalid query parameters if passed in in
|
|
61
63
|
|
62
64
|
## Using Filemaker::Model
|
63
65
|
|
64
|
-
If you want ActiveModel-like access with a decent query DSL like `where`, `find`, `
|
66
|
+
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.
|
65
67
|
|
66
68
|
```ruby
|
67
69
|
class Job
|
68
70
|
include Filemaker::Model
|
69
71
|
|
70
|
-
server :default # Taken from filemaker.yml config file
|
71
72
|
database :jobs
|
72
73
|
layout :job
|
74
|
+
|
75
|
+
paginates_per 50
|
73
76
|
|
74
|
-
|
75
|
-
|
77
|
+
# Taken from filemaker.yml config file, default to :default
|
78
|
+
# Only use registry if you have multiple FileMaker servers you want to connect
|
79
|
+
registry :read_slave
|
80
|
+
|
81
|
+
string :job_id, fm_name: 'JobOrderID', id: true
|
82
|
+
string :title, :requirements
|
83
|
+
datetime :created_at
|
84
|
+
datetime :published_at, fm_name: 'ModifiedDate'
|
85
|
+
money :salary
|
76
86
|
|
77
87
|
validates :title, presence: true
|
78
88
|
|
79
|
-
|
80
|
-
|
81
|
-
super(options)
|
82
|
-
end
|
89
|
+
belongs_to :company
|
90
|
+
has_many :applicants, class_name: 'JobApplication', reference_key: 'job_id'
|
83
91
|
end
|
84
92
|
```
|
85
93
|
|
@@ -89,11 +97,114 @@ end
|
|
89
97
|
development:
|
90
98
|
default:
|
91
99
|
host: localhost
|
100
|
+
account_name: ENV['FILEMAKER_ACCOUNT_NAME']
|
101
|
+
password: ENV['FILEMAKER_PASSWORD']
|
92
102
|
ssl: true
|
103
|
+
log: :curl
|
104
|
+
|
105
|
+
read_slave:
|
106
|
+
host: ...
|
107
|
+
ssl: { verify: false }
|
108
|
+
|
109
|
+
production:
|
110
|
+
default:
|
111
|
+
host: example.com
|
112
|
+
ssl: { ca_path: '/secret/path' }
|
93
113
|
```
|
94
114
|
|
95
115
|
## Query DSL
|
96
116
|
|
117
|
+
### Using -find
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
Model.where(gender: 'male', age: '< 50') # Default -lop=and
|
121
|
+
Model.where(gender: 'male').or(age: '< 50') # -lop=or
|
122
|
+
Model.where(gender: 'male').not(age: 40) # age.op=neq
|
123
|
+
|
124
|
+
# Supply a block to configure additional options like
|
125
|
+
# -script, -script.prefind, -lay.response, etc
|
126
|
+
Model.where(gender: 'male').or(age: '< 50') do |option|
|
127
|
+
option[:script] = ['RemoveDuplicates', 20]
|
128
|
+
end
|
129
|
+
|
130
|
+
Model.where(gender: 'male').or(name: 'Lee').not(age: '=40')
|
131
|
+
|
132
|
+
# Comparison operator
|
133
|
+
|
134
|
+
Model.equals(candidate_id: '123') # { candidate_id: '=123' }
|
135
|
+
Model.contains(name: 'Chong') # { name: '*Chong*' }
|
136
|
+
Model.begins_with(salary: '2000...4000') # ??
|
137
|
+
Model.ends_with(name: 'Yong') # { name: '*Yong' }
|
138
|
+
Model.gt(age: 20)
|
139
|
+
Model.gte(age: 20)
|
140
|
+
Model.lt(age: 20)
|
141
|
+
Model.lte(age: 20)
|
142
|
+
Model.not(name: 'Bob')
|
143
|
+
```
|
144
|
+
|
145
|
+
### Using -findquery
|
146
|
+
|
147
|
+
OR broadens the found set and AND narrows it
|
148
|
+
|
149
|
+
```ruby
|
150
|
+
# (q0);(q1)
|
151
|
+
# (Singapore) OR (Malaysia)
|
152
|
+
Model.in(nationality: %w(Singapore Malaysia))
|
153
|
+
|
154
|
+
# (q0,q1)
|
155
|
+
# Essentially the same as:
|
156
|
+
# Model.where(nationality: 'Singapore', age: 30)
|
157
|
+
Model.in(nationality: 'Singapore', age: 30)
|
158
|
+
|
159
|
+
# (q0);(q1);(q2);(q3)
|
160
|
+
Model.in({ nationality: %w(Singapore Malaysia) }, { age: [20, 30] })
|
161
|
+
|
162
|
+
# (q0,q2);(q1,q2)
|
163
|
+
# (Singapore AND male) OR (Malaysia AND male)
|
164
|
+
Model.in(nationality: %w(Singapore Malaysia), gender: 'male')
|
165
|
+
|
166
|
+
# !(q0);!(q1)
|
167
|
+
# NOT(Singapore) OR NOT(Malaysia)
|
168
|
+
Model.not_in(nationality: %w(Singapore Malaysia))
|
169
|
+
|
170
|
+
# !(q0,q1)
|
171
|
+
Model.not_in(name: 'Lee', age: '< 40')
|
172
|
+
|
173
|
+
# !(q0);!(q1)
|
174
|
+
# Must be within an array of hashes
|
175
|
+
Model.not_in([{ name: 'Lee' }, { age: '< 40' }])
|
176
|
+
|
177
|
+
# (q0);(q1);!(q2,q3)
|
178
|
+
Model.in(nationality: %w(Singapore Malaysia)).not_in(name: 'Lee', age: '< 40')
|
179
|
+
```
|
180
|
+
|
181
|
+
- [x] Please test the above query with real data to ensure correctness!
|
182
|
+
- [x] Please test the comparison operators with keyword as well as applied to value.
|
183
|
+
- [x] Test serialization of BigDecimal and other types.
|
184
|
+
|
185
|
+
## Pagination
|
186
|
+
|
187
|
+
If you have [kaminari](https://github.com/amatsuda/kaminari) in your project's `Gemfile`, `Filemaker::Model` will use it to page through the returned collection.
|
188
|
+
|
189
|
+
```ruby
|
190
|
+
Job.where(title: 'admin').per(50) # default to page(1)
|
191
|
+
Job.where(title: 'admin').page(5) # default to per(25)
|
192
|
+
Job.where(title: 'admin').page(2).per(35)
|
193
|
+
|
194
|
+
# In your model, you can customize the per_page
|
195
|
+
class Job
|
196
|
+
include Filemaker::Model
|
197
|
+
|
198
|
+
database :jobs
|
199
|
+
layout :job
|
200
|
+
|
201
|
+
paginates_per 50
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
Job.per_page # => 50
|
206
|
+
```
|
207
|
+
|
97
208
|
## Credits
|
98
209
|
|
99
210
|
This project is heavily inspired by the following Filemaker Ruby effort and several other ORM gems.
|
@@ -101,6 +212,7 @@ This project is heavily inspired by the following Filemaker Ruby effort and seve
|
|
101
212
|
* [Rfm](https://github.com/lardawge/rfm)
|
102
213
|
* [ginjo/rfm](https://github.com/ginjo/rfm)
|
103
214
|
* [mongoid](https://github.com/mongoid/mongoid)
|
215
|
+
* [origin](https://github.com/mongoid/origin)
|
104
216
|
* [elasticsearch-ruby](https://github.com/elasticsearch/elasticsearch-ruby)
|
105
217
|
|
106
218
|
## Contributing
|
data/filemaker.gemspec
CHANGED
@@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_runtime_dependency 'faraday'
|
22
22
|
spec.add_runtime_dependency 'typhoeus'
|
23
23
|
spec.add_runtime_dependency 'nokogiri'
|
24
|
+
spec.add_runtime_dependency 'activemodel'
|
24
25
|
|
25
26
|
spec.add_development_dependency 'bundler', '~> 1.6'
|
26
27
|
spec.add_development_dependency 'rake', '~> 10.0'
|
data/lib/filemaker.rb
CHANGED
@@ -11,3 +11,50 @@ require 'filemaker/record'
|
|
11
11
|
require 'filemaker/layout'
|
12
12
|
require 'filemaker/script'
|
13
13
|
require 'filemaker/error'
|
14
|
+
|
15
|
+
require 'active_support'
|
16
|
+
require 'active_support/core_ext'
|
17
|
+
require 'active_model'
|
18
|
+
|
19
|
+
require 'filemaker/model/criteria'
|
20
|
+
require 'filemaker/model'
|
21
|
+
|
22
|
+
require 'yaml'
|
23
|
+
|
24
|
+
module Filemaker
|
25
|
+
module_function
|
26
|
+
|
27
|
+
# Based on the environment, register the server so we only ever have one
|
28
|
+
# instance of Filemaker::Server per named session. The named session will be
|
29
|
+
# defined at the `filemaker.yml` config file.
|
30
|
+
def load!(path, environment = nil)
|
31
|
+
sessions = YAML.load(ERB.new(File.new(path).read).result)[environment.to_s]
|
32
|
+
fail Error::ConfigurationError, 'Environment wrong?' if sessions.nil?
|
33
|
+
|
34
|
+
sessions.each_pair do |key, value|
|
35
|
+
registry[key] = Filemaker::Server.new do |config|
|
36
|
+
config.host = value.fetch('host') do
|
37
|
+
fail Error::ConfigurationError, 'Missing config.host'
|
38
|
+
end
|
39
|
+
|
40
|
+
config.account_name = value.fetch('account_name') do
|
41
|
+
fail Error::ConfigurationError, 'Missing config.account_name'
|
42
|
+
end
|
43
|
+
|
44
|
+
config.password = value.fetch('password') do
|
45
|
+
fail Error::ConfigurationError, 'Missing config.password'
|
46
|
+
end
|
47
|
+
|
48
|
+
config.ssl = value['ssl'] if value['ssl']
|
49
|
+
config.log = value['log'] if value['log']
|
50
|
+
config.endpoint = value['endpoint'] if value['endpoint']
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def registry
|
56
|
+
@registry ||= {}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
require 'filemaker/railtie' if defined?(Rails)
|
@@ -6,18 +6,30 @@ module Filemaker
|
|
6
6
|
# query(status: 'open', title: 'web') => (q0,q1)
|
7
7
|
# query(status: %w(open closed)) => (q0);(q1)
|
8
8
|
#
|
9
|
-
def query(array_hash)
|
9
|
+
def query(array_hash, options = {})
|
10
10
|
compound_find = CompoundFind.new(array_hash)
|
11
11
|
|
12
12
|
query_hash = compound_find.key_values.merge(
|
13
13
|
'-query' => compound_find.key_maps_string
|
14
14
|
)
|
15
15
|
|
16
|
-
findquery(query_hash)
|
16
|
+
findquery(query_hash, options)
|
17
17
|
end
|
18
18
|
|
19
19
|
# Raw -findquery if you want to construct your own.
|
20
20
|
def findquery(query_hash, options = {})
|
21
|
+
valid_options(options,
|
22
|
+
:max,
|
23
|
+
:skip,
|
24
|
+
:sortfield,
|
25
|
+
:sortorder,
|
26
|
+
:lay_response,
|
27
|
+
:script,
|
28
|
+
:script_prefind,
|
29
|
+
:script_presort,
|
30
|
+
:relatedsets_filter,
|
31
|
+
:relatedsets_max)
|
32
|
+
|
21
33
|
perform_request('-findquery', query_hash, options)
|
22
34
|
end
|
23
35
|
|
@@ -39,6 +51,10 @@ module Filemaker
|
|
39
51
|
translate_key_maps
|
40
52
|
end
|
41
53
|
|
54
|
+
def to_s
|
55
|
+
"#{key_values}, #{key_maps_string}"
|
56
|
+
end
|
57
|
+
|
42
58
|
private
|
43
59
|
|
44
60
|
def build_key_values(hash)
|
@@ -65,7 +81,8 @@ module Filemaker
|
|
65
81
|
len = q_tag_array.length
|
66
82
|
result = q_tag_array.flatten.combination(len).select do |c|
|
67
83
|
q_tag_array.all? { |a| (a & c).size > 0 }
|
68
|
-
end
|
84
|
+
end
|
85
|
+
result = result.each { |c| c.unshift('-omit') if omit }
|
69
86
|
@key_maps.concat result
|
70
87
|
end
|
71
88
|
|
@@ -1,18 +1,3 @@
|
|
1
|
-
class Hash
|
2
|
-
def transform_keys
|
3
|
-
return enum_for(:transform_keys) unless block_given?
|
4
|
-
result = self.class.new
|
5
|
-
each_key do |key|
|
6
|
-
result[yield(key)] = self[key]
|
7
|
-
end
|
8
|
-
result
|
9
|
-
end
|
10
|
-
|
11
|
-
def stringify_keys
|
12
|
-
transform_keys { |key| key.to_s }
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
1
|
module Filemaker
|
17
2
|
class HashWithIndifferentAndCaseInsensitiveAccess < Hash
|
18
3
|
def []=(key, value)
|
@@ -23,6 +8,25 @@ module Filemaker
|
|
23
8
|
super(convert_key(key))
|
24
9
|
end
|
25
10
|
|
11
|
+
def key?(key)
|
12
|
+
super(convert_key(key))
|
13
|
+
end
|
14
|
+
|
15
|
+
alias_method :include?, :key?
|
16
|
+
alias_method :member?, :key?
|
17
|
+
|
18
|
+
def fetch(key, *extras)
|
19
|
+
super(convert_key(key), *extras)
|
20
|
+
end
|
21
|
+
|
22
|
+
def values_at(*indices)
|
23
|
+
indices.map { |key| self[convert_key(key)] }
|
24
|
+
end
|
25
|
+
|
26
|
+
def delete(key)
|
27
|
+
super(convert_key(key))
|
28
|
+
end
|
29
|
+
|
26
30
|
protected
|
27
31
|
|
28
32
|
def convert_key(key)
|
data/lib/filemaker/error.rb
CHANGED
@@ -4,6 +4,7 @@ module Filemaker
|
|
4
4
|
class AuthenticationError < StandardError; end
|
5
5
|
class ParameterError < StandardError; end
|
6
6
|
class CoerceError < StandardError; end
|
7
|
+
class ConfigurationError < StandardError; end
|
7
8
|
|
8
9
|
class FilemakerError < StandardError
|
9
10
|
attr_reader :code
|
@@ -35,6 +36,7 @@ module Filemaker
|
|
35
36
|
class ScriptMissingError < MissingError; end
|
36
37
|
class LayoutMissingError < MissingError; end
|
37
38
|
class TableMissingError < MissingError; end
|
39
|
+
class InvalidFieldError < StandardError; end
|
38
40
|
|
39
41
|
class SecurityError < FilemakerError; end
|
40
42
|
class RecordAccessDeniedError < SecurityError; end
|
@@ -68,6 +70,9 @@ module Filemaker
|
|
68
70
|
class UnableToCreateTempFileError < FileError; end
|
69
71
|
class UnableToOpenFileError < FileError; end
|
70
72
|
|
73
|
+
class QueryError < StandardError; end
|
74
|
+
class MixedClauseError < QueryError; end
|
75
|
+
|
71
76
|
def self.raise_error_by_code(code)
|
72
77
|
msg = error_message_by_code(code)
|
73
78
|
error_class = find_error_class_by_code(code)
|
@@ -43,8 +43,23 @@ module Filemaker
|
|
43
43
|
when 'date'
|
44
44
|
# date_format likely will be '%m/%d/%Y', but if we got '19/8/2014',
|
45
45
|
# then `strptime` will raise invalid date error
|
46
|
-
|
47
|
-
|
46
|
+
# Sometimes we can get '27/11 /1981' also :(
|
47
|
+
begin
|
48
|
+
Date.strptime(value, @resultset.date_format)
|
49
|
+
rescue
|
50
|
+
# We could be getting back these date:
|
51
|
+
# '17.12.95', '19/05/99', '27/11 /1981'
|
52
|
+
# '1959-07-03' will be beyong us, so consider returning nil
|
53
|
+
value = value.gsub(/-|\./, '/')
|
54
|
+
split = value.split('/').map(&:strip)
|
55
|
+
split[2] = "19#{split[2]}" if split[2].size == 2
|
56
|
+
value = split.join('/')
|
57
|
+
|
58
|
+
Date.strptime(
|
59
|
+
Date.parse(value)
|
60
|
+
.strftime(@resultset.date_format), @resultset.date_format
|
61
|
+
)
|
62
|
+
end
|
48
63
|
when 'time'
|
49
64
|
DateTime.strptime("1/1/-4712 #{value}", @resultset.timestamp_format)
|
50
65
|
when 'timestamp'
|
@@ -54,9 +69,10 @@ module Filemaker
|
|
54
69
|
else
|
55
70
|
value
|
56
71
|
end
|
57
|
-
rescue
|
58
|
-
|
59
|
-
|
72
|
+
rescue
|
73
|
+
warn "Could not coerce #{value}. Return nil instead."
|
74
|
+
nil
|
75
|
+
# raise Filemaker::Error::CoerceError, msg
|
60
76
|
end
|
61
77
|
|
62
78
|
private
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require 'filemaker/model/components'
|
2
|
+
|
3
|
+
module Filemaker
|
4
|
+
module Model
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
include Components
|
7
|
+
|
8
|
+
# @return [Boolean] indicates if this is a new fresh record
|
9
|
+
attr_reader :attributes, :new_record, :record_id, :mod_id
|
10
|
+
|
11
|
+
included do
|
12
|
+
class_attribute :db, :lay, :registry_name, :server, :api, :per_page
|
13
|
+
self.per_page = Kaminari.config.default_per_page if defined?(Kaminari)
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(attrs = nil)
|
17
|
+
@new_record = true
|
18
|
+
@attributes = {}
|
19
|
+
@relations = {}
|
20
|
+
apply_defaults
|
21
|
+
process_attributes(attrs)
|
22
|
+
end
|
23
|
+
|
24
|
+
def new_record?
|
25
|
+
new_record
|
26
|
+
end
|
27
|
+
|
28
|
+
def persisted?
|
29
|
+
!new_record?
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_a
|
33
|
+
[self]
|
34
|
+
end
|
35
|
+
|
36
|
+
def id
|
37
|
+
self.class.identity ? identity_id : record_id
|
38
|
+
end
|
39
|
+
|
40
|
+
def identity_id
|
41
|
+
public_send(identity.name) if identity
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_param
|
45
|
+
id.to_s if id
|
46
|
+
end
|
47
|
+
|
48
|
+
def fm_attributes
|
49
|
+
self.class.with_model_fields(attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def process_attributes(attrs)
|
55
|
+
attrs ||= {}
|
56
|
+
return if attrs.empty?
|
57
|
+
|
58
|
+
attrs.each_pair do |key, value|
|
59
|
+
public_send("#{key}=", value) if respond_to?("#{key}=")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
module ClassMethods
|
64
|
+
def database(db)
|
65
|
+
self.db = db
|
66
|
+
self.registry_name ||= 'default' unless lay.blank?
|
67
|
+
register
|
68
|
+
end
|
69
|
+
|
70
|
+
def layout(lay)
|
71
|
+
self.lay = lay
|
72
|
+
self.registry_name ||= 'default' unless db.blank?
|
73
|
+
register
|
74
|
+
end
|
75
|
+
|
76
|
+
def registry(name)
|
77
|
+
self.registry_name = (name || 'default').to_s
|
78
|
+
register
|
79
|
+
end
|
80
|
+
|
81
|
+
def register
|
82
|
+
self.server = Filemaker.registry[registry_name]
|
83
|
+
self.api = server.db[db][lay] if server && db && lay
|
84
|
+
end
|
85
|
+
|
86
|
+
# A chance for the model to set it's per_page.
|
87
|
+
def paginates_per(value)
|
88
|
+
self.per_page = value.to_i
|
89
|
+
end
|
90
|
+
|
91
|
+
# Make use of -view to return an array of [name, data_type] for this
|
92
|
+
# model from FileMaker.
|
93
|
+
#
|
94
|
+
# @return [Array] array of [name, data_type]
|
95
|
+
def fm_fields
|
96
|
+
api.view.fields.values.map { |field| [field.name, field.data_type] }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Filter out any fields that do not match model's fields.
|
100
|
+
#
|
101
|
+
# A testing story to tell: when working on `in` query, we have value that
|
102
|
+
# is an array. Without the test and expectation setup, debugging the
|
103
|
+
# output will take far longer to realise. This reinforce the belief that
|
104
|
+
# TDD is in fact a valuable thing to do.
|
105
|
+
def with_model_fields(criterion, coerce = true)
|
106
|
+
accepted_fields = {}
|
107
|
+
|
108
|
+
criterion.each_pair do |key, value|
|
109
|
+
field = find_field_by_name(key)
|
110
|
+
|
111
|
+
next unless field
|
112
|
+
|
113
|
+
# We do not serialize at this point, as we are still in Ruby-land.
|
114
|
+
# Filemaker::Server will help us serialize into FileMaker format.
|
115
|
+
if value.is_a? Array
|
116
|
+
temp = []
|
117
|
+
value.each do |v|
|
118
|
+
temp << (coerce ? field.coerce(v) : v)
|
119
|
+
end
|
120
|
+
|
121
|
+
accepted_fields[field.fm_name] = temp
|
122
|
+
else
|
123
|
+
accepted_fields[field.fm_name] = \
|
124
|
+
coerce ? field.coerce(value) : value
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
accepted_fields
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|