filemaker 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|