ginjo-rfm 1.4.2

Sign up to get free protection for your applications and to get access to all the features.
data/lib/rfm/error.rb ADDED
@@ -0,0 +1,186 @@
1
+ module Rfm
2
+
3
+ # Error is the base for the error hierarchy representing errors returned by Filemaker.
4
+ #
5
+ # One could raise a FileMakerError by doing:
6
+ # raise Rfm::Error.getError(102)
7
+ #
8
+ # It also takes an optional argument to give a more discriptive error message:
9
+ # err = Rfm::Error.getError(102, 'add description with more detail here')
10
+ #
11
+ # The above code would return a FieldMissing instance. Your could use this instance to raise that appropriate
12
+ # exception:
13
+ #
14
+ # raise err
15
+ #
16
+ # You could access the specific error code by accessing:
17
+ #
18
+ # err.code
19
+ module Error
20
+
21
+ class RfmError < StandardError #:nodoc:
22
+ attr_reader :code
23
+
24
+ def initialize(code, message=nil)
25
+ @code = code
26
+ super(message)
27
+ end
28
+ end
29
+
30
+ class UnknownError < RfmError
31
+ end
32
+
33
+ class SystemError < RfmError
34
+ end
35
+
36
+ class MissingError < RfmError
37
+ end
38
+
39
+ class RecordMissingError < MissingError #:nodoc:
40
+ end
41
+
42
+ class FieldMissingError < MissingError #:nodoc:
43
+ end
44
+
45
+ class ScriptMissingError < MissingError #:nodoc:
46
+ end
47
+
48
+ class LayoutMissingError < MissingError #:nodoc:
49
+ end
50
+
51
+ class TableMissingError < MissingError #:nodoc:
52
+ end
53
+
54
+ class SecurityError < RfmError #:nodoc:
55
+ end
56
+
57
+ class RecordAccessDeniedError < SecurityError #:nodoc:
58
+ end
59
+
60
+ class FieldCannotBeModifiedError < SecurityError #:nodoc:
61
+ end
62
+
63
+ class FieldAccessIsDeniedError < SecurityError #:nodoc:
64
+ end
65
+
66
+ class ConcurrencyError < RfmError #:nodoc:
67
+ end
68
+
69
+ class RecordInUseError < ConcurrencyError #:nodoc:
70
+ end
71
+
72
+ class TableInUseError < ConcurrencyError #:nodoc:
73
+ end
74
+
75
+ class RecordModIdDoesNotMatchError < ConcurrencyError #:nodoc:
76
+ end
77
+
78
+ class GeneralError < RfmError #:nodoc:
79
+ end
80
+
81
+ class NoRecordsFoundError < GeneralError #:nodoc:
82
+ end
83
+
84
+ class ValidationError < RfmError #:nodoc:
85
+ end
86
+
87
+ class DateValidationError < ValidationError #:nodoc:
88
+ end
89
+
90
+ class TimeValidationError < ValidationError #:nodoc:
91
+ end
92
+
93
+ class NumberValidationError < ValidationError #:nodoc:
94
+ end
95
+
96
+ class RangeValidationError < ValidationError #:nodoc:
97
+ end
98
+
99
+ class UniqueValidationError < ValidationError #:nodoc:
100
+ end
101
+
102
+ class ExistingValidationError < ValidationError #:nodoc:
103
+ end
104
+
105
+ class ValueListValidationError < ValidationError #:nodoc:
106
+ end
107
+
108
+ class ValidationCalculationError < ValidationError #:nodoc:
109
+ end
110
+
111
+ class InvalidFindModeValueError < ValidationError #:nodoc:
112
+ end
113
+
114
+ class MaximumCharactersValidationError < ValidationError #:nodoc:
115
+ end
116
+
117
+ class FileError < RfmError #:nodoc:
118
+ end
119
+
120
+ class UnableToOpenFileError < FileError #:nodoc:
121
+ end
122
+
123
+ extend self
124
+ # This method returns the appropriate FileMaker object depending on the error code passed to it. It
125
+ # also accepts an optional message.
126
+ def getError(code, message=nil)
127
+ klass = find_by_code(code)
128
+ message = build_message(klass, code, message)
129
+ error = klass.new(code, message)
130
+ error
131
+ end
132
+
133
+ def build_message(klass, code, message=nil) #:nodoc:
134
+ msg = ": #{message}"
135
+ msg << " " unless message.nil?
136
+ msg << "(FileMaker Error ##{code})"
137
+
138
+ "#{klass.to_s.gsub(/Rfm::Error::/, '')} occurred#{msg}"
139
+ end
140
+
141
+ def find_by_code(code) #:nodoc:
142
+ case code
143
+ when 0..99 then SystemError
144
+ when 100..199
145
+ if code == 101; RecordMissingError
146
+ elsif code == 102; FieldMissingError
147
+ elsif code == 104; ScriptMissingError
148
+ elsif code == 105; LayoutMissingError
149
+ elsif code == 106; TableMissingError
150
+ else; MissingError; end
151
+ when 203..299
152
+ if code == 200; RecordAccessDeniedError
153
+ elsif code == 201; FieldCannotBeModifiedError
154
+ elsif code == 202; FieldAccessIsDeniedError
155
+ else; SecurityError; end
156
+ when 300..399
157
+ if code == 301; RecordInUseError
158
+ elsif code == 302; TableInUseError
159
+ elsif code == 306; RecordModIdDoesNotMatchError
160
+ else; ConcurrencyError; end
161
+ when 400..499
162
+ if code == 401; NoRecordsFoundError
163
+ else; GeneralError; end
164
+ when 500..599
165
+ if code == 500; DateValidationError
166
+ elsif code == 501; TimeValidationError
167
+ elsif code == 502; NumberValidationError
168
+ elsif code == 503; RangeValidationError
169
+ elsif code == 504; UniqueValidationError
170
+ elsif code == 505; ExistingValidationError
171
+ elsif code == 506; ValueListValidationError
172
+ elsif code == 507; ValidationCalculationError
173
+ elsif code == 508; InvalidFindModeValueError
174
+ elsif code == 511; MaximumCharactersValidationError
175
+ else; ValidationError
176
+ end
177
+ when 800..899
178
+ if code == 802; UnableToOpenFileError
179
+ else; FileError; end
180
+ else
181
+ UnknownError
182
+ end
183
+ end
184
+ end
185
+
186
+ end
data/lib/rfm/layout.rb ADDED
@@ -0,0 +1,291 @@
1
+ module Rfm
2
+ # The Layout object represents a single FileMaker Pro layout. You use it to interact with
3
+ # records in FileMaker. *All* access to FileMaker data is done through a layout, and this
4
+ # layout determines which _table_ you actually hit (since every layout is explicitly associated
5
+ # with a particular table -- see FileMakers Layout->Layout Setup dialog box). You never specify
6
+ # _table_ information directly in RFM.
7
+ #
8
+ # Also, the layout determines which _fields_ will be returned. If a layout contains only three
9
+ # fields from a large table, only those three fields are returned. If a layout includes related
10
+ # fields from another table, they are returned as well. And if the layout includes portals, all
11
+ # data in the portals is returned (see Record::portal for details).
12
+ #
13
+ # As such, you can _significantly_ improve performance by limiting what you put on the layout.
14
+ #
15
+ # =Using Layouts
16
+ #
17
+ # The Layout object is where you get most of your work done. It includes methods for all
18
+ # FileMaker actions:
19
+ #
20
+ # * Layout::all
21
+ # * Layout::any
22
+ # * Layout::find
23
+ # * Layout::edit
24
+ # * Layout::create
25
+ # * Layout::delete
26
+ #
27
+ # =Running Scripts
28
+ #
29
+ # In FileMaker, execution of a script must accompany another action. For example, to run a script
30
+ # called _Remove Duplicates_ with a found set that includes everybody
31
+ # named _Bill_, do this:
32
+ #
33
+ # myLayout.find({"First Name" => "Bill"}, :post_script => "Remove Duplicates")
34
+ #
35
+ # ==Controlling When the Script Runs
36
+ #
37
+ # When you perform an action in FileMaker, it always executes in this order:
38
+ #
39
+ # 1. Perform any find
40
+ # 2. Sort the records
41
+ # 3. Return the results
42
+ #
43
+ # You can control when in the process the script runs. Each of these options is available:
44
+ #
45
+ # * *post_script* tells FileMaker to run the script after finding and sorting
46
+ # * *pre_find_script* tells FileMaker to run the script _before_ finding
47
+ # * *pre_sort_script* tells FileMaker to run the script _before_ sorting, but _after_ finding
48
+ #
49
+ # ==Passing Parameters to a Script
50
+ #
51
+ # If you want to pass a parameter to the script, use the options above, but supply an array value
52
+ # instead of a single string. For example:
53
+ #
54
+ # myLayout.find({"First Name" => "Bill"}, :post_script => ["Remove Duplicates", 10])
55
+ #
56
+ # This sample runs the script called "Remove Duplicates" and passes it the value +10+ as its
57
+ # script parameter.
58
+ #
59
+ # =Common Options
60
+ #
61
+ # Most of the methods on the Layout object accept an optional hash of +options+ to manipulate the
62
+ # action. For example, when you perform a find, you will typiclaly get back _all_ matching records.
63
+ # If you want to limit the number of records returned, you can do this:
64
+ #
65
+ # myLayout.find({"First Name" => "Bill"}, :max_records => 100)
66
+ #
67
+ # The +:max_records+ option tells FileMaker to limit the number of records returned.
68
+ #
69
+ # This is the complete list of available options:
70
+ #
71
+ # * *max_records* tells FileMaker how many records to return
72
+ #
73
+ # * *skip_records* tells FileMaker how many records in the found set to skip, before
74
+ # returning results; this is typically combined with +max_records+ to "page" through
75
+ # records
76
+ #
77
+ # * *sort_field* tells FileMaker to sort the records by the specified field
78
+ #
79
+ # * *sort_order* can be +descend+ or +ascend+ and determines the order
80
+ # of the sort when +sort_field+ is specified
81
+ #
82
+ # * *post_script* tells FileMaker to perform a script after carrying out the action; you
83
+ # can pass the script name, or a two-element array, with the script name first, then the
84
+ # script parameter
85
+ #
86
+ # * *pre_find_script* is like +post_script+ except the script runs before any find is
87
+ # performed
88
+ #
89
+ # * *pre_sort_script* is like +pre_find_script+ except the script runs after any find
90
+ # and before any sort
91
+ #
92
+ # * *response_layout* tells FileMaker to switch layouts before producing the response; this
93
+ # is useful when you need a field on a layout to perform a find, edit, or create, but you
94
+ # want to improve performance by not including the field in the result
95
+ #
96
+ # * *logical_operator* can be +and+ or +or+ and tells FileMaker how to process multiple fields
97
+ # in a find request
98
+ #
99
+ # * *modification_id* lets you pass in the modification id from a Record object with the request;
100
+ # when you do, the action will fail if the record was modified in FileMaker after it was retrieved
101
+ # by RFM but before the action was run
102
+ #
103
+ #
104
+ # =Attributes
105
+ #
106
+ # The Layout object has a few useful attributes:
107
+ #
108
+ # * +name+ is the name of the layout
109
+ #
110
+ # * +field_controls+ is a hash of FieldControl objects, with the field names as keys. FieldControl's
111
+ # tell you about the field on the layout: how is it formatted and what value list is assigned
112
+ #
113
+ # Note: It is possible to put the same field on a layout more than once. When this is the case, the
114
+ # value in +field_controls+ for that field is an array with one element representing each instance
115
+ # of the field.
116
+ #
117
+ # * +value_lists+ is a hash of arrays. The keys are value list names, and the values in the hash
118
+ # are arrays containing the actual value list items. +value_lists+ will include every value
119
+ # list that is attached to any field on the layout
120
+
121
+ class Layout
122
+
123
+ # Initialize a layout object. You never really need to do this. Instead, just do this:
124
+ #
125
+ # myServer = Rfm::Server.new(...)
126
+ # myDatabase = myServer["Customers"]
127
+ # myLayout = myDatabase["Details"]
128
+ #
129
+ # This sample code gets a layout object representing the Details layout in the Customers database
130
+ # on the FileMaker server.
131
+ #
132
+ # In case it isn't obvious, this is more easily expressed this way:
133
+ #
134
+ # myServer = Rfm::Server.new(...)
135
+ # myLayout = myServer["Customers"]["Details"]
136
+ def initialize(name, db)
137
+ @name = name
138
+ @db = db
139
+
140
+ @loaded = false
141
+ @field_controls = Rfm::CaseInsensitiveHash.new
142
+ @value_lists = Rfm::CaseInsensitiveHash.new
143
+ end
144
+
145
+ attr_reader :name, :db
146
+
147
+ # Returns a ResultSet object containing _every record_ in the table associated with this layout.
148
+ def all(options = {})
149
+ get_records('-findall', {}, options)
150
+ end
151
+
152
+ # Returns a ResultSet containing a single random record from the table associated with this layout.
153
+ def any(options = {})
154
+ get_records('-findany', {}, options)
155
+ end
156
+
157
+ # Finds a record. Typically you will pass in a hash of field names and values. For example:
158
+ #
159
+ # myLayout.find({"First Name" => "Bill"})
160
+ #
161
+ # Values in the hash work just like value in FileMaker's Find mode. You can use any special
162
+ # symbols (+==+, +...+, +>+, etc...).
163
+ #
164
+ # If you pass anything other than a hash as the first parameter, it is converted to a string and
165
+ # assumed to be FileMaker's internal id for a record (the recid).
166
+ def find(hash_or_recid, options = {})
167
+ if hash_or_recid.kind_of? Hash
168
+ get_records('-find', hash_or_recid, options)
169
+ else
170
+ get_records('-find', {'-recid' => hash_or_recid.to_s}, options)
171
+ end
172
+ end
173
+
174
+ # Updates the contents of the record whose internal +recid+ is specified. Send in a hash of new
175
+ # data in the +values+ parameter. Returns a RecordSet containing the modified record. For example:
176
+ #
177
+ # recid = myLayout.find({"First Name" => "Bill"})[0].record_id
178
+ # myLayout.edit(recid, {"First Name" => "Steve"})
179
+ #
180
+ # The above code would find the first record with _Bill_ in the First Name field and change the
181
+ # first name to _Steve_.
182
+ def edit(recid, values, options = {})
183
+ get_records('-edit', {'-recid' => recid}.merge(values), options)
184
+ end
185
+
186
+ # Creates a new record in the table associated with this layout. Pass field data as a hash in the
187
+ # +values+ parameter. Returns the newly created record in a RecordSet. You can use the returned
188
+ # record to, ie, discover the values in auto-enter fields (like serial numbers).
189
+ #
190
+ # For example:
191
+ #
192
+ # result = myLayout.create({"First Name" => "Jerry", "Last Name" => "Robin"})
193
+ # id = result[0]["ID"]
194
+ #
195
+ # The above code adds a new record with first name _Jerry_ and last name _Robin_. It then
196
+ # puts the value from the ID field (a serial number) into a ruby variable called +id+.
197
+ def create(values, options = {})
198
+ get_records('-new', values, options)
199
+ end
200
+
201
+ # Deletes the record with the specified internal recid. Returns a ResultSet with the deleted record.
202
+ #
203
+ # For example:
204
+ #
205
+ # recid = myLayout.find({"First Name" => "Bill"})[0].record_id
206
+ # myLayout.delete(recid)
207
+ #
208
+ # The above code finds every record with _Bill_ in the First Name field, then deletes the first one.
209
+ def delete(recid, options = {})
210
+ get_records('-delete', {'-recid' => recid}, options)
211
+ return nil
212
+ end
213
+
214
+ def field_controls
215
+ load unless @loaded
216
+ @field_controls
217
+ end
218
+
219
+ def value_lists
220
+ load unless @loaded
221
+ @value_lists
222
+ end
223
+
224
+ private
225
+
226
+ def load
227
+ #require 'rexml/document'
228
+ require 'nokogiri'
229
+
230
+ @loaded = true
231
+ #fmpxmllayout = @db.server.load_layout(self).body
232
+ fmpxmllayout = @db.server.load_layout(self)
233
+ #doc = REXML::Document.new(fmpxmllayout)
234
+ doc = Nokogiri::XML(fmpxmllayout)
235
+ #root = doc.root
236
+
237
+ # check for errors
238
+ #error = root.elements['ERRORCODE'].text.to_i
239
+ error = doc.xpath('//ERRORCODE').children[0].to_s.to_i
240
+ raise Rfm::Error::FileMakerError.getError(error) if error != 0
241
+
242
+ # process valuelists
243
+ if doc.xpath('//VALUELIST').size > 0 #root.elements['VALUELISTS'].size > 0
244
+ #root.elements['VALUELISTS'].each_element('VALUELIST') { |valuelist|
245
+ doc.xpath('//VALUELIST').each {|valuelist|
246
+ name = valuelist['NAME']
247
+ #@value_lists[name] = valuelist.elements.collect {|e| e.text}
248
+ @value_lists[name] = valuelist.children.collect{|value|
249
+ Rfm::Metadata::ValueListItem.new(value.children[0].to_s, value['DISPLAY'], name)
250
+ }
251
+ }
252
+ @value_lists.freeze
253
+ end
254
+
255
+ # process field controls
256
+ #root.elements['LAYOUT'].each_element('FIELD') { |field|
257
+ doc.xpath('//FIELD').each {|field|
258
+ #name = field.attributes['NAME']
259
+ name = field['NAME']
260
+ style_xml = field.children[0]
261
+ #style = field.elements['STYLE'].attributes['TYPE']
262
+ style = style_xml['TYPE']
263
+ #value_list_name = field.elements['STYLE'].attributes['VALUELIST']
264
+ value_list_name = style_xml['VALUELIST']
265
+ value_list = @value_lists[value_list_name] if value_list_name != ''
266
+ field_control = Rfm::Metadata::FieldControl.new(name, style, value_list_name, value_list)
267
+ existing = @field_controls[name]
268
+ if existing
269
+ if existing.kind_of?(Array)
270
+ existing << field_control
271
+ else
272
+ @field_controls[name] = Array[existing, field_control]
273
+ end
274
+ else
275
+ @field_controls[name] = field_control
276
+ end
277
+ }
278
+ @field_controls.freeze
279
+ end
280
+
281
+ def get_records(action, extra_params = {}, options = {})
282
+ include_portals = options[:include_portals] ? options.delete(:include_portals) : nil
283
+ xml_response = @db.server.connect(@db.account_name, @db.password, action, params.merge(extra_params), options).body
284
+ Rfm::Resultset.new(@db.server, xml_response, self, include_portals)
285
+ end
286
+
287
+ def params
288
+ {"-db" => @db.name, "-lay" => self.name}
289
+ end
290
+ end
291
+ end