dm-filemaker-adapter 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 CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- OTEzN2JmZDA1YjA0Yzg0ZjcxMjllNDkxZDQxM2E4NTE0NzhjYzEwZA==
4
+ NzA1ZDIzZGI0NWJkMDFmNjIyM2Q2NGMwMTQxZWM3MGMwYWU2ZDM0Mg==
5
5
  data.tar.gz: !binary |-
6
- YmI3MzhmYWEyODk0MjYwNzNlOTI0NGNhYTJhMWFiYjU1MjA4YjM4OA==
6
+ M2M4OWE4Nzc1NmMyYTViNmI1NzAwZTFmNDlkNDUyZGY3MDAxMGJkOQ==
7
7
  SHA512:
8
8
  metadata.gz: !binary |-
9
- NjBiZTQxMmFiOWEzYmMxYWUwNzkxOTcyM2QzNThlMDgyMGJjMDA1OTNmYWNi
10
- OGU0MmM2YTI4Yzc0Y2YxMDI2MGVmMDRiZGEwYTUyYzYzMjMwOGFiYzU2ZTAz
11
- M2JhNzRmODE0ZjkwZGM4ZDBhZGIxNTM4YzIwYzNhNzEwM2VjZDk=
9
+ Y2RmYTdjNjg2YzgwNTFkYzc1ODUyMTAyOThkMzc1ZDIwYTk0ZDA1ZTI1ZDhi
10
+ ZDRhODFhMjNlNzJhOGNjOTM0YmNkNmQyZjczYzdhMWU0NmVlNjcyM2Y4OTgz
11
+ NjQ1MDI1M2U1NDZkNzY5NmMxYzkzNDJjYTk4NTg4MGQ2YTFmYmQ=
12
12
  data.tar.gz: !binary |-
13
- MTRjNDRhYWM3NzkxMjkzNTkwOGQ1N2M0ZmNiYzdkYWU0NGJhMDMyZTc3Njgx
14
- MmQzYzNlODE2M2UzMWFlOWQ4Y2M0OTYzOTA4NWI0OGZkNTQ2YTdkZjZlMGE1
15
- MDllNzgxMDBhNzMxMzc0ZTNkM2U3MTg2NjQzNmQ4Mzk0N2I5ODM=
13
+ YWI2ZjRhM2Q5MmYzOTdhOWM3NmNjNjM3ZDJlNzU1OGY4ZDZhYjQ2ZGMyNWM1
14
+ NjBlZGJkOWJmNDA1N2NlYTI0OGQ0M2FmODgzZGNjNGI4NTQ4MzVhN2IxYmQw
15
+ OTRmNjhkNzMyMmVmODFjZTI2NTFmODI1OGFiOTgyNmIwMDVlMjU=
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  A Filemaker adapter for DataMapper, allowing DataMapper to use Filemaker Server as a datastore.
4
4
 
5
- dm-filemaker-adapter uses the ginjo-rfm gem as the backend command and xml parser. Ginjo-rfm is a full featured filemaker-ruby adapter that exposes most of Filemaker's xml interface functionality in ruby. dm-filemaker-adapter doesn't tap into all of rfm's features, but rather, it provides DataMapper the ability to use Filemaker Server as a backend datastore. All of the basic functionality of DataMapper's CRUD interface is supported, including compound queries and OR queries (using Filemaker's -findquery command), query operators like :field.gt=>..., lazy-loading where possible, first & last record, aggregate queries, ranges, field mapping, and more.
5
+ dm-filemaker-adapter uses the ginjo-rfm gem as the backend command and xml parser. Ginjo-rfm is a full featured filemaker-ruby adapter that exposes most of Filemaker's xml interface functionality in ruby. dm-filemaker-adapter doesn't tap into all of rfm's features, but rather, dm-filemaker-adapter provides DataMapper the ability to use Filemaker Server as a backend datastore. All of the basic functionality of DataMapper's CRUD interface is supported, including compound queries and 'or' queries (using Filemaker's -findquery command), query operators like :field.gt=>..., lazy-loading where possible, first & last record, aggregate queries, ranges, field mapping, and more.
6
6
 
7
7
  ## Installation
8
8
 
@@ -20,6 +20,8 @@ Or install it yourself as:
20
20
 
21
21
  ## Usage
22
22
 
23
+ # ruby
24
+
23
25
  DB_CONFIG = {
24
26
  adapter: 'filemaker',
25
27
  host: 'my.server.com',
@@ -32,25 +34,56 @@ Or install it yourself as:
32
34
 
33
35
  class User
34
36
  include DataMapper::Resource
35
- storage_names[:default] = 'user_xml' # This is your filemaker layout for the user table.
37
+ storage_names[:default] = 'user_xml' # This is the name of a filemaker layout representing the table you're modeling.
36
38
 
37
39
  # Property & field names in this list must be lowercase, regardless of what they are in Filemaker.
38
40
 
39
- property :userid, String, :key=>true, :required=>false
40
- property :email, String
41
- property :login, String, :field=>'username'
42
- property :updated, DateTime, :field=>'updated_at'
41
+ property :id, Serial
42
+ property :username, String, :length => 128, :unique => true, :required => true,
43
+ :default => lambda {|r,v| r.instance_variable_get :@email}
44
+ property :email, String, :length => 128, :unique => true, :required => true, :format=>:email_address
45
+ property :updated_at, DateTime, :field=>'modification_timestamp'
43
46
  property :encrypted_password, BCryptPassword
44
47
  end
45
48
 
46
49
  DataMapper.finalize
47
50
 
48
- User.get 'usr1035'
49
- User.first :email => 'wbr'
50
- User.all :updated.gt => 3.days.ago
51
-
52
-
53
-
54
-
51
+
52
+
53
+ # create records
54
+ User.create(:email => 'abc@company.com', :username => 'abc')
55
+
56
+ # get a specific user id
57
+ User.get '1035'
58
+
59
+ # first record that matches exactly 'name'
60
+ User.first :username => 'name'
61
+
62
+ # all records updated since 3 days ago
63
+ User.all :updated.gt => Time.now-3*24*60*60
64
+
65
+ # records 10 thru 20, ordered by :id (the range is resolved by filemaker, before records are returned!)
66
+ User.all(:order => :id)[10..20]
67
+
68
+ # use the union operator to create 2 find requests in a filemaker 'OR' operation
69
+ User.all(:email => 'abc@company.com', :activated_at.gt => '1/1/1980') | \
70
+ User.all(:username => 'abc', :activated_at.gt => '1/1/1980')
71
+
72
+ # which gets translated to the filemaker query
73
+ User.find [
74
+ {:email => 'abc@company.com', :activated_at => '>1/1/1980'},
75
+ {:username => 'abc', :activated_at.gt => '>1/1/1980'}
76
+ ]
77
+
78
+ # use the intersection operator to combine multiple search criteria in a filemaker 'AND' operation
79
+ User.all(:email => 'abc@company.com', :activated_at.gt => '1/1/2015') & \
80
+ User.all(:email => 'abc@company.com', :activated_at.lt => '5/1/2015')
81
+
82
+ # you can also write this as
83
+ User.all(:email => 'abc@company.com', :activated_at.gt => '1/1/2015', :activated_at.lt => '5/1/2015')
55
84
 
85
+ # both of the above get translated to the filemaker query
86
+ User.find(:email => 'abc@company.com', :activated_at => '>1/1/2015 <5/1/2015')
87
+
88
+
56
89
 
@@ -1,7 +1,5 @@
1
1
  require 'dm-core'
2
2
  require 'dm-core/adapters/abstract_adapter'
3
+ require 'rfm'
3
4
  require "dm-filemaker-adapter/version"
4
5
  require 'dm-filemaker-adapter/adapter'
5
- require 'rfm'
6
-
7
-
@@ -1,35 +1,6 @@
1
1
  # Property & field names in dm-filemaker-adapter models must be declared lowercase, regardless of what they are in FMP.
2
-
2
+ require 'dm-filemaker-adapter/core_patches'
3
3
  module DataMapper
4
- [Resource, Model, Adapters]
5
-
6
- # All this to tack on class and instance methods to the model/resource.
7
- module Resource
8
- class << self
9
- alias_method :included_orig, :included
10
- def included(klass)
11
- included_orig(klass)
12
- if klass.repository.adapter.to_s[/filemaker/i]
13
- klass.instance_eval do
14
- extend repository.adapter.class::ModelMethods
15
- include repository.adapter.class::ResourceMethods
16
- end
17
- end
18
- end
19
- end
20
- end
21
-
22
- module Model
23
- #attr_accessor :last_query
24
- alias_method :finalize_orig, :finalize
25
- def finalize(*args)
26
- property :record_id, Integer, :lazy=>false
27
- property :mod_id, Integer, :lazy=>false
28
- finalize_orig
29
- end
30
- end
31
-
32
-
33
4
 
34
5
  module Adapters
35
6
 
@@ -37,24 +8,6 @@ module DataMapper
37
8
  @fmresultset_template_path = File.expand_path('../dm-fmresultset.yml', __FILE__).to_s
38
9
  class << self; attr_accessor :fmresultset_template_path; end
39
10
  VERSION = DataMapper::FilemakerAdapter::VERSION
40
-
41
-
42
- ### UTILITY METHODS ###
43
-
44
- # Class methods extended onto model.
45
- module ModelMethods
46
- def layout
47
- Rfm.layout(storage_name, repository.adapter.options.symbolize_keys)
48
- end
49
- end
50
-
51
- # Instance methods included in model.
52
- module ResourceMethods
53
- def layout
54
- model.layout
55
- end
56
- end
57
-
58
11
 
59
12
 
60
13
  ### ADAPTER CORE METHODS ###
@@ -74,10 +27,10 @@ module DataMapper
74
27
  #
75
28
  # @api semipublic
76
29
  def create(resources)
77
- #resources[0].model.last_query = resources
30
+ resources[0].model.last_query = resources
78
31
  counter = 0
79
32
  resources.each do |resource|
80
- fm_params = fmp_attributes resource.dirty_attributes
33
+ fm_params = prepare_fmp_attributes(resource.dirty_attributes)
81
34
  rslt = layout(resource.model).create(fm_params, :template=>self.class.fmresultset_template_path)
82
35
  merge_fmp_response(resource, rslt[0])
83
36
  counter +=1
@@ -104,12 +57,14 @@ module DataMapper
104
57
  # end
105
58
  #
106
59
  def read(query)
107
- #query.model.last_query = query
60
+ query.model.last_query = query
108
61
  #y query
109
62
  _layout = layout(query.model)
110
- opts = fmp_options(query)
63
+ opts = query.fmp_options
64
+ #puts "FMP OPTIONS #{opts.inspect}"
111
65
  opts[:template] = self.class.fmresultset_template_path
112
- prms = fmp_query(query.conditions) #.to_set.first)
66
+ prms = query.to_fmp_query
67
+ #puts "ADAPTER#read fmp_query built: #{prms.inspect}"
113
68
  rslt = prms.empty? ? _layout.all(opts) : _layout.find(prms, opts)
114
69
  rslt.dup.each_with_index(){|r, i| rslt[i] = r.to_h}
115
70
  rslt
@@ -118,12 +73,12 @@ module DataMapper
118
73
  # Takes a query and returns number of matched records.
119
74
  # An empty query will return the total record count
120
75
  def aggregate(query)
121
- #query.model.last_query = query
76
+ query.model.last_query = query
122
77
  #y query
123
78
  _layout = layout(query.model)
124
- opts = fmp_options(query)
79
+ opts = query.fmp_options
125
80
  opts[:template] = self.class.fmresultset_template_path
126
- prms = fmp_query(query.conditions) #.to_set.first)
81
+ prms = fmp_query(query.conditions)
127
82
  #[prms.empty? ? _layout.all(:max_records=>0).foundset_count : _layout.count(prms)]
128
83
  [prms.empty? ? _layout.view.total_count : _layout.count(prms)]
129
84
  end
@@ -145,8 +100,8 @@ module DataMapper
145
100
  #
146
101
  # @api semipublic
147
102
  def update(attributes, collection)
148
- #collection[0].model.last_query = [attributes, collection]
149
- fm_params = fmp_attributes(attributes)
103
+ collection[0].model.last_query = [attributes, collection]
104
+ fm_params = prepare_fmp_attributes(attributes)
150
105
  counter = 0
151
106
  collection.each do |resource|
152
107
  rslt = layout(resource.model).edit(resource.record_id, fm_params, :template=>self.class.fmresultset_template_path)
@@ -182,89 +137,41 @@ module DataMapper
182
137
 
183
138
 
184
139
 
185
- ### ADAPTER HELPER METHODS ###
140
+ ### ADAPTER HELPER METHODS & UTILITIES ###
186
141
 
187
142
  # Create fmp layout object from model object.
188
143
  def layout(model)
189
144
  #Rfm.layout(model.storage_name, options.symbolize_keys) #query.repository.adapter.options.symbolize_keys)
190
145
  model.layout
191
146
  end
192
-
193
- # Convert dm query object to fmp query params (hash)
194
- def fmp_query(input)
195
- #puts "CONDITIONS input #{input.class.name} (#{input})"
196
- if input.class.name[/OrOperation/]
197
- input.operands.collect {|o| fmp_query o}
198
- elsif input.class.name[/AndOperation/]
199
- h = Hash.new
200
- input.operands.each do |k,v|
201
- r = fmp_query(k)
202
- #puts "CONDITIONS operand #{r}"
203
- if r.is_a?(Hash)
204
- h.merge!(r)
205
- else
206
- h=r
207
- break
208
- end
209
- end
210
- h
211
- elsif input.class.name[/NullOperation/] || input.nil?
212
- {}
213
- else
214
- #puts "FMP_QUERY OPERATION #{input.class}"
215
- val = input.loaded_value
216
147
 
217
- if val.to_s != ''
218
-
219
- operation = input.class.name
220
- operator = case
221
- when operation[/EqualTo/]; '='
222
- when operation[/GreaterThan/]; '>'
223
- when operation[/LessThan/]; '<'
224
- when operation[/Like/]; ''
225
- when operation[/Null/]; ''
226
- else ''
227
- end
228
-
229
- val = val._to_fm if val.respond_to? :_to_fm
230
- {input.subject.field.to_s => "#{operator}#{val}"}
231
- else
232
- {}
233
- end
234
- end
235
- end
236
-
237
-
238
- # Convert dm attributes hash to regular hash
239
- # TODO: Should the result be string or symbol keys?
240
- def fmp_attributes(attributes)
241
- #puts "ATTRIBUTES"
242
- y attributes
243
- fm_params = Hash.new
244
- attributes.to_h.each do |k,v|
245
- fm_params[k.field] = v.respond_to?(:_to_fm) ? v._to_fm : v
246
- end
247
- # fm_params = Hash.new
248
- # resource.dirty_attributes.each do |a,v|
249
- # fm_params[a.field] = v.respond_to?(:_to_fm) ? v._to_fm : v
250
- # end
251
- fm_params
252
- end
253
-
254
- # Get fmp options hash from query
255
- def fmp_options(query)
256
- fm_options = {}
257
- fm_options[:skip_records] = query.offset if query.offset
258
- fm_options[:max_records] = query.limit if query.limit
259
- if query.order
260
- fm_options[:sort_field] = query.order.collect do |ord|
261
- ord.target.field
262
- end
263
- fm_options[:sort_order] = query.order.collect do |ord|
264
- ord.operator.to_s + 'end'
265
- end
266
- end
267
- fm_options
148
+ def prepare_fmp_attributes(attributes, *args)
149
+ options = args.last.is_a?(Hash) ? args.pop : {}
150
+ prepend, append = options[:prepend], options[:append]
151
+ fm_attributes = {}
152
+ #puts "PREPARE FMP ATTRIBUTES"
153
+ #y attributes
154
+ attributes_as_fields(attributes).each do |key, val|
155
+ #puts "EACH ATTRIBUTE class #{val.class}"
156
+ #puts "EACH ATTRIBUTE value #{val}"
157
+ new_val = val && [val.is_a?(Fixnum) ? val : val.dup].flatten.inject([]) do |r, v|
158
+ #puts "INJECTING v"
159
+ #puts v
160
+ new_v = v.respond_to?(:_to_fm) ? v._to_fm : v
161
+ #puts "CONVERTING VAL #{new_val} TO STRING"
162
+ new_v = new_v.to_s
163
+ #puts "PREPENDING #{new_v} with '#{prepend}'"
164
+ new_v.prepend prepend if prepend rescue nil
165
+ new_v.append append if append rescue nil
166
+ r << new_v
167
+ end
168
+ #puts "NEW_VAL"
169
+ #puts new_val
170
+ fm_attributes[key] = (new_val && new_val.size < 2) ? new_val[0] : new_val
171
+ end
172
+ #puts "FM_ATTRIBUTES"
173
+ #puts fm_attributes
174
+ fm_attributes
268
175
  end
269
176
 
270
177
  def merge_fmp_response(resource, record)
@@ -281,40 +188,30 @@ module DataMapper
281
188
  # field
282
189
  # end
283
190
 
284
-
285
- protected :fmp_query, :fmp_attributes, :fmp_options, :merge_fmp_response
191
+ protected :merge_fmp_response #,:fmp_options, :prepare_fmp_attributes, :fmp_operator,:fmp_query
192
+
193
+
194
+
195
+ ### LOADED WHEN RESOURCES ARE INCLUDED ###
196
+
197
+ # Class methods extended onto model subclass.
198
+ module ModelMethods
199
+ def layout
200
+ @layout ||= Rfm.layout(storage_name, repository.adapter.options.symbolize_keys)
201
+ end
202
+
203
+ # Not how to do this. Doesn't work anywhere I've tried it:
204
+ #extend Forwardable
205
+ #def_delegators :layout, *layout.class.instance_methods.select {|m| m.to_s[/^[a-z]/]}
206
+ end
207
+
208
+ # Instance methods included in model.
209
+ module ResourceMethods
210
+ def layout
211
+ model.layout
212
+ end
213
+ end
286
214
 
287
215
  end # FilemakerAdapter
288
216
  end # Adapters
289
217
  end # DataMapper
290
-
291
- class Time
292
- def _to_fm
293
- d = strftime('%m/%d/%Y') unless Date.today == Date.parse(self.to_s)
294
- t = strftime('%T')
295
- d ? "#{d} #{t}" : t
296
- end
297
- end # Time
298
-
299
- class DateTime
300
- def _to_fm
301
- d = strftime('%m/%d/%Y')
302
- t =strftime('%T')
303
- "#{d} #{t}"
304
- end
305
- end # Time
306
-
307
- class Timestamp
308
- def _to_fm
309
- d = strftime('%m/%d/%Y')
310
- t =strftime('%T')
311
- "#{d} #{t}"
312
- end
313
- end # Time
314
-
315
- class Date
316
- def _to_fm
317
- strftime('%m/%d/%Y')
318
- end
319
- end # Time
320
-
@@ -0,0 +1,144 @@
1
+ class Time
2
+ def _to_fm
3
+ d = strftime('%m/%d/%Y') #unless Date.today == Date.parse(self.to_s)
4
+ t = strftime('%T')
5
+ d ? "#{d} #{t}" : t
6
+ end
7
+ end # Time
8
+
9
+ class DateTime
10
+ def _to_fm
11
+ d = strftime('%m/%d/%Y')
12
+ t =strftime('%T')
13
+ "#{d} #{t}"
14
+ end
15
+ end # Time
16
+
17
+ class Timestamp
18
+ def _to_fm
19
+ d = strftime('%m/%d/%Y')
20
+ t =strftime('%T')
21
+ "#{d} #{t}"
22
+ end
23
+ end # Time
24
+
25
+ class Date
26
+ def _to_fm
27
+ strftime('%m/%d/%Y')
28
+ end
29
+ end # Time
30
+
31
+
32
+ module DataMapper
33
+ [Adapters, Model, Query, Resource]
34
+
35
+ # All this to tack on class and instance methods to the model/resource.
36
+ module Resource
37
+ class << self
38
+ alias_method :included_orig, :included
39
+ def included(klass)
40
+ included_orig(klass)
41
+ if klass.repository.adapter.to_s[/filemaker/i]
42
+ klass.instance_eval do
43
+ extend repository.adapter.class::ModelMethods
44
+ include repository.adapter.class::ResourceMethods
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+
51
+ module Model
52
+ attr_accessor :last_query
53
+ alias_method :finalize_orig, :finalize
54
+ def finalize(*args)
55
+ property :record_id, Integer, :lazy=>false
56
+ property :mod_id, Integer, :lazy=>false
57
+ finalize_orig
58
+ end
59
+ end
60
+
61
+ class Query
62
+ # Convert dm query conditions to fmp query params (hash)
63
+ def to_fmp_query(input=self.conditions)
64
+ #puts "FMP_QUERY input #{input.class.name}"
65
+ rslt = if input.class.name[/OrOperation/]
66
+ #puts "FMP_QUERY OrOperation #{input.class}"
67
+ input.operands.collect do |o|
68
+ r = to_fmp_query o
69
+ #puts "FMP_QUERY or-operation operand #{r}"
70
+ r
71
+ end
72
+ elsif input.class.name[/AndOperation/]
73
+ #puts "FMP_QUERY AndOperation input class #{input.class}"
74
+ #puts "FMP_QUERY AndOperation input value #{input.inspect}"
75
+ out = {}
76
+ input.operands.each do |k,v|
77
+ #puts "FMP_QUERY and-operation pre-process operand key:val #{k}:#{v}"
78
+ r = to_fmp_query(k).to_hash
79
+ #puts "FMP_QUERY and-operation post-process operand #{r}"
80
+ if r.is_a?(Hash)
81
+ #puts "FMP_QUERY and-operation operand is a hash"
82
+ # Filemaker can't have the same field twice in a single find request,
83
+ # but we can mash the two conditions together in a way that FMP can use.
84
+ out.merge!(r){|k, oldv, newv| "#{oldv} #{newv}"}
85
+ else
86
+ #puts "FMP_QUERY and-operation operand is NOT a hash"
87
+ out = r
88
+ break
89
+ end
90
+ end
91
+ out
92
+ elsif input.class.name[/NullOperation/] || input.nil?
93
+ #puts "FMP_QUERY NullOperation #{input.class}"
94
+ {}
95
+ else
96
+ #puts "FMP_QUERY else input class #{input.class}"
97
+ #puts "FMP_QUERY else input value #{input.inspect}"
98
+ #puts "FMP_QUERY else-options #{self.options.inspect}"
99
+ #prepare_fmp_attributes({input.subject=>input.value}, :prepend=>fmp_operator(input.class.name))
100
+ value = (
101
+ self.options[input.keys[0]] ||
102
+ self.options[input.subject.name] ||
103
+ self.options.find{|o,v| o.respond_to?(:target) && o.target.to_s == input.subject.name.to_s}[1] ||
104
+ input.value
105
+ ) rescue input.value #(puts "ERROR #{$!}"; input.value)
106
+ #puts "FMP_QUERY else-value #{value}"
107
+ repository.adapter.prepare_fmp_attributes({input.subject=>value}, :prepend=>fmp_operator(input.class.name))
108
+ end
109
+ #puts "FMP_QUERY output #{rslt.inspect}"
110
+ rslt
111
+ end # to_fmp_query
112
+
113
+ # Convert operation class to operator string
114
+ def fmp_operator(operation)
115
+ case
116
+ when operation[/GreaterThanOrEqualTo/]; '>='
117
+ when operation[/LessThanOrEqualTo/]; '<='
118
+ when operation[/GreaterThan/]; '>'
119
+ when operation[/LessThan/]; '<'
120
+ when operation[/EqualTo/]; '=='
121
+ when operation[/Like/];
122
+ when operation[/Null/];
123
+ else nil
124
+ end
125
+ end
126
+
127
+ # Get fmp options hash from query
128
+ def fmp_options(query=self)
129
+ fm_options = {}
130
+ fm_options[:skip_records] = query.offset if query.offset
131
+ fm_options[:max_records] = query.limit if query.limit
132
+ if query.order
133
+ fm_options[:sort_field] = query.order.collect do |ord|
134
+ ord.target.field
135
+ end
136
+ fm_options[:sort_order] = query.order.collect do |ord|
137
+ ord.operator.to_s + 'end'
138
+ end
139
+ end
140
+ fm_options
141
+ end
142
+
143
+ end # Query
144
+ end # DataMapper
@@ -1,5 +1,5 @@
1
1
  module DataMapper
2
2
  module FilemakerAdapter
3
- VERSION = "0.0.1"
3
+ VERSION = "0.0.2"
4
4
  end
5
5
  end
@@ -1,11 +1,172 @@
1
1
  require 'spec_helper'
2
2
 
3
- describe DataMapper::Adapters::FilemakerAdapter do
4
- it 'has a version number' do
5
- expect(DataMapper::Adapters::FilemakerAdapter::VERSION).not_to be nil
6
- end
7
-
8
- it 'does something useful' do
9
- expect(true).to eq(true)
10
- end
11
- end
3
+ # Rspec Issues (maybe not bugs, but certainly not expected behavior!).
4
+ #
5
+ # expect/allow_any_instance_of does not work with block form of arguments in '...to receive(:message) do |args|
6
+ # The args will always just be the instance object.
7
+ #
8
+ # and_call_original breaks block form of expect.
9
+ #
10
+
11
+ # Shared Example examples:
12
+ # RSpec.shared_examples "#to_fmp_query" do
13
+ # it 'Receives dm query conditions and returns fmp query' do
14
+ # allow_any_instance_of(Rfm::Layout).to receive(:find).and_return(Rfm::Resultset.allocate)
15
+ # expect_any_instance_of(DataMapper::Query).to receive(:to_fmp_query).at_least(:once).and_call_original
16
+ # query.inspect
17
+ # end
18
+ # end
19
+ #
20
+ # context "with simple .all query" do
21
+ # include_examples "#to_fmp_query" do
22
+ # let(:query){User.all(:id=>1)}
23
+ # end
24
+ # end
25
+
26
+ describe DataMapper do
27
+ before :each do
28
+ DataMapper.setup(:default, 'filemaker://user:pass@hostname.com/DatabaseName')
29
+ class ::User
30
+ include DataMapper::Resource
31
+ property :id, Serial
32
+ property :email, String
33
+ property :username, String
34
+ property :activated_at, DateTime
35
+
36
+ has n, :orders
37
+ end
38
+ class ::Order
39
+ include DataMapper::Resource
40
+ property :id, Serial
41
+ property :total, Decimal
42
+
43
+ belongs_to :user
44
+ end
45
+ DataMapper.finalize
46
+ end
47
+
48
+ describe DataMapper::Adapters::FilemakerAdapter do
49
+
50
+ it 'Has a version number' do
51
+ expect(DataMapper::Adapters::FilemakerAdapter::VERSION).not_to be nil
52
+ end
53
+
54
+ it 'Does something useful' do
55
+ expect(true).to eq(true)
56
+ end
57
+
58
+ it 'Supports model classes' do
59
+ expect(User.ancestors.include?(DataMapper::Adapters::FilemakerAdapter::ResourceMethods)).to eq(true)
60
+ end
61
+
62
+ it 'Acts as dm database adapter for model repository' do
63
+ expect(User.repository.adapter.class).to eq(DataMapper::Adapters::FilemakerAdapter)
64
+ end
65
+
66
+
67
+
68
+ # THESE ARE ALL WRONG. They should process and return real data, instead of returning canned 'safe' responses with rspec mocking.
69
+ # Use .and_call_original to allow expected methods to run their original purpose.
70
+ # See https://github.com/rspec/rspec-mocks.
71
+ describe '#read' do
72
+ it 'Receives dm query object with conditions' do
73
+ #expect_any_instance_of(DataMapper::Adapters::FilemakerAdapter).to receive(:read).and_return(Rfm::Resultset.allocate)
74
+ expect(User.repository.adapter).to receive(:read) do |query|
75
+ expect(query.class).to eq(DataMapper::Query)
76
+ expect(query.conditions.first.subject.field).to eq('id')
77
+ expect(query.conditions.first.value).to eq(1)
78
+ end.and_return(Rfm::Resultset.allocate)
79
+ User.all(:id=>1).inspect
80
+ end
81
+ end
82
+
83
+ end # datamapper-adapters-filemaker
84
+
85
+ describe DataMapper::Query do
86
+
87
+ describe '#to_fmp_query' do
88
+ # I'm passing in the example here, so we can use the metadata to construct
89
+ # streamlined tests for a variety of query inputs to the to_fmp_query method.
90
+ before(:each) do |example|
91
+
92
+ # Not really needed yet, but helpful if calls go into Rfm.
93
+ allow_any_instance_of(Rfm::Layout).to receive(:find).and_return(Rfm::Resultset.allocate)
94
+ #expect_any_instance_of(DataMapper::Query).to receive(:to_fmp_query).at_least(:once).and_call_original
95
+
96
+ # Capture the query object passed to the adapter#read method
97
+ expect(DataMapper.repository.adapter).to receive(:read) do |query|
98
+ #puts "QUERY #{query}"
99
+ #puts "Self within before/expect block #{self}"
100
+ @query = query
101
+ #puts @query.conditions.to_yaml
102
+ # TODO: See rspec mocks docs for how to pass control to the original method and get a result back right here.
103
+ # hint - it does something like this: @original_method = adapter.method(:to_fmp_query); @original_method.call(query)
104
+ []
105
+ end
106
+
107
+ # Possible helpful meta info for each example.
108
+ #puts "EXAMPLE INSTANCE VARS #{example.instance_variables.inspect}"
109
+ #
110
+ # Uses the desription of the example as the user query code,
111
+ # and uses the block of the example as the expected to_fmp_query result.
112
+ # Also replaces the description with more informative info, including the expected result.
113
+ #
114
+ @name = example.description.dup
115
+ @block = example.instance_variable_get(:@example_block)
116
+ @expected_result = @block.call
117
+ example.description.replace "#{@name} should return #{@expected_result}"
118
+ # Runs the query to get the query object.
119
+ eval(@name.to_s).inspect
120
+ @fmp_query = @query.to_fmp_query
121
+ # All of the above, so we can do this.
122
+ expect(@fmp_query).to eq(@expected_result)
123
+ end
124
+
125
+ context "Simple .all" do
126
+ it('User.all'){ {} }
127
+ end
128
+
129
+ context "Simple .first plus less-than comparison on time" do
130
+ it('User.first(:id=>1)') { {'id' => '==1'} }
131
+ it('User.first(:activated_at.lt=>"1/1/2015 00:00:00")') { {"activated_at"=>"<01/01/2015 00:00:00"} }
132
+ end
133
+
134
+ context "Compound OR with simple comparison on integer" do
135
+ it('(User.all(:id=>1) | User.all(:id=>2))') { [{"id"=>"==1"}, {"id"=>"==2"}] }
136
+ end
137
+
138
+ context "Simple .all containing duplicate keys with differing operators" do
139
+ it('User.all(:id.gt=>1, :id.lt=>5)') { {"id"=>">1 <5"} }
140
+ end
141
+
142
+ context "Compound AND operation" do
143
+ it('(User.all(:id=>1) & User.all(:email=>"some_email"))') { {"id"=>"==1", "email"=>"==some_email"} }
144
+ end
145
+
146
+ context "Simple implied .in operation" do
147
+ it('User.all(:id=>[1, 3, 5, 9])') { {"id"=>["1", "3", "5", "9"]} }
148
+ end
149
+
150
+ context "Compound OR with dup keys with differing operators and .gte comparison" do
151
+ it("(User.all(:username=>'uname', :activated_at.gt=>Time.parse('1970-01-01 00:00:00')) | User.all(:email.like=>'uname', :activated_at.gte=>Time.parse('1970-03-01 00:00:00')))"){
152
+ [{"username"=>"==uname", "activated_at"=>">01/01/1970 00:00:00"}, {"email"=>"uname", "activated_at"=>">=03/01/1970 00:00:00"}]
153
+ }
154
+ end
155
+
156
+ context "Compound AND with dup keys for a date range with .gte and .lte comparison" do
157
+ it('User.all(:activated_at.gte=>Time.parse("2015-01-01 00:00:00")) & User.all(:activated_at.lte=>Time.parse("2015-03-01 00:00:00"))') {
158
+ {"activated_at"=>">=01/01/2015 00:00:00 <=03/01/2015 00:00:00"}
159
+ }
160
+ end
161
+ # TODO: test field-name-translation
162
+ # TODO: test all other operator possibilities
163
+
164
+ # This tests nested associational query, which dm-filemaker-adapter cannot yet do.
165
+ #it('User.all(:email=>"something@dot.com", :orders=>{:total.gt=>10.0})') {{}}
166
+
167
+
168
+ end #to_fmp_query
169
+
170
+ end # datamapper-query
171
+
172
+ end # datamapper
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dm-filemaker-adapter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Richardson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-02-16 00:00:00.000000000 Z
11
+ date: 2015-03-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: data_mapper
@@ -96,6 +96,7 @@ files:
96
96
  - dm-filemaker-adapter.gemspec
97
97
  - lib/dm-filemaker-adapter.rb
98
98
  - lib/dm-filemaker-adapter/adapter.rb
99
+ - lib/dm-filemaker-adapter/core_patches.rb
99
100
  - lib/dm-filemaker-adapter/dm-fmresultset.yml
100
101
  - lib/dm-filemaker-adapter/version.rb
101
102
  - spec/dm-filemaker-adapter/adapter_spec.rb