filemaker 1.0.0 → 1.0.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 90e8d2b8704500b90fa6649ad585301c88f94e420bb34f4cd620a1c7c53abe9d
4
- data.tar.gz: 65fff04efa068012d5fcc54a1877c50b15434dde10040ad41a146b3889267f77
3
+ metadata.gz: 760eb94b95fc1aacf25f18cf284780211c18c9fda1beaf4a53a6be49e3fd0aae
4
+ data.tar.gz: f1525fe3dbfb0697b72fd5d031c64fc93d5d9b0ee50d5d755336ef909344703b
5
5
  SHA512:
6
- metadata.gz: a858783d95970eca0e7166f84e494a755885b86674c895574b3563fbfee44f4b2fc8c249a20580062c5c649c9bb9b6812230e7e787235ac5fcdaca396b111810
7
- data.tar.gz: 4e5b6d9fc0d0ae3bf85ded1daee20c85e4fbac8d657fbfd4ce56231e8d5ac4a49837c8a41a966d2050d0455fbe302688c43c0181df4bd3bf4c700786bbd0f69f
6
+ metadata.gz: b56eaf97df56cbf2d1ff8b5359128a2251046a55834da211fc163c274b4e294cf713e44f0fd2470cf7306b1b9be05b15d5ceac5df77b201e9ac4c6068d925104
7
+ data.tar.gz: e0430af65e889355a80085cb8972e79daf665dd8735541e270936e563f27d9c51abd3022df191564305f32ed9b148b7e0d3ea665eeb082e91ed10f70329d5ad4
@@ -16,7 +16,7 @@ MethodLength:
16
16
  Enabled: false
17
17
 
18
18
  LineLength:
19
- Max: 80
19
+ Max: 120
20
20
 
21
21
  SpecialGlobalVars:
22
22
  Enabled: false
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Filemaker
2
2
 
3
3
  [![Build Status](https://travis-ci.org/mech/filemaker-ruby.svg?branch=master)](https://travis-ci.org/mech/filemaker-ruby)
4
+ [![security](https://hakiri.io/github/mech/filemaker-ruby/master.svg)](https://hakiri.io/github/mech/filemaker-ruby/master)
4
5
 
5
6
  A Ruby wrapper to FileMaker XML API.
6
7
 
@@ -220,23 +221,24 @@ Model.not(name: 'Bob')
220
221
  ```ruby
221
222
  # (q0);(q1)
222
223
  # (Singapore) OR (Malaysia)
223
- Model.in(nationality: %w(Singapore Malaysia))
224
+ Model.in(nationality: %w[Singapore Malaysia])
224
225
 
225
226
  # (q0,q1)
227
+ # (nationality AND age)
226
228
  # Essentially the same as:
227
229
  # Model.where(nationality: 'Singapore', age: 30)
228
230
  Model.in(nationality: 'Singapore', age: 30)
229
231
 
230
232
  # (q0);(q1);(q2);(q3)
231
- Model.in({ nationality: %w(Singapore Malaysia) }, { age: [20, 30] })
233
+ Model.in({ nationality: %w[Singapore Malaysia] }, { age: [20, 30] })
232
234
 
233
235
  # (q0,q2);(q1,q2)
234
236
  # (Singapore AND male) OR (Malaysia AND male)
235
- Model.in(nationality: %w(Singapore Malaysia), gender: 'male')
237
+ Model.in(nationality: %w[Singapore Malaysia], gender: 'male')
236
238
 
237
239
  # !(q0);!(q1)
238
240
  # NOT(Singapore) OR NOT(Malaysia)
239
- Model.not_in(nationality: %w(Singapore Malaysia))
241
+ Model.not_in(nationality: %w[Singapore Malaysia])
240
242
 
241
243
  # !(q0,q1)
242
244
  Model.not_in(name: 'Lee', age: '< 40')
@@ -19,12 +19,12 @@ Gem::Specification.new do |spec|
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_runtime_dependency 'typhoeus'
22
- spec.add_runtime_dependency 'nokogiri', '~> 1.7'
22
+ spec.add_runtime_dependency 'nokogiri', '~> 1.10.4'
23
23
  spec.add_runtime_dependency 'activemodel'
24
24
  spec.add_runtime_dependency 'globalid'
25
25
 
26
- spec.add_development_dependency 'bundler', '~> 1.13'
27
- spec.add_development_dependency 'rake', '~> 12.0'
26
+ spec.add_development_dependency 'bundler'
27
+ spec.add_development_dependency 'rake'
28
28
  spec.add_development_dependency 'rspec', '~> 3.7'
29
29
  spec.add_development_dependency 'rubocop'
30
30
  spec.add_development_dependency 'pry-byebug'
@@ -41,8 +41,8 @@ module Filemaker
41
41
 
42
42
  def cache_key
43
43
  return "#{model_key}/new" if new_record?
44
- return "#{model_key}/#{id}-#{updated_at.to_datetime.utc.to_s(:number)}" \
45
- if respond_to?(:updated_at) && send(:updated_at)
44
+ return "#{model_key}/#{id}-#{updated_at.to_datetime.utc.to_s(:number)}" if respond_to?(:updated_at) && send(:updated_at)
45
+
46
46
  "#{model_key}/#{id}"
47
47
  end
48
48
 
@@ -59,7 +59,11 @@ module Filemaker
59
59
  end
60
60
 
61
61
  def fm_attributes
62
- self.class.with_model_fields(attributes)
62
+ self.class.with_model_fields_for_query(attributes)
63
+ end
64
+
65
+ def create_attributes
66
+ self.class.with_model_fields_for_creation(attributes)
63
67
  end
64
68
 
65
69
  def dirty_attributes
@@ -69,7 +73,7 @@ module Filemaker
69
73
  end
70
74
 
71
75
  # We need to use serialize_for_update instead
72
- self.class.with_model_fields(dirty, use_query: false)
76
+ self.class.with_model_fields_for_update(dirty)
73
77
  end
74
78
 
75
79
  private
@@ -129,7 +133,7 @@ module Filemaker
129
133
  # is an array. Without the test and expectation setup, debugging the
130
134
  # output will take far longer to realise. This reinforce the belief that
131
135
  # TDD is in fact a valuable thing to do.
132
- def with_model_fields(criterion, use_query: true)
136
+ def with_model_fields_for_query(criterion)
133
137
  accepted_fields = {}
134
138
 
135
139
  criterion.each_pair do |key, value|
@@ -141,23 +145,61 @@ module Filemaker
141
145
 
142
146
  # We do not serialize at this point, as we are still in Ruby-land.
143
147
  # Filemaker::Server will help us serialize into FileMaker format.
144
- if value.is_a? Array
145
- temp = []
146
- value.each do |v|
147
- temp << if use_query
148
- field.serialize_for_query(v)
149
- else
150
- field.serialize_for_update(v)
151
- end
148
+ if value.is_a?(Array)
149
+ accepted_fields[field.fm_name] = value.map { |v| field.serialize_for_query(v) }
150
+ else
151
+ accepted_fields[field.fm_name] = field.serialize_for_query(value)
152
+ end
153
+ end
154
+
155
+ accepted_fields
156
+ end
157
+
158
+ def with_model_fields_for_creation(criterion)
159
+ accepted_fields = {}
160
+
161
+ criterion.each_pair do |key, value|
162
+ field = find_field_by_name(key)
163
+
164
+ # Do not process nil value
165
+ next unless field && value
166
+
167
+ # We do not serialize at this point, as we are still in Ruby-land.
168
+ # Filemaker::Server will help us serialize into FileMaker format.
169
+ if value.is_a?(Array)
170
+ field.max_repeat.times do |idx|
171
+ index = idx + 1
172
+ repeated_fm_name = "#{field.fm_name}(#{index})"
173
+ accepted_fields[repeated_fm_name] = field.serialize_for_update(value[idx])
152
174
  end
175
+ else
176
+ accepted_fields[field.fm_name] = field.serialize_for_update(value)
177
+ end
178
+ end
179
+
180
+ accepted_fields
181
+ end
182
+
183
+ def with_model_fields_for_update(criterion)
184
+ accepted_fields = {}
153
185
 
154
- accepted_fields[field.fm_name] = temp
186
+ criterion.each_pair do |key, value|
187
+ field = find_field_by_name(key)
188
+
189
+ next unless field
190
+
191
+ # Able to process nil value only for update
192
+
193
+ # We do not serialize at this point, as we are still in Ruby-land.
194
+ # Filemaker::Server will help us serialize into FileMaker format.
195
+ if value.is_a?(Array)
196
+ field.max_repeat.times do |idx|
197
+ index = idx + 1
198
+ repeated_fm_name = "#{field.fm_name}(#{index})"
199
+ accepted_fields[repeated_fm_name] = field.serialize_for_update(value[idx])
200
+ end
155
201
  else
156
- accepted_fields[field.fm_name] = if use_query
157
- field.serialize_for_query(value)
158
- else
159
- field.serialize_for_update(value)
160
- end
202
+ accepted_fields[field.fm_name] = field.serialize_for_update(value)
161
203
  end
162
204
  end
163
205
 
@@ -1,22 +1,24 @@
1
1
  module Filemaker
2
2
  module Model
3
3
  module Batches
4
- def in_batches(batch_size: 200, options: {})
4
+ def in_batches(batch_size: 200, options: {}, sleep: 0)
5
5
  output = []
6
6
  total = self.in(options).count
7
7
  pages = (total / batch_size.to_f).ceil
8
8
  1.upto(pages) do |page|
9
+ sleep(sleep)
9
10
  output.concat self.in(options).per(batch_size).page(page)
10
11
  end
11
12
 
12
13
  output
13
14
  end
14
15
 
15
- def where_batches(batch_size: 200, options: {})
16
+ def where_batches(batch_size: 200, options: {}, sleep: 0)
16
17
  output = []
17
18
  total = where(options).count
18
19
  pages = (total / batch_size.to_f).ceil
19
20
  1.upto(pages) do |page|
21
+ sleep(sleep)
20
22
  output.concat where(options).per(batch_size).page(page)
21
23
  end
22
24
 
@@ -33,6 +33,15 @@ module Filemaker
33
33
  value = field.cast(record[fm_field_name])
34
34
  object.public_send(setter, value)
35
35
 
36
+ if record[fm_field_name].is_a?(Array) && field.max_repeat > 1
37
+ field.max_repeat.times do |idx|
38
+ index = idx + 1
39
+ repeated_setter = "#{field.name}__#{index}="
40
+ repeated_value = field.cast(record[fm_field_name][idx])
41
+ object.public_send(repeated_setter, repeated_value)
42
+ end
43
+ end
44
+
36
45
  # So after hydrating, we do not say it was dirty
37
46
  object.clear_changes_information
38
47
  end
@@ -1,11 +1,12 @@
1
1
  module Filemaker
2
2
  module Model
3
3
  class Field
4
- attr_reader :name, :type, :default_value, :fm_name
4
+ attr_reader :name, :type, :default_value, :fm_name, :max_repeat
5
5
 
6
6
  def initialize(name, type, options = {})
7
7
  @name = name
8
8
  @type = type
9
+ @max_repeat = options.fetch(:max_repeat) { 1 }
9
10
  @default_value = serialize_for_update(options.fetch(:default) { nil })
10
11
 
11
12
  # We need to downcase because Filemaker::Record is
@@ -16,7 +17,9 @@ module Filemaker
16
17
  # Will delegate to the underlying @type for casting
17
18
  # From raw input to Ruby type
18
19
  def cast(value)
19
- return value if skip_modifying_value(value)
20
+ return value if value.nil?
21
+ return value if value.is_a?(Array) && @max_repeat > 1
22
+
20
23
  @type.__filemaker_cast_to_ruby_object(value)
21
24
  rescue StandardError => e
22
25
  warn "[#{e.message}] Could not cast: #{name}=#{value}"
@@ -26,8 +29,22 @@ module Filemaker
26
29
  # Convert to Ruby type situable for making FileMaker update
27
30
  # For attr_writer
28
31
  def serialize_for_update(value)
29
- return value if skip_modifying_value(value)
30
- @type.__filemaker_serialize_for_update(value)
32
+ return value if value.nil?
33
+
34
+ if value.is_a?(Array) && @max_repeat > 1
35
+ value.map { |v| @type.__filemaker_serialize_for_update(v) }
36
+
37
+ # @max_repeat.times do |idx|
38
+ # index = idx + 1
39
+ # repeated_field_name = "@#{@name}__#{index}"
40
+ # repeated_field_value = @type.__filemaker_serialize_for_update(value[idx])
41
+ # instance_eval do
42
+ # instance_variable_set(repeated_field_name, repeated_field_value)
43
+ # end
44
+ # end
45
+ else
46
+ @type.__filemaker_serialize_for_update(value)
47
+ end
31
48
  rescue StandardError => e
32
49
  warn "[#{e.message}] Could not serialize for update: #{name}=#{value}"
33
50
  value
@@ -35,21 +52,16 @@ module Filemaker
35
52
 
36
53
  # Convert to Ruby type situable for making FileMaker query
37
54
  def serialize_for_query(value)
38
- return value if skip_modifying_value(value)
55
+ return value if value.nil?
56
+ return value if value =~ /^==|=\*/
57
+ return value if value =~ /(\.\.\.)/
58
+ return value if value =~ /\A(<|<=|>|>=)/
59
+
39
60
  @type.__filemaker_serialize_for_query(value)
40
61
  rescue StandardError => e
41
62
  warn "[#{e.message}] Could not serialize for query: #{name}=#{value}"
42
63
  value
43
64
  end
44
-
45
- # Doc why we skip it!
46
- # TODO - we may need to customize it for query and update. For example
47
- # query will bypass `==`, but update do not need to care.
48
- def skip_modifying_value(value)
49
- return true if value.nil?
50
- return true if value =~ /^==|=\*/
51
- return true if value =~ /(\.\.\.)/
52
- end
53
65
  end
54
66
  end
55
67
  end
@@ -64,6 +64,24 @@ module Filemaker
64
64
  field_names.each do |name|
65
65
  add_field(name, Filemaker::Model::Type.registry[type], options)
66
66
  create_accessors(name)
67
+
68
+ next unless options[:max_repeat] && options[:max_repeat] > 1
69
+
70
+ # We have repeating fields
71
+ # It will create [max_repeat] number of attribute with names like:
72
+ # xxx__1, xxx__2, xxx__3
73
+ # Their fm_name will be xxx(1), xxx(2), xxx(3)
74
+ options[:max_repeat].times do |idx|
75
+ index = idx + 1
76
+ repeated_field_name = "#{name}__#{index}"
77
+ fm_name = (options.fetch(:fm_name) { name }).to_s.downcase.freeze
78
+ add_field(
79
+ repeated_field_name,
80
+ Filemaker::Model::Type.registry[type],
81
+ options.merge(fm_name: "#{fm_name}(#{index})")
82
+ )
83
+ create_accessors(repeated_field_name)
84
+ end
67
85
  end
68
86
  end
69
87
  end
@@ -105,10 +123,17 @@ module Filemaker
105
123
 
106
124
  # Find FileMaker's real name given either the attribute name or the real
107
125
  # FileMaker name.
126
+ # FIXME - This may have ordering problem. If fm_name is the same as the
127
+ # field name.
108
128
  def find_field_by_name(name)
109
129
  name = name.to_s
110
130
  fields.values.find do |f|
111
131
  f.name == name || f.fm_name == name
132
+
133
+ # Unfortunately can't use this as builder.rb need to find field based
134
+ # on fm_name
135
+ # Always find by attribute name for now
136
+ # f.name == name
112
137
  end
113
138
  end
114
139
  end
@@ -6,6 +6,7 @@ module Filemaker
6
6
  :skip,
7
7
  :order,
8
8
  :find,
9
+ :id,
9
10
  :first,
10
11
  :recid,
11
12
  :in,
@@ -10,9 +10,9 @@ module Filemaker
10
10
  # Call save! but do not raise error.
11
11
  def save
12
12
  save!
13
- rescue StandardError
14
- errors.add(:base) << $! # Does this works?
15
- nil
13
+ rescue StandardError => e
14
+ errors.add(:base) << e.message # Does this works?
15
+ false
16
16
  end
17
17
 
18
18
  def save!
@@ -27,7 +27,7 @@ module Filemaker
27
27
  run_callbacks :create do
28
28
  options = {}
29
29
  yield options if block_given?
30
- resultset = api.new(fm_attributes, options)
30
+ resultset = api.new(create_attributes, options)
31
31
  changes_applied
32
32
  replace_new_data(resultset)
33
33
  end
@@ -51,10 +51,18 @@ module Filemaker
51
51
 
52
52
  def update_attributes(attrs = {})
53
53
  return self if attrs.blank?
54
+
54
55
  assign_attributes(attrs)
55
56
  save
56
57
  end
57
58
 
59
+ def update_attributes!(attrs = {})
60
+ return self if attrs.blank?
61
+
62
+ assign_attributes(attrs)
63
+ save!
64
+ end
65
+
58
66
  # Use -delete to remove the record backed by the model.
59
67
  # @return [Filemaker::Model] frozen instance
60
68
  def destroy
@@ -45,9 +45,9 @@ module Filemaker
45
45
  # job.company(true)
46
46
  define_method(name) do |force_reload = false|
47
47
  if force_reload
48
- @relations[name] = type.new(self, name, options)
48
+ @relations[name] = type.init(self, name, options)
49
49
  else
50
- @relations[name] ||= type.new(self, name, options)
50
+ @relations[name] ||= type.init(self, name, options)
51
51
  end
52
52
  end
53
53
 
@@ -58,8 +58,9 @@ module Filemaker
58
58
  # save and return the identity ID, then we update the parent's
59
59
  # reference_key.
60
60
  define_method("#{name}=") do |object|
61
- params = { "#{name}_id" => object.public_send("#{name}_id") }
62
- update_attributes(params)
61
+ return nil if object.nil?
62
+
63
+ @relations[name] = object
63
64
  end
64
65
 
65
66
  # Creator
@@ -22,7 +22,7 @@ module Filemaker
22
22
  end
23
23
 
24
24
  # Order: source_key first, reference_key next, then identity
25
- # all must be findable using `to find_field_by_name`
25
+ # all must be findable using `find_field_by_name`
26
26
  def final_reference_key
27
27
  target_class.find_field_by_name(source_key).try(:name) ||
28
28
  target_class.find_field_by_name(reference_key).try(:name) ||
@@ -36,11 +36,13 @@ module Filemaker
36
36
  # If the field value contains underscore or space like 'FM_notified'
37
37
  # or 'FM notified', a single `=` may not match correctly.
38
38
  def build_target
39
- @target = nil if reference_value.blank? || final_reference_key.blank?
40
-
41
- @target = target_class.where(
42
- final_reference_key => "=#{reference_value}"
43
- ).first
39
+ @target = if reference_value.blank? || final_reference_key.blank?
40
+ nil
41
+ else
42
+ target_class.where(
43
+ final_reference_key => "==#{reference_value}"
44
+ ).first
45
+ end
44
46
  end
45
47
  end
46
48
  end
@@ -42,6 +42,7 @@ module Filemaker
42
42
  def <<(*args)
43
43
  docs = args.flatten
44
44
  return concat(docs) if docs.size > 1
45
+
45
46
  if (doc = docs.first)
46
47
  create(doc)
47
48
  end
@@ -81,11 +82,13 @@ module Filemaker
81
82
  protected
82
83
 
83
84
  def build_target
84
- @target = [] if reference_value.blank? || final_reference_key.blank?
85
-
86
- @target = target_class.where(
87
- final_reference_key => "=#{reference_value}"
88
- )
85
+ @target = if reference_value.blank? || final_reference_key.blank?
86
+ []
87
+ else
88
+ target_class.where(
89
+ final_reference_key => "==#{reference_value}"
90
+ )
91
+ end
89
92
  end
90
93
  end
91
94
  end
@@ -6,7 +6,7 @@ module Filemaker
6
6
  class Proxy
7
7
  instance_methods.each do |method|
8
8
  undef_method(method) unless
9
- method =~ /(^__|^send|^object_id|^respond_to|^tap|^extend)/
9
+ method =~ /^(__.*|send|object_id|equal\?|respond_to\?|tap|public_send)$/
10
10
  end
11
11
 
12
12
  attr_accessor :owner, :target, :options
@@ -21,8 +21,15 @@ module Filemaker
21
21
  @class_name = options.fetch(:class_name) { name.to_s.classify }
22
22
  end
23
23
 
24
+ # Create will not return the proxy if target was NilClass
25
+ def self.init(owner, name, options)
26
+ new_instance = new(owner, name, options)
27
+ new_instance.target.nil? ? nil : new_instance
28
+ end
29
+
24
30
  def target_class
25
31
  return @class_name if @class_name.is_a?(Class)
32
+
26
33
  @class_name.constantize
27
34
  end
28
35
 
@@ -16,7 +16,7 @@ module Filemaker
16
16
  chains.delete(:in)
17
17
 
18
18
  @selector ||= {}
19
- selector.merge!(klass.with_model_fields(criterion))
19
+ selector.merge!(klass.with_model_fields_for_query(criterion))
20
20
  yield options if block_given?
21
21
  self
22
22
  end
@@ -39,7 +39,8 @@ module Filemaker
39
39
  return where(criterion) if criterion.is_a? Hash
40
40
 
41
41
  # Find using model ID (may not be the -recid)
42
- id = criterion.to_s.gsub(/\A=*/, '=') # Always append '=' for ID
42
+ # Always append double '=' for ID instead of just one '='
43
+ id = criterion.to_s.gsub(/\A=*/, '==')
43
44
 
44
45
  # If we are finding with ID, we just limit to one and return
45
46
  # immediately. Last resort is to use the recid to find.
@@ -68,11 +69,7 @@ module Filemaker
68
69
  chains.delete(:in)
69
70
  @selector ||= {}
70
71
 
71
- criterion = if operator == 'bw'
72
- klass.with_model_fields(criterion, use_query: false)
73
- else
74
- klass.with_model_fields(criterion)
75
- end
72
+ criterion = klass.with_model_fields_for_query(criterion)
76
73
 
77
74
  criterion.each_key do |key|
78
75
  selector["#{key}.op"] = operator
@@ -87,6 +84,7 @@ module Filemaker
87
84
  end
88
85
  end
89
86
 
87
+ alias id find
90
88
  alias equals eq
91
89
  alias contains cn
92
90
  alias begins_with bw
@@ -115,7 +113,7 @@ module Filemaker
115
113
  @selector ||= []
116
114
 
117
115
  become_array(criterion).each do |hash|
118
- accepted_hash = klass.with_model_fields(hash)
116
+ accepted_hash = klass.with_model_fields_for_query(hash)
119
117
  accepted_hash['-omit'] = true if negating
120
118
  @selector << accepted_hash
121
119
  end
@@ -153,7 +151,7 @@ module Filemaker
153
151
  end
154
152
 
155
153
  @selector ||= {}
156
- selector.merge!(klass.with_model_fields(criterion))
154
+ selector.merge!(klass.with_model_fields_for_query(criterion))
157
155
  options[:lop] = 'or'
158
156
  yield options if block_given?
159
157
  self
@@ -1,5 +1,6 @@
1
1
  require 'filemaker/model/types/text'
2
2
  require 'filemaker/model/types/date'
3
+ require 'filemaker/model/types/date_time'
3
4
  require 'filemaker/model/types/time'
4
5
  require 'filemaker/model/types/big_decimal'
5
6
  require 'filemaker/model/types/integer'
@@ -21,7 +22,8 @@ module Filemaker
21
22
  register(:string, Filemaker::Model::Types::Text)
22
23
  register(:text, Filemaker::Model::Types::Text)
23
24
  register(:date, Filemaker::Model::Types::Date)
24
- register(:datetime, Filemaker::Model::Types::Time)
25
+ register(:datetime, Filemaker::Model::Types::DateTime)
26
+ register(:time, Filemaker::Model::Types::Time)
25
27
  register(:money, Filemaker::Model::Types::BigDecimal)
26
28
  register(:number, Filemaker::Model::Types::BigDecimal)
27
29
  register(:integer, Filemaker::Model::Types::Integer)
@@ -3,17 +3,23 @@ module Filemaker
3
3
  module Types
4
4
  class BigDecimal
5
5
  def self.__filemaker_cast_to_ruby_object(value)
6
+ return nil if value.nil?
6
7
  return value if value.is_a?(::BigDecimal)
8
+
7
9
  BigDecimal(value.to_s)
8
10
  end
9
11
 
10
12
  def self.__filemaker_serialize_for_update(value)
13
+ return nil if value.nil?
11
14
  return value if value.is_a?(::BigDecimal)
15
+
12
16
  BigDecimal(value.to_s)
13
17
  end
14
18
 
15
19
  def self.__filemaker_serialize_for_query(value)
20
+ return nil if value.nil?
16
21
  return value if value.is_a?(::BigDecimal)
22
+
17
23
  BigDecimal(value.to_s)
18
24
  end
19
25
  end
@@ -3,19 +3,25 @@ module Filemaker
3
3
  module Types
4
4
  class Date
5
5
  def self.__filemaker_cast_to_ruby_object(value)
6
+ return nil if value.nil?
6
7
  return value if value.is_a?(::Date)
8
+
7
9
  ::Date.parse(value.to_s)
8
10
  end
9
11
 
10
12
  def self.__filemaker_serialize_for_update(value)
13
+ return nil if value.nil?
11
14
  return value if value.is_a?(::Date)
15
+
12
16
  ::Date.parse(value.to_s)
13
17
  end
14
18
 
15
19
  def self.__filemaker_serialize_for_query(value)
16
20
  # If we are doing date range query like
17
21
  # Model.where(date: '12/2018')
22
+ return nil if value.nil?
18
23
  return value if value.is_a?(::Date) || value.is_a?(String)
24
+
19
25
  ::Date.parse(value.to_s)
20
26
  end
21
27
  end
@@ -0,0 +1,28 @@
1
+ module Filemaker
2
+ module Model
3
+ module Types
4
+ class DateTime
5
+ def self.__filemaker_cast_to_ruby_object(value)
6
+ return nil if value.nil?
7
+ return value if value.is_a?(::Time)
8
+
9
+ ::Time.parse(value.to_s)
10
+ end
11
+
12
+ def self.__filemaker_serialize_for_update(value)
13
+ return nil if value.nil?
14
+ return value if value.is_a?(::Time)
15
+
16
+ ::Time.parse(value.to_s)
17
+ end
18
+
19
+ def self.__filemaker_serialize_for_query(value)
20
+ return nil if value.nil?
21
+ return value if value.is_a?(::Time) || value.is_a?(String)
22
+
23
+ ::Time.parse(value.to_s)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -3,17 +3,23 @@ module Filemaker
3
3
  module Types
4
4
  class Integer
5
5
  def self.__filemaker_cast_to_ruby_object(value)
6
+ return nil if value.nil?
6
7
  return value if value.is_a?(::Integer)
8
+
7
9
  value.to_i
8
10
  end
9
11
 
10
12
  def self.__filemaker_serialize_for_update(value)
13
+ return nil if value.nil?
11
14
  return value if value.is_a?(::Integer)
15
+
12
16
  value.to_i
13
17
  end
14
18
 
15
19
  def self.__filemaker_serialize_for_query(value)
20
+ return nil if value.nil?
16
21
  return value if value.is_a?(::Integer)
22
+
17
23
  value.to_i
18
24
  end
19
25
  end
@@ -3,14 +3,20 @@ module Filemaker
3
3
  module Types
4
4
  class Text
5
5
  def self.__filemaker_cast_to_ruby_object(value)
6
+ return nil if value.nil?
7
+
6
8
  value.to_s
7
9
  end
8
10
 
9
11
  def self.__filemaker_serialize_for_update(value)
12
+ return nil if value.nil?
13
+
10
14
  value.to_s
11
15
  end
12
16
 
13
17
  def self.__filemaker_serialize_for_query(value)
18
+ return nil if value.nil?
19
+
14
20
  value.to_s
15
21
  end
16
22
  end
@@ -3,18 +3,25 @@ module Filemaker
3
3
  module Types
4
4
  class Time
5
5
  def self.__filemaker_cast_to_ruby_object(value)
6
- return value if value.is_a?(::Time)
6
+ return nil if value.nil?
7
+ return value.strftime("%H:%M") if value.is_a?(::Time)
8
+
7
9
  ::Time.parse(value.to_s)
8
10
  end
9
11
 
10
12
  def self.__filemaker_serialize_for_update(value)
11
- return value if value.is_a?(::Time)
12
- ::Time.parse(value.to_s)
13
+ return nil if value.nil?
14
+ return value.strftime("%H:%M") if value.is_a?(::Time) || value.is_a?(::DateTime)
15
+
16
+ # Could be a string like "09:00" already
17
+ value
13
18
  end
14
19
 
15
20
  def self.__filemaker_serialize_for_query(value)
16
- return value if value.is_a?(::Time) || value.is_a?(String)
17
- ::Time.parse(value.to_s)
21
+ return nil if value.nil?
22
+ return value.strftime("%H:%M") if value.is_a?(::Time)
23
+
24
+ value
18
25
  end
19
26
  end
20
27
  end
@@ -22,8 +22,7 @@ module Filemaker
22
22
  # `field` is Nokogiri::XML::Element
23
23
  field_name = field['name']
24
24
  # Right now, I do not want to mess with the field name
25
- # field_name.gsub!(Regexp.new(portal_table_name + '::'), '')
26
- # \if portal_table_name
25
+ # field_name.gsub!(Regexp.new(portal_table_name + '::'), '') if portal_table_name
27
26
  datum = []
28
27
 
29
28
  metadata_fields = if portal_table_name
@@ -45,15 +44,15 @@ module Filemaker
45
44
  end
46
45
 
47
46
  def [](key)
48
- raise(Filemaker::Errors::InvalidFieldError, "Invalid field: #{key}") \
49
- unless key?(key)
47
+ raise(Filemaker::Errors::InvalidFieldError, "Invalid field: #{key}") unless key?(key)
48
+
50
49
  super
51
50
  end
52
51
 
53
52
  def []=(key, value)
54
53
  if @ready
55
- raise(Filemaker::Errors::InvalidFieldError, "Invalid field: #{key}") \
56
- unless key?(key)
54
+ raise(Filemaker::Errors::InvalidFieldError, "Invalid field: #{key}") unless key?(key)
55
+
57
56
  @dirty[key] = value
58
57
  else
59
58
  super
@@ -80,6 +79,7 @@ module Filemaker
80
79
 
81
80
  def normalize_data(datum)
82
81
  return nil if datum.empty?
82
+
83
83
  datum.size == 1 ? datum.first : datum
84
84
  end
85
85
 
@@ -87,6 +87,7 @@ module Filemaker
87
87
  method = symbol.to_s
88
88
  return self[method] if key?(method)
89
89
  return @dirty[$`] = args.first if method =~ /(=)$/ && key?($`)
90
+
90
91
  super
91
92
  end
92
93
 
@@ -1,3 +1,4 @@
1
+ require 'forwardable'
1
2
  require 'typhoeus'
2
3
  require 'filemaker/configuration'
3
4
 
@@ -102,12 +103,10 @@ module Filemaker
102
103
 
103
104
  args.each do |key, value|
104
105
  case value
105
- when DateTime
106
+ when DateTime, Time
106
107
  args[key] = value.strftime('%m/%d/%Y %H:%M:%S')
107
108
  when Date
108
109
  args[key] = value.strftime('%m/%d/%Y')
109
- when Time
110
- args[key] = value.strftime('%H:%M')
111
110
  else
112
111
  # Especially for range operator (...), we want to output as String
113
112
  args[key] = value.to_s
@@ -1,3 +1,3 @@
1
1
  module Filemaker
2
- VERSION = '1.0.0'.freeze
2
+ VERSION = '1.0.1'.freeze
3
3
  end
@@ -25,6 +25,13 @@ describe Filemaker::Model::Builder do
25
25
  expect(subject.modify_date).to eq(Date.parse('2014-08-12'))
26
26
  end
27
27
 
28
+ it 'has repeated fields' do
29
+ expect(subject.bonus).to eq [BigDecimal(1000), BigDecimal(2000), BigDecimal(3000)]
30
+ expect(subject.bonus__1).to eq BigDecimal(1000)
31
+ expect(subject.bonus__2).to eq BigDecimal(2000)
32
+ expect(subject.bonus__3).to eq BigDecimal(3000)
33
+ end
34
+
28
35
  it 'is not dirty' do
29
36
  expect(subject.changed?).to eq false
30
37
  expect(subject.changes).to be_empty
@@ -60,7 +60,13 @@ describe Filemaker::Model::Criteria do
60
60
  it 'will use the identity to find' do
61
61
  allow(criteria).to receive(:first).and_return([])
62
62
  criteria.find(22)
63
- expect(criteria.selector).to eq({ 'ca id' => '=22' })
63
+ expect(criteria.selector).to eq({ 'ca id' => '==22' })
64
+ end
65
+
66
+ it 'alias id to find' do
67
+ allow(criteria).to receive(:first).and_return([])
68
+ criteria.id(22)
69
+ expect(criteria.selector).to eq({ 'ca id' => '==22' })
64
70
  end
65
71
  end
66
72
 
@@ -34,10 +34,11 @@ describe Filemaker::Model::Relations do
34
34
  @model.candidate.target
35
35
  end
36
36
 
37
- it 'uses identity for missing reference_key' do
38
- expect(@model.member.reference_key).to eq 'member_id'
39
- expect(@model.member.final_reference_key).to eq 'id'
40
- end
37
+ # Comment this test - because we return nil instead of Proxy object
38
+ # it 'uses identity for missing reference_key' do
39
+ # expect(@model.member.reference_key).to eq 'member_id'
40
+ # expect(@model.member.final_reference_key).to eq 'id'
41
+ # end
41
42
 
42
43
  it 'proxy blank?' do
43
44
  expect(@model.candidate.blank?).to eq false
@@ -79,9 +80,10 @@ describe Filemaker::Model::Relations do
79
80
  expect(@model.manager.reference_key).to eq 'manager_id'
80
81
  end
81
82
 
82
- it 'another_manager has different reference_key' do
83
- expect(@model.another_manager.reference_key).to eq :candidate_id
84
- end
83
+ # Comment this test - because we return nil instead of Proxy object
84
+ # it 'another_manager has different reference_key' do
85
+ # expect(@model.another_manager.reference_key).to eq :candidate_id
86
+ # end
85
87
  end
86
88
  end
87
89
 
@@ -105,7 +107,7 @@ describe Filemaker::Model::Relations do
105
107
  end
106
108
 
107
109
  it 'returns criteria instead of an array of model objects' do
108
- expect(@model.posts).to be_a Filemaker::Model::Criteria
110
+ expect(@model.posts.class).to eq Filemaker::Model::Criteria
109
111
  end
110
112
  end
111
113
 
@@ -35,26 +35,45 @@ describe Filemaker::Model::Types do
35
35
  end
36
36
  end
37
37
 
38
- context 'Types::Time' do
38
+ context 'Types::DateTime' do
39
39
  it 'assign as a time' do
40
40
  model.updated_at = Time.new(2018, 1, 1, 12, 12, 12)
41
41
  expect(model.updated_at).to be_a Time
42
42
  expect(model.updated_at).to eq Time.new(2018, 1, 1, 12, 12, 12)
43
43
  end
44
44
 
45
+ it 'assign as a datetime but return as time' do
46
+ model.updated_at = DateTime.new(2018, 1, 1, 12, 12, 12)
47
+ expect(model.updated_at).to be_a Time
48
+ expect(model.updated_at).to eq Time.parse(model.updated_at.to_s)
49
+ end
50
+
45
51
  it 'can query as a string' do
46
52
  c = MyModel.where(updated_at: '2018')
47
53
  expect(c.selector['modifieddate']).to be_a String
48
54
  expect(c.selector['modifieddate']).to eq '2018'
49
55
  end
50
56
 
51
- it 'can query as a time' do
57
+ it 'can query as a datetime' do
52
58
  c = MyModel.where(updated_at: Time.new(2018, 1, 1, 12, 12, 12))
53
59
  expect(c.selector['modifieddate']).to be_a Time
54
60
  expect(c.selector['modifieddate']).to eq Time.new(2018, 1, 1, 12, 12, 12)
55
61
  end
56
62
  end
57
63
 
64
+ context 'Types::DateTime' do
65
+ it 'assign as time' do
66
+ model.time_in = DateTime.new(2019, 1, 1, 9, 0, 0)
67
+ expect(model.time_in).to be_a String
68
+ expect(model.time_in).to eq '09:00'
69
+ end
70
+
71
+ it 'query with HH:MM format' do
72
+ c = MyModel.where(time_in: Time.new(2019, 1, 1, 15, 45, 0))
73
+ expect(c.selector['time_in']).to eq '15:45'
74
+ end
75
+ end
76
+
58
77
  context 'Types::BigDecimal' do
59
78
  it 'assign as a big decimal' do
60
79
  model.salary = '25'
@@ -88,7 +88,12 @@ describe Filemaker::Model do
88
88
  'created_at',
89
89
  'modifieddate',
90
90
  'salary',
91
- 'passage of time'
91
+ 'bonus',
92
+ 'bonus(1)',
93
+ 'bonus(2)',
94
+ 'bonus(3)',
95
+ 'passage of time',
96
+ 'time_in'
92
97
  ]
93
98
  end
94
99
 
@@ -103,23 +108,23 @@ describe Filemaker::Model do
103
108
  # end
104
109
 
105
110
  it 'accepts date range as string' do
106
- model.created_at = '1/1/2016...1/31/2016'
107
- expect(model.created_at).to be_a String
111
+ c = MyModel.where(created_at: '1/1/2016...1/31/2016')
112
+ expect(c.selector['created_at']).to be_a String
108
113
  end
109
114
 
110
115
  it 'accepts number range as string' do
111
- model.salary = '1000...2000'
112
- expect(model.salary).to be_a String
116
+ c = MyModel.where(salary: '1000...2000')
117
+ expect(c.selector['salary']).to be_a String
113
118
  end
114
119
 
115
120
  it 'accepts == for any type' do
116
- model.salary = '=='
117
- expect(model.salary).to eq '=='
121
+ c = MyModel.where(salary: '==')
122
+ expect(c.selector['salary']).to eq '=='
118
123
  end
119
124
 
120
125
  it 'accepts =* for any type' do
121
- model.age = '=*'
122
- expect(model.age).to eq '=*'
126
+ c = MyModel.where(age: '=*')
127
+ expect(c.selector['passage of time']).to eq '=*'
123
128
  end
124
129
 
125
130
  it 'check for presence of name and salary' do
@@ -32,6 +32,7 @@ class Job
32
32
  string :status
33
33
  string :jdid, fm_name: 'JDID'
34
34
  date :modify_date, fm_name: 'modify date'
35
+ money :bonus, max_repeat: 3
35
36
  end
36
37
 
37
38
  class Member
@@ -72,7 +73,9 @@ class MyModel
72
73
  date :created_at
73
74
  datetime :updated_at, fm_name: 'ModifiedDate'
74
75
  money :salary
76
+ money :bonus, max_repeat: 3
75
77
  integer :age, fm_name: 'passage of time'
78
+ time :time_in
76
79
  end
77
80
 
78
81
  class Project
@@ -13,6 +13,7 @@
13
13
  <field-definition auto-enter="no" four-digit-year="no" global="no" max-repeat="1" name="experience &amp; qualification" not-empty="no" numeric-only="no" result="text" time-of-day="no" type="normal"/>
14
14
  <field-definition auto-enter="no" four-digit-year="no" global="no" max-repeat="1" name="location" not-empty="no" numeric-only="no" result="text" time-of-day="no" type="normal"/>
15
15
  <field-definition auto-enter="no" four-digit-year="no" global="no" max-repeat="1" name="Salary" not-empty="no" numeric-only="no" result="text" time-of-day="no" type="normal"/>
16
+ <field-definition auto-enter="no" four-digit-year="no" global="no" max-repeat="3" name="Bonus" not-empty="no" numeric-only="no" result="text" time-of-day="no" type="normal"/>
16
17
  <field-definition auto-enter="no" four-digit-year="no" global="no" max-repeat="1" name="JD Cat" not-empty="no" numeric-only="no" result="text" time-of-day="no" type="normal"/>
17
18
  <field-definition auto-enter="no" four-digit-year="no" global="no" max-repeat="1" name="JobType" not-empty="no" numeric-only="no" result="text" time-of-day="no" type="normal"/>
18
19
  <field-definition auto-enter="yes" four-digit-year="no" global="no" max-repeat="1" name="modify date" not-empty="no" numeric-only="no" result="date" time-of-day="no" type="normal"/>
@@ -39,6 +40,11 @@
39
40
  <field name="Salary">
40
41
  <data>Negotiable</data>
41
42
  </field>
43
+ <field name="Bonus">
44
+ <data>1000</data>
45
+ <data>2000</data>
46
+ <data>3000</data>
47
+ </field>
42
48
  <field name="JD Cat">
43
49
  <data>IT &amp; Telecommunications</data>
44
50
  </field>
@@ -74,6 +80,11 @@
74
80
  <field name="Salary">
75
81
  <data>Negotiable</data>
76
82
  </field>
83
+ <field name="Bonus">
84
+ <data>4000</data>
85
+ <data>5000</data>
86
+ <data>6000</data>
87
+ </field>
77
88
  <field name="JD Cat">
78
89
  <data>Administrative &amp; Related</data>
79
90
  </field>
@@ -109,6 +120,11 @@
109
120
  <field name="Salary">
110
121
  <data>6000 - 7000</data>
111
122
  </field>
123
+ <field name="Bonus">
124
+ <data>7000</data>
125
+ <data>8000</data>
126
+ <data>9000</data>
127
+ </field>
112
128
  <field name="JD Cat">
113
129
  <data>IT &amp; Telecommunications</data>
114
130
  </field>
@@ -144,6 +160,11 @@
144
160
  <field name="Salary">
145
161
  <data>Negotiable</data>
146
162
  </field>
163
+ <field name="Bonus">
164
+ <data>1000</data>
165
+ <data></data>
166
+ <data></data>
167
+ </field>
147
168
  <field name="JD Cat">
148
169
  <data>IT &amp; Telecommunications</data>
149
170
  </field>
@@ -179,6 +200,11 @@
179
200
  <field name="Salary">
180
201
  <data>3000 - 5500</data>
181
202
  </field>
203
+ <field name="Bonus">
204
+ <data></data>
205
+ <data></data>
206
+ <data></data>
207
+ </field>
182
208
  <field name="JD Cat">
183
209
  <data>IT &amp; Telecommunications</data>
184
210
  </field>
@@ -196,4 +222,4 @@
196
222
  </field>
197
223
  </record>
198
224
  </resultset>
199
- </fmresultset>
225
+ </fmresultset>
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: filemaker
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - mech
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-06-21 00:00:00.000000000 Z
11
+ date: 2019-08-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: typhoeus
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.7'
33
+ version: 1.10.4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.7'
40
+ version: 1.10.4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: activemodel
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,30 +70,30 @@ dependencies:
70
70
  name: bundler
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '1.13'
75
+ version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '1.13'
82
+ version: '0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: rake
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - "~>"
87
+ - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '12.0'
89
+ version: '0'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - "~>"
94
+ - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '12.0'
96
+ version: '0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: rspec
99
99
  requirement: !ruby/object:Gem::Requirement
@@ -193,6 +193,7 @@ files:
193
193
  - lib/filemaker/model/type.rb
194
194
  - lib/filemaker/model/types/big_decimal.rb
195
195
  - lib/filemaker/model/types/date.rb
196
+ - lib/filemaker/model/types/date_time.rb
196
197
  - lib/filemaker/model/types/email.rb
197
198
  - lib/filemaker/model/types/integer.rb
198
199
  - lib/filemaker/model/types/text.rb
@@ -253,8 +254,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
253
254
  - !ruby/object:Gem::Version
254
255
  version: '0'
255
256
  requirements: []
256
- rubyforge_project:
257
- rubygems_version: 2.7.7
257
+ rubygems_version: 3.0.6
258
258
  signing_key:
259
259
  specification_version: 4
260
260
  summary: A Ruby wrapper to FileMaker XML API.