lardawge-rfm 1.3.1 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,84 @@
1
+ # The classes in this module are used internally by RFM and are not intended for outside
2
+ # use.
3
+ #
4
+ # Author:: Geoff Coffey (mailto:gwcoffey@gmail.com)
5
+ # Copyright:: Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumri
6
+ # License:: See MIT-LICENSE for details
7
+
8
+ module Rfm
9
+ module Factory # :nodoc: all
10
+ class DbFactory < Rfm::Utility::CaseInsensitiveHash
11
+
12
+ def initialize(server)
13
+ @server = server
14
+ @loaded = false
15
+ end
16
+
17
+ def [](dbname)
18
+ super or (self[dbname] = Rfm::Database.new(dbname, @server))
19
+ end
20
+
21
+ def all
22
+ if !@loaded
23
+ Rfm::Result::ResultSet.new(@server, @server.do_action(@server.state[:account_name], @server.state[:password], '-dbnames', {}).body).each {|record|
24
+ name = record['DATABASE_NAME']
25
+ self[name] = Rfm::Database.new(name, @server) if self[name] == nil
26
+ }
27
+ @loaded = true
28
+ end
29
+ self.values
30
+ end
31
+
32
+ end
33
+
34
+ class LayoutFactory < Rfm::Utility::CaseInsensitiveHash
35
+
36
+ def initialize(server, database)
37
+ @server = server
38
+ @database = database
39
+ @loaded = false
40
+ end
41
+
42
+ def [](layout_name)
43
+ super or (self[layout_name] = Rfm::Layout.new(layout_name, @database))
44
+ end
45
+
46
+ def all
47
+ if !@loaded
48
+ Rfm::Result::ResultSet.new(@server, @server.do_action(@server.state[:account_name], @server.state[:password], '-layoutnames', {"-db" => @database.name}).body).each {|record|
49
+ name = record['LAYOUT_NAME']
50
+ self[name] = Rfm::Layout.new(name, @database) if self[name] == nil
51
+ }
52
+ @loaded = true
53
+ end
54
+ self.values
55
+ end
56
+
57
+ end
58
+
59
+ class ScriptFactory < Rfm::Utility::CaseInsensitiveHash
60
+
61
+ def initialize(server, database)
62
+ @server = server
63
+ @database = database
64
+ @loaded = false
65
+ end
66
+
67
+ def [](script_name)
68
+ super or (self[script_name] = Rfm::Script.new(script_name, @database))
69
+ end
70
+
71
+ def all
72
+ if !@loaded
73
+ Rfm::Result::ResultSet.new(@server, @server.do_action(@server.state[:account_name], @server.state[:password], '-scriptnames', {"-db" => @database.name}).body).each {|record|
74
+ name = record['SCRIPT_NAME']
75
+ self[name] = Rfm::Script.new(name, @database) if self[name] == nil
76
+ }
77
+ @loaded = true
78
+ end
79
+ self.values
80
+ end
81
+
82
+ end
83
+ end
84
+ end
data/lib/rfm/result.rb ADDED
@@ -0,0 +1,446 @@
1
+ # This module includes classes that represent FileMaker data. When you communicate with FileMaker
2
+ # using, ie, the Layout object, you typically get back ResultSet objects. These contain Records,
3
+ # which in turn contain Fields, Portals, and arrays of data.
4
+ #
5
+ # Author:: Geoff Coffey (mailto:gwcoffey@gmail.com)
6
+ # Copyright:: Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumri
7
+ # License:: See MIT-LICENSE for details
8
+ require 'nokogiri'
9
+ require 'bigdecimal'
10
+ require 'date'
11
+
12
+ module Rfm
13
+ module Result
14
+
15
+ # The ResultSet object represents a set of records in FileMaker. It is, in every way, a real Ruby
16
+ # Array, so everything you expect to be able to do with an Array can be done with a ResultSet as well.
17
+ # In this case, the elements in the array are Record objects.
18
+ #
19
+ # Here's a typical example, displaying the results of a Find:
20
+ #
21
+ # myServer = Rfm::Server.new(...)
22
+ # results = myServer["Customers"]["Details"].find("First Name" => "Bill")
23
+ # results.each {|record|
24
+ # puts record["First Name"]
25
+ # puts record["Last Name"]
26
+ # puts record["Email Address"]
27
+ # }
28
+ #
29
+ # =Attributes
30
+ #
31
+ # The ResultSet object has several useful attributes:
32
+ #
33
+ # * *server* is the server object this ResultSet came from
34
+ #
35
+ # * *fields* is a hash with field names for keys and Field objects for values; it provides
36
+ # metadata about the fields in the ResultSet
37
+ #
38
+ # * *portals* is a hash with table occurrence names for keys and arrays of Field objects for values;
39
+ # it provides metadata about the portals in the ResultSet and the Fields on those portals
40
+
41
+ class ResultSet < Array
42
+
43
+ # Initializes a new ResultSet object. You will probably never do this your self (instead, use the Layout
44
+ # object to get various ResultSet obejects).
45
+ #
46
+ # If you feel so inclined, though, pass a Server object, and some +fmpxmlresult+ compliant XML in a String.
47
+ #
48
+ # =Attributes
49
+ #
50
+ # The ResultSet object includes several useful attributes:
51
+ #
52
+ # * *fields* is a hash (with field names for keys and Field objects for values). It includes an entry for
53
+ # every field in the ResultSet. Note: You don't use Field objects to access _data_. If you're after
54
+ # data, get a Record object (ResultSet is an array of records). Field objects tell you about the fields
55
+ # (their type, repetitions, and so forth) in case you find that information useful programmatically.
56
+ #
57
+ # Note: keys in the +fields+ hash are downcased for convenience (and [] automatically downcases on
58
+ # lookup, so it should be seamless). But if you +each+ a field hash and need to know a field's real
59
+ # name, with correct case, do +myField.name+ instead of relying on the key in the hash.
60
+ #
61
+ # * *portals* is a hash (with table occurrence names for keys and Field objects for values). If your
62
+ # layout contains portals, you can find out what fields they contain here. Again, if it's the data you're
63
+ # after, you want to look at the Record object.
64
+ def initialize(server, fmresultset, layout = nil)
65
+ @server = server
66
+ @resultset = nil
67
+ @layout = layout
68
+ @fields = Rfm::Utility::CaseInsensitiveHash.new
69
+ @portals = Rfm::Utility::CaseInsensitiveHash.new
70
+ @date_format = nil
71
+ @time_format = nil
72
+ @timestamp_format = nil
73
+ @total_count = nil
74
+ @foundset_count = nil
75
+
76
+ doc = Nokogiri.XML(fmresultset)
77
+
78
+ #seperate content for less searching
79
+ datasource = doc.search('datasource')
80
+ resultset = doc.search('resultset')
81
+ metadata = doc.search('metadata')
82
+
83
+ # check for errors
84
+ error = doc.search('error').attribute('code').value.to_i
85
+ if error != 0 && (error != 401 || @server.state[:raise_on_401])
86
+ raise Rfm::Error::FileMakerError.getError(error)
87
+ end
88
+
89
+ # ascertain date and time formats
90
+ @date_format = convertFormatString(datasource.attribute('date-format').value)
91
+ @time_format = convertFormatString(datasource.attribute('time-format').value)
92
+ @timestamp_format = convertFormatString(datasource.attribute('timestamp-format').value)
93
+
94
+ # retrieve count
95
+ @foundset_count = resultset.attribute('count').value.to_i
96
+ @total_count = datasource.attribute('total-count').value.to_i
97
+
98
+ # process field metadata
99
+ metadata.search('field-definition').each do |field|
100
+ @fields[field['name']] = Field.new(self, field)
101
+ end
102
+ @fields.freeze
103
+
104
+ # process relatedset metadata
105
+ metadata.search('relatedset-definition').each do |relatedset|
106
+ table = relatedset.attribute('table').value
107
+ fields = {}
108
+ relatedset.search('field-definition').each do |field|
109
+ name = field.attribute('name').value.sub(Regexp.new(table + '::'), '')
110
+ fields[name] = Field.new(self, field)
111
+ end
112
+ @portals[table] = fields
113
+ end
114
+ @portals.freeze
115
+
116
+ # build record rows
117
+ resultset.search('record').each do |record|
118
+ self << Record.new(record, self, @fields, @layout)
119
+ end
120
+ end
121
+
122
+ attr_reader :server, :fields, :portals, :date_format, :time_format, :timestamp_format, :total_count, :foundset_count, :layout
123
+
124
+ private
125
+
126
+ def convertFormatString(fm_format)
127
+ fm_format.gsub('MM', '%m').gsub('dd', '%d').gsub('yyyy', '%Y').gsub('HH', '%H').gsub('mm', '%M').gsub('ss', '%S')
128
+ end
129
+
130
+ end
131
+
132
+ # The Record object represents a single FileMaker record. You typically get them from ResultSet objects.
133
+ # For example, you might use a Layout object to find some records:
134
+ #
135
+ # results = myLayout.find({"First Name" => "Bill"})
136
+ #
137
+ # The +results+ variable in this example now contains a ResultSet object. ResultSets are really just arrays of
138
+ # Record objects (with a little extra added in). So you can get a record object just like you would access any
139
+ # typical array element:
140
+ #
141
+ # first_record = results[0]
142
+ #
143
+ # You can find out how many record were returned:
144
+ #
145
+ # record_count = results.size
146
+ #
147
+ # And you can of course iterate:
148
+ #
149
+ # results.each (|record|
150
+ # // you can work with the record here
151
+ # )
152
+ #
153
+ # =Accessing Field Data
154
+ #
155
+ # You can access field data in the Record object in two ways. Typically, you simply treat Record like a hash
156
+ # (because it _is_ a hash...I love OOP). Keys are field names:
157
+ #
158
+ # first = myRecord["First Name"]
159
+ # last = myRecord["Last Name"]
160
+ #
161
+ # If your field naming conventions mean that your field names are also valid Ruby symbol named (ie: they contain only
162
+ # letters, numbers, and underscores) then you can treat them like attributes of the record. For example, if your fields
163
+ # are called "first_name" and "last_name" you can do this:
164
+ #
165
+ # first = myRecord.first_name
166
+ # last = myRecord.last_name
167
+ #
168
+ # Note: This shortcut will fail (in a rather mysterious way) if your field name happens to match any real attribute
169
+ # name of a Record object. For instance, you may have a field called "server". If you try this:
170
+ #
171
+ # server_name = myRecord.server
172
+ #
173
+ # you'll actually set +server_name+ to the Rfm::Server object this Record came from. This won't fail until you try
174
+ # to treat it as a String somewhere else in your code. It is also possible a future version of Rfm will include
175
+ # new attributes on the Record class which may clash with your field names. This will cause perfectly valid code
176
+ # today to fail later when you upgrade. If you can't stomach this kind of insanity, stick with the hash-like
177
+ # method of field access, which has none of these limitations. Also note that the +myRecord[]+ method is probably
178
+ # somewhat faster since it doesn't go through +method_missing+.
179
+ #
180
+ # =Accessing Repeating Fields
181
+ #
182
+ # If you have a repeating field, RFM simply returns an array:
183
+ #
184
+ # val1 = myRecord["Price"][0]
185
+ # val2 = myRecord["Price"][1]
186
+ #
187
+ # In the above example, the Price field is a repeating field. The code puts the first repetition in a variable called
188
+ # +val1+ and the second in a variable called +val2+.
189
+ #
190
+ # =Accessing Portals
191
+ #
192
+ # If the ResultSet includes portals (because the layout it comes from has portals on it) you can access them
193
+ # using the Record::portals attribute. It is a hash with table occurrence names for keys, and arrays of Record
194
+ # objects for values. In other words, you can do this:
195
+ #
196
+ # myRecord.portals["Orders"].each {|record|
197
+ # puts record["Order Number"]
198
+ # }
199
+ #
200
+ # This code iterates through the rows of the _Orders_ portal.
201
+ #
202
+ # =Field Types and Ruby Types
203
+ #
204
+ # RFM automatically converts data from FileMaker into a Ruby object with the most reasonable type possible. The
205
+ # type are mapped thusly:
206
+ #
207
+ # * *Text* fields are converted to Ruby String objects
208
+ #
209
+ # * *Number* fields are converted to Ruby BigDecimal objects (the basic Ruby numeric types have
210
+ # much less precision and range than FileMaker number fields)
211
+ #
212
+ # * *Date* fields are converted to Ruby Date objects
213
+ #
214
+ # * *Time* fields are converted to Ruby DateTime objects (you can ignore the date component)
215
+ #
216
+ # * *Timestamp* fields are converted to Ruby DateTime objects
217
+ #
218
+ # * *Container* fields are converted to Ruby URI objects
219
+ #
220
+ # =Attributes
221
+ #
222
+ # In addition to +portals+, the Record object has these useful attributes:
223
+ #
224
+ # * *record_id* is FileMaker's internal identifier for this record (_not_ any ID field you might have
225
+ # in your table); you need a +record_id+ to edit or delete a record
226
+ #
227
+ # * *mod_id* is the modification identifier for the record; whenever a record is modified, its +mod_id+
228
+ # changes so you can tell if the Record object you're looking at is up-to-date as compared to another
229
+ # copy of the same record
230
+ class Record < Rfm::Utility::CaseInsensitiveHash
231
+
232
+ # Initializes a Record object. You really really never need to do this yourself. Instead, get your records
233
+ # from a ResultSet object.
234
+ def initialize(row_element, resultset, fields, layout, portal=nil)
235
+ @record_id = row_element['record-id']
236
+ @mod_id = row_element['mod-id']
237
+ @mods = {}
238
+ @resultset = resultset
239
+ @layout = layout
240
+
241
+ @loaded = false
242
+ related_sets = row_element.search('relatedset')
243
+
244
+ row_element.search('field').each do |field|
245
+ field_name = field['name']
246
+ field_name.sub!(Regexp.new(portal + '::'), '') if portal
247
+ datum = []
248
+ field.search('data').each do |x|
249
+ datum.push(fields[field_name].coerce(x.inner_text))
250
+ end
251
+ if datum.length == 1
252
+ self[field_name] = datum[0]
253
+ elsif datum.length == 0
254
+ self[field_name] = nil
255
+ else
256
+ self[field_name] = datum
257
+ end
258
+ end
259
+
260
+ unless related_sets.empty?
261
+ @portals = Rfm::Utility::CaseInsensitiveHash.new
262
+ related_sets.each do |relatedset|
263
+ table = relatedset['table']
264
+ records = []
265
+ relatedset.search('record').each do |record|
266
+ records << Record.new(record, @resultset, @resultset.portals[table], @layout, table)
267
+ end
268
+ @portals[table] = records
269
+ end
270
+ end
271
+ @loaded = true
272
+ end
273
+
274
+ attr_reader :record_id, :mod_id, :portals
275
+
276
+ # Saves local changes to the Record object back to Filemaker. For example:
277
+ #
278
+ # myLayout.find({"First Name" => "Bill"}).each(|record|
279
+ # record["First Name"] = "Steve"
280
+ # record.save
281
+ # )
282
+ #
283
+ # This code finds every record with _Bill_ in the First Name field, then changes the first name to
284
+ # Steve.
285
+ #
286
+ # Note: This method is smart enough to not bother saving if nothing has changed. So there's no need
287
+ # to optimize on your end. Just save, and if you've changed the record it will be saved. If not, no
288
+ # server hit is incurred.
289
+ def save
290
+ self.merge(@layout.edit(self.record_id, @mods)[0]) if @mods.size > 0
291
+ @mods.clear
292
+ end
293
+
294
+ # Like Record::save, except it fails (and raises an error) if the underlying record in FileMaker was
295
+ # modified after the record was fetched but before it was saved. In other words, prevents you from
296
+ # accidentally overwriting changes someone else made to the record.
297
+ def save_if_not_modified
298
+ self.merge(@layout.edit(@record_id, @mods, {:modification_id => @mod_id})[0]) if @mods.size > 0
299
+ @mods.clear
300
+ end
301
+
302
+ # Gets the value of a field from the record. For example:
303
+ #
304
+ # first = myRecord["First Name"]
305
+ # last = myRecord["Last Name"]
306
+ #
307
+ # This sample puts the first and last name from the record into Ruby variables.
308
+ #
309
+ # You can also update a field:
310
+ #
311
+ # myRecord["First Name"] = "Sophia"
312
+ #
313
+ # When you do, the change is noted, but *the data is not updated in FileMaker*. You must call
314
+ # Record::save or Record::save_if_not_modified to actually save the data.
315
+ def []=(pname, value)
316
+ return super unless @loaded # keeps us from getting mods during initialization
317
+ name = pname
318
+ if self[name] != nil
319
+ @mods[name] = val
320
+ else
321
+ raise Rfm::Error::ParameterError.new("You attempted to modify a field called '#{name}' on the Rfm::Record object, but that field does not exist.")
322
+ end
323
+ end
324
+
325
+ def method_missing (symbol, *attrs)
326
+ # check for simple getter
327
+ return self[symbol.to_s] if self.include?(symbol.to_s)
328
+
329
+ # check for setter
330
+ symbol_name = symbol.to_s
331
+ if symbol_name[-1..-1] == '=' && self.has_key?(symbol_name[0..-2])
332
+ return @mods[symbol_name[0..-2]] = attrs[0]
333
+ end
334
+ super
335
+ end
336
+
337
+ def respond_to?(symbol, include_private = false)
338
+ return true if self[symbol.to_s] != nil
339
+ super
340
+ end
341
+ end
342
+
343
+ # The Field object represents a single FileMaker field. It *does not hold the data* in the field. Instead,
344
+ # it serves as a source of metadata about the field. For example, if you're script is trying to be highly
345
+ # dynamic about its field access, it may need to determine the data type of a field at run time. Here's
346
+ # how:
347
+ #
348
+ # field_name = "Some Field Name"
349
+ # case myRecord.fields[field_name].result
350
+ # when "text"
351
+ # # it is a text field, so handle appropriately
352
+ # when "number"
353
+ # # it is a number field, so handle appropriately
354
+ # end
355
+ #
356
+ # =Attributes
357
+ #
358
+ # The Field object has the following attributes useful attributes:
359
+ #
360
+ # * *name* is the name of the field
361
+ #
362
+ # * *result* is the data type of the field; possible values include:
363
+ # * text
364
+ # * number
365
+ # * date
366
+ # * time
367
+ # * timestamp
368
+ # * container
369
+ #
370
+ # * *type* any of these:
371
+ # * normal (a normal data field)
372
+ # * calculation
373
+ # * summary
374
+ #
375
+ # * *max_repeats* is the number of repetitions (1 for a normal field, more for a repeating field)
376
+ #
377
+ # * *global* is +true+ is this is a global field, *false* otherwise
378
+ #
379
+ # Note: Field types match FileMaker's own values, but the terminology differs. The +result+ attribute
380
+ # tells you the data type of the field, regardless of whether it is a calculation, summary, or normal
381
+ # field. So a calculation field whose result type is _timestamp_ would have these attributes:
382
+ #
383
+ # * result: timestamp
384
+ # * type: calculation
385
+ #
386
+ # * *control& is a FieldControl object representing the sytle and value list information associated
387
+ # with this field on the layout.
388
+ #
389
+ # Note: Since a field can sometimes appear on a layout more than once, +control+ may be an Array.
390
+ # If you don't know ahead of time, you'll need to deal with this. One easy way is:
391
+ #
392
+ # controls = [myField.control].flatten
393
+ # controls.each {|control|
394
+ # # do something with the control here
395
+ # }
396
+ #
397
+ # The code above makes sure the control is always an array. Typically, though, you'll know up front
398
+ # if the control is an array or not, and you can code accordingly.
399
+
400
+ class Field
401
+
402
+ # Initializes a field object. You'll never need to do this. Instead, get your Field objects from
403
+ # ResultSet::fields
404
+ def initialize(result_set, field)
405
+ @result_set = result_set
406
+ @name = field['name']
407
+ @result = field['result']
408
+ @type = field['type']
409
+ @max_repeats = field['max-repeats']
410
+ @global = field['global']
411
+
412
+ @loaded = false
413
+ end
414
+
415
+ attr_reader :name, :result, :type, :max_repeats, :global
416
+
417
+ def control
418
+ @result_set.layout.field_controls[@name]
419
+ end
420
+
421
+ # Coerces the text value from an +fmresultset+ document into proper Ruby types based on the
422
+ # type of the field. You'll never need to do this: Rfm does it automatically for you when you
423
+ # access field data through the Record object.
424
+ def coerce(value)
425
+ return nil if (value == nil || value == '') && @result != "text"
426
+ case @result
427
+ when "text"
428
+ return value
429
+ when "number"
430
+ return BigDecimal.new(value)
431
+ when "date"
432
+ return Date.strptime(value, @result_set.date_format)
433
+ when "time"
434
+ return DateTime.strptime("1/1/-4712 " + value, "%m/%d/%Y #{@result_set.time_format}")
435
+ when "timestamp"
436
+ return DateTime.strptime(value, @result_set.timestamp_format)
437
+ when "container"
438
+ return URI.parse("#{@result_set.server.scheme}://#{@result_set.server.host_name}:#{@result_set.server.port}#{value}")
439
+ else
440
+ return nil
441
+ end
442
+ end
443
+ end
444
+
445
+ end
446
+ end
@@ -0,0 +1,12 @@
1
+ module Rfm
2
+ module Utility # :nodoc: all
3
+ class CaseInsensitiveHash < Hash
4
+ def []=(key, value)
5
+ super(key.downcase, value)
6
+ end
7
+ def [](key)
8
+ super(key.downcase)
9
+ end
10
+ end
11
+ end
12
+ end
data/lib/rfm.rb CHANGED
@@ -222,11 +222,20 @@
222
222
  # config.frameworks -= [ :active_record ]
223
223
  #
224
224
  # Now Rails will no longer insist on a SQL database.
225
-
226
- $: << File.expand_path(File.dirname(__FILE__))
225
+ path = File.expand_path(File.dirname(__FILE__))
226
+ $:.unshift(path) unless $:.include?(path)
227
227
 
228
- require 'rfm_command'
229
- require 'rfm_util'
230
- require 'rfm_result'
231
- require 'rfm_factory'
232
- require 'rfm_error'
228
+ module Rfm
229
+
230
+ autoload :Error, "rfm/error"
231
+ autoload :Factory, "rfm/factory"
232
+ autoload :Result, "rfm/result"
233
+ autoload :Utility, "rfm/utility"
234
+
235
+ autoload :Database, 'rfm/commands/database'
236
+ autoload :FieldControl, 'rfm/commands/field_control'
237
+ autoload :Layout, 'rfm/commands/layout'
238
+ autoload :Script, 'rfm/commands/script'
239
+ autoload :Server, 'rfm/commands/server'
240
+
241
+ end
@@ -1,5 +1,6 @@
1
1
  require 'test/unit'
2
- require '../lib/rfm'
2
+ require 'rubygems'
3
+ require 'rfm'
3
4
 
4
5
  # Test cases for testing the FileMakerError classes
5
6
  #
@@ -12,7 +13,7 @@ class TC_TestErrors < Test::Unit::TestCase
12
13
  begin
13
14
  raise Rfm::Error::FileMakerError.getError(0)
14
15
  rescue Rfm::Error::SystemError => ex
15
- assert_equal(ex.message, 'SystemError occurred.')
16
+ assert_equal(ex.message, 'SystemError occurred. (FileMaker Error #0)')
16
17
  assert_equal(ex.code, 0)
17
18
  end
18
19
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lardawge-rfm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geoff Coffey
@@ -12,7 +12,7 @@ autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
14
 
15
- date: 2009-11-04 00:00:00 -05:00
15
+ date: 2009-12-19 00:00:00 -05:00
16
16
  default_executable:
17
17
  dependencies:
18
18
  - !ruby/object:Gem::Dependency
@@ -21,9 +21,9 @@ dependencies:
21
21
  version_requirement:
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "="
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 1.3.3
26
+ version: "0"
27
27
  version:
28
28
  description: Rfm brings your FileMaker data to Ruby with elegance and speed. Now your Ruby scripts and Rails applications can talk directly to your FileMaker server with a syntax that just feels right.
29
29
  email: http://groups.google.com/group/rfmcommunity
@@ -36,11 +36,15 @@ extra_rdoc_files:
36
36
  - README.rdoc
37
37
  files:
38
38
  - lib/rfm.rb
39
- - lib/rfm_command.rb
40
- - lib/rfm_error.rb
41
- - lib/rfm_factory.rb
42
- - lib/rfm_result.rb
43
- - lib/rfm_util.rb
39
+ - lib/rfm/commands/database.rb
40
+ - lib/rfm/commands/field_control.rb
41
+ - lib/rfm/commands/layout.rb
42
+ - lib/rfm/commands/script.rb
43
+ - lib/rfm/commands/server.rb
44
+ - lib/rfm/error.rb
45
+ - lib/rfm/factory.rb
46
+ - lib/rfm/result.rb
47
+ - lib/rfm/utility.rb
44
48
  - LICENSE
45
49
  - README.rdoc
46
50
  has_rdoc: true
@@ -74,5 +78,4 @@ signing_key:
74
78
  specification_version: 3
75
79
  summary: FileMaker to Ruby adapter
76
80
  test_files:
77
- - test/rfm_test_errors.rb
78
- - test/rfm_tester.rb
81
+ - test/errors_test.rb