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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +129 -17
  3. data/filemaker.gemspec +1 -0
  4. data/lib/filemaker.rb +47 -0
  5. data/lib/filemaker/api/query_commands/findquery.rb +20 -3
  6. data/lib/filemaker/configuration.rb +1 -1
  7. data/lib/filemaker/core_ext/hash.rb +19 -15
  8. data/lib/filemaker/error.rb +5 -0
  9. data/lib/filemaker/metadata/field.rb +21 -5
  10. data/lib/filemaker/model.rb +132 -0
  11. data/lib/filemaker/model/builder.rb +52 -0
  12. data/lib/filemaker/model/components.rb +25 -0
  13. data/lib/filemaker/model/criteria.rb +101 -0
  14. data/lib/filemaker/model/field.rb +38 -0
  15. data/lib/filemaker/model/fields.rb +80 -0
  16. data/lib/filemaker/model/findable.rb +35 -0
  17. data/lib/filemaker/model/optional.rb +69 -0
  18. data/lib/filemaker/model/pagination.rb +41 -0
  19. data/lib/filemaker/model/persistable.rb +96 -0
  20. data/lib/filemaker/model/relations.rb +72 -0
  21. data/lib/filemaker/model/relations/belongs_to.rb +30 -0
  22. data/lib/filemaker/model/relations/has_many.rb +79 -0
  23. data/lib/filemaker/model/relations/proxy.rb +35 -0
  24. data/lib/filemaker/model/selectable.rb +146 -0
  25. data/lib/filemaker/railtie.rb +17 -0
  26. data/lib/filemaker/record.rb +25 -0
  27. data/lib/filemaker/resultset.rb +12 -4
  28. data/lib/filemaker/server.rb +7 -5
  29. data/lib/filemaker/version.rb +1 -1
  30. data/spec/filemaker/api/query_commands/compound_find_spec.rb +13 -1
  31. data/spec/filemaker/configuration_spec.rb +23 -0
  32. data/spec/filemaker/layout_spec.rb +0 -1
  33. data/spec/filemaker/model/criteria_spec.rb +304 -0
  34. data/spec/filemaker/model/relations_spec.rb +85 -0
  35. data/spec/filemaker/model_spec.rb +73 -0
  36. data/spec/filemaker/record_spec.rb +12 -1
  37. data/spec/spec_helper.rb +1 -0
  38. data/spec/support/filemaker.yml +13 -0
  39. data/spec/support/models.rb +38 -0
  40. metadata +44 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: da908ea60c24ec21422e5405e5c18e149825bb78
4
- data.tar.gz: cf777449498d727f9c15ab765f5e610b292d0f8d
3
+ metadata.gz: 2d84b6982396ee09d4e78ae0d12be8fbb4ca3b69
4
+ data.tar.gz: 8a758a58c2b0462e8b34ec246b6aae68b70ca750
5
5
  SHA512:
6
- metadata.gz: f6d6523f795649e9f2b09cc66ce21fff013586713a677b002734ffbc03eb3d5cdec949198764361d594c896851d1c7000f58e2adb28c76293091cd37b9acc029
7
- data.tar.gz: 2b44e3649cfce1a9fa65ce5d7264f717ede0c86f63eafd695b9370deebd14f4870d2d5eb626860317e9cd5013e7f579cfa4bf64bd81125bd3ca7c38c1a49eda0
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 = 'localhost'
31
- config.account = ENV['FILEMAKER_ACCOUNT']
32
- config.password = ENV['FILEMAKER_PASSWORD']
33
- config.ssl = { verify: false }
34
- config.log = :curl
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 # Using -dbnames
38
- server.database['candidates'].layouts # Using -layoutnames and -db=candidates
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 request to read/write to FileMaker API.
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`, `all`, you can include `Filemaker::Model` to your model. Your Rails form will work as well as JSON serialization.
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
- string :title, :requirements
75
- datetime :created_at, :published_at
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
- def as_json(options = {})
80
- options[:except] ||= [:created_at]
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
@@ -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'
@@ -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.each { |c| c.unshift('-omit') if omit }
84
+ end
85
+ result = result.each { |c| c.unshift('-omit') if omit }
69
86
  @key_maps.concat result
70
87
  end
71
88
 
@@ -13,7 +13,7 @@ module Filemaker
13
13
 
14
14
  %w(host account_name password).each do |name|
15
15
  define_method "#{name}_missing?" do
16
- (send(name.to_sym) || '').empty?
16
+ (public_send(name.to_sym) || '').empty?
17
17
  end
18
18
  end
19
19
 
@@ -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)
@@ -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
- Date.strptime(value, @resultset.date_format)
47
- # Date.strptime(Date.parse(value).strftime(@resultset.date_format), @resultset.date_format)
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 Exception => e
58
- msg = "Could not coerce #{value} due to #{e.message}"
59
- raise Filemaker::Error::CoerceError, msg
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