dm-filemaker-adapter 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml 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