lardawge-rfm 1.4.0 → 1.4.1

Sign up to get free protection for your applications and to get access to all the features.
File without changes
data/lib/rfm/error.rb CHANGED
@@ -1,256 +1,186 @@
1
- require "set"
2
-
3
- # These classes wrap the filemaker error codes. FileMakerError is the base class of this hierarchy.
4
- #
5
- # One could get a FileMakerError by doing:
6
- # err = Rfm::Error::FileMakerError.getError(102)
7
- #
8
- # The above code would return a FieldMissingError instance. Your could use this instance to raise that appropriate
9
- # exception:
10
- #
11
- # raise err
12
- #
13
- # You could access the specific error code by accessing:
14
- #
15
- # err.code
16
- #
17
- # Author:: Mufaddal Khumri
18
- # Copyright:: Copyright (c) 2007 Six Fried Rice, LLC and Mufaddal Khumri
19
- # License:: See MIT-LICENSE for details
20
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
21
19
  module Error
22
20
 
23
- class RfmError < StandardError
24
- end
25
-
26
- class CommunicationError < RfmError
27
- end
28
-
29
- class ParameterError < RfmError
30
- end
31
-
32
- class AuthenticationError < RfmError
33
- end
34
-
35
- # Base class for all FileMaker errors
36
- class FileMakerError < RfmError
37
- attr_accessor :code
21
+ class RfmError < StandardError #:nodoc:
22
+ attr_reader :code
38
23
 
39
- # Default filemaker error message map
40
- @default_messages = {}
41
- class << self; attr_reader :default_messages; end
42
-
43
-
44
- # This method instantiates and returns the appropriate FileMakerError object depending on the error code passed to it. It
45
- # also accepts an optional message.
46
- def self.getError(code, message = nil)
47
- if @default_messages == nil or @default_messages.size == 0
48
- (0..99).each{|i| @default_messages[i] = 'SystemError occurred.'}
49
- (100..199).each{|i| @default_messages[i] = 'MissingError occurred.'}
50
- @default_messages[102] = 'FieldMissingError occurred.'
51
- @default_messages[104] = 'ScriptMissingError occurred.'
52
- @default_messages[105] = 'LayoutMissingError occurred.'
53
- @default_messages[106] = 'TableMissingError occurred.'
54
- (200..299).each{|i| @default_messages[i] = 'SecurityError occurred.'}
55
- @default_messages[200] = 'RecordAccessDeniedError occurred.'
56
- @default_messages[201] = 'FieldCannotBeModifiedError occurred.'
57
- @default_messages[202] = 'FieldAccessIsDeniedError occurred.'
58
- (300..399).each{|i| @default_messages[i] = 'ConcurrencyError occurred.'}
59
- @default_messages[301] = 'RecordInUseError occurred.'
60
- @default_messages[302] = 'TableInUseError occurred.'
61
- @default_messages[306] = 'RecordModIdDoesNotMatchError occurred.'
62
- (400..499).each{|i| @default_messages[i] = 'GeneralError occurred.'}
63
- @default_messages[401] = 'NoRecordsFoundError occurred.'
64
- (500..599).each{|i| @default_messages[i] = 'ValidationError occurred.'}
65
- @default_messages[500] = 'DateValidationError occurred.'
66
- @default_messages[501] = 'TimeValidationError occurred.'
67
- @default_messages[502] = 'NumberValidationError occurred.'
68
- @default_messages[503] = 'RangeValidationError occurred.'
69
- @default_messages[504] = 'UniqueValidationError occurred.'
70
- @default_messages[505] = 'ExistingValidationError occurred.'
71
- @default_messages[506] = 'ValueListValidationError occurred.'
72
- @default_messages[507] = 'ValidationCalculationError occurred.'
73
- @default_messages[508] = 'InvalidFindModeValueError occurred.'
74
- @default_messages[511] = 'MaximumCharactersValidationError occurred.'
75
- (800..899).each{|i| @default_messages[i] = 'FileError occurred.'}
76
- @default_messages[802] = 'UnableToOpenFileError occurred.'
77
- end
78
-
79
- message = @default_messages[code] if message == nil || message.strip == ''
80
- message += " (FileMaker Error ##{code})"
81
-
82
- if 0 <= code and code <= 99
83
- err = SystemError.new(message)
84
- elsif 100 <= code and code <= 199
85
- if code == 101
86
- err = RecordMissingError.new(message)
87
- elsif code == 102
88
- err = FieldMissingError.new(message)
89
- elsif code == 104
90
- err = ScriptMissingError.new(message)
91
- elsif code == 105
92
- err = LayoutMissingError.new(message)
93
- elsif code == 106
94
- err = TableMissingError.new(message)
95
- else
96
- err = MissingError.new(message)
97
- end
98
- elsif 200 <= code and code <= 299
99
- if code == 200
100
- err = RecordAccessDeniedError.new(message)
101
- elsif code == 201
102
- err = FieldCannotBeModifiedError.new(message)
103
- elsif code == 202
104
- err = FieldAccessIsDeniedError.new(message)
105
- else
106
- err = SecurityError.new(message)
107
- end
108
- elsif 300 <= code and code <= 399
109
- if code == 301
110
- err = RecordInUseError.new(message)
111
- elsif code == 302
112
- err = TableInUseError.new(message)
113
- elsif code == 306
114
- err = RecordModIdDoesNotMatchError.new(message)
115
- else
116
- err = ConcurrencyError.new(message)
117
- end
118
- elsif 400 <= code and code <= 499
119
- if code == 401
120
- err = NoRecordsFoundError.new(message)
121
- else
122
- err = GeneralError.new(message)
123
- end
124
- elsif 500 <= code and code <= 599
125
- if code == 500
126
- err = DateValidationError.new(message)
127
- elsif code == 501
128
- err = TimeValidationError.new(message)
129
- elsif code == 502
130
- err = NumberValidationError.new(message)
131
- elsif code == 503
132
- err = RangeValidationError.new(message)
133
- elsif code == 504
134
- err = UniqueValidationError.new(message)
135
- elsif code == 505
136
- err = ExistingValidationError.new(message)
137
- elsif code == 506
138
- err = ValueListValidationError.new(message)
139
- elsif code == 507
140
- err = ValidationCalculationError.new(message)
141
- elsif code == 508
142
- err = InvalidFindModeValueError.new(message)
143
- elsif code == 511
144
- err = MaximumCharactersValidationError.new(message)
145
- else
146
- err = ValidationError.new(message)
147
- end
148
- elsif 800 <= code and code <= 899
149
- if code == 802
150
- err = UnableToOpenFileError.new(message)
151
- else
152
- err = FileError.new(message)
153
- end
154
- else
155
- # called for code == -1 or any other code not handled above.
156
- err = UnknownError.new(message)
157
- end
158
- err.code = code
159
- return err
160
- end
24
+ def initialize(code, message=nil)
25
+ @code = code
26
+ super(message)
27
+ end
161
28
  end
162
-
163
- class UnknownError < FileMakerError
29
+
30
+ class UnknownError < RfmError
164
31
  end
165
32
 
166
- class SystemError < FileMakerError
33
+ class SystemError < RfmError
167
34
  end
168
35
 
169
- class MissingError < FileMakerError
36
+ class MissingError < RfmError
170
37
  end
171
-
172
- class RecordMissingError < MissingError
38
+
39
+ class RecordMissingError < MissingError #:nodoc:
173
40
  end
174
-
175
- class FieldMissingError < MissingError
41
+
42
+ class FieldMissingError < MissingError #:nodoc:
176
43
  end
177
-
178
- class ScriptMissingError < MissingError
44
+
45
+ class ScriptMissingError < MissingError #:nodoc:
179
46
  end
180
-
181
- class LayoutMissingError < MissingError
47
+
48
+ class LayoutMissingError < MissingError #:nodoc:
182
49
  end
183
-
184
- class TableMissingError < MissingError
50
+
51
+ class TableMissingError < MissingError #:nodoc:
185
52
  end
186
-
187
- class SecurityError < FileMakerError
53
+
54
+ class SecurityError < RfmError #:nodoc:
188
55
  end
189
-
190
- class RecordAccessDeniedError < SecurityError
56
+
57
+ class RecordAccessDeniedError < SecurityError #:nodoc:
191
58
  end
192
-
193
- class FieldCannotBeModifiedError < SecurityError
59
+
60
+ class FieldCannotBeModifiedError < SecurityError #:nodoc:
194
61
  end
195
-
196
- class FieldAccessIsDeniedError < SecurityError
62
+
63
+ class FieldAccessIsDeniedError < SecurityError #:nodoc:
197
64
  end
198
-
199
- class ConcurrencyError < FileMakerError
65
+
66
+ class ConcurrencyError < RfmError #:nodoc:
200
67
  end
201
-
202
- class RecordInUseError < ConcurrencyError
68
+
69
+ class RecordInUseError < ConcurrencyError #:nodoc:
203
70
  end
204
-
205
- class TableInUseError < ConcurrencyError
71
+
72
+ class TableInUseError < ConcurrencyError #:nodoc:
206
73
  end
207
-
208
- class RecordModIdDoesNotMatchError < ConcurrencyError
74
+
75
+ class RecordModIdDoesNotMatchError < ConcurrencyError #:nodoc:
209
76
  end
210
-
211
- class GeneralError < FileMakerError
77
+
78
+ class GeneralError < RfmError #:nodoc:
212
79
  end
213
-
214
- class NoRecordsFoundError < GeneralError
80
+
81
+ class NoRecordsFoundError < GeneralError #:nodoc:
215
82
  end
216
-
217
- class ValidationError < FileMakerError
83
+
84
+ class ValidationError < RfmError #:nodoc:
218
85
  end
219
-
220
- class DateValidationError < ValidationError
86
+
87
+ class DateValidationError < ValidationError #:nodoc:
221
88
  end
222
-
223
- class TimeValidationError < ValidationError
89
+
90
+ class TimeValidationError < ValidationError #:nodoc:
224
91
  end
225
-
226
- class NumberValidationError < ValidationError
92
+
93
+ class NumberValidationError < ValidationError #:nodoc:
227
94
  end
228
-
229
- class RangeValidationError < ValidationError
95
+
96
+ class RangeValidationError < ValidationError #:nodoc:
230
97
  end
231
-
232
- class UniqueValidationError < ValidationError
98
+
99
+ class UniqueValidationError < ValidationError #:nodoc:
233
100
  end
234
-
235
- class ExistingValidationError < ValidationError
101
+
102
+ class ExistingValidationError < ValidationError #:nodoc:
236
103
  end
237
-
238
- class ValueListValidationError < ValidationError
104
+
105
+ class ValueListValidationError < ValidationError #:nodoc:
239
106
  end
240
-
241
- class ValidationCalculationError < ValidationError
107
+
108
+ class ValidationCalculationError < ValidationError #:nodoc:
242
109
  end
243
-
244
- class InvalidFindModeValueError < ValidationError
110
+
111
+ class InvalidFindModeValueError < ValidationError #:nodoc:
245
112
  end
246
-
247
- class MaximumCharactersValidationError < ValidationError
113
+
114
+ class MaximumCharactersValidationError < ValidationError #:nodoc:
248
115
  end
249
-
250
- class FileError < FileMakerError
116
+
117
+ class FileError < RfmError #:nodoc:
251
118
  end
252
-
253
- class UnableToOpenFileError < FileError
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
254
183
  end
255
184
  end
185
+
256
186
  end
@@ -136,24 +136,10 @@ module Rfm
136
136
  def initialize(name, db)
137
137
  @name = name
138
138
  @db = db
139
-
140
- @loaded = false
141
- @field_controls = Rfm::Utility::CaseInsensitiveHash.new
142
- @value_lists = Rfm::Utility::CaseInsensitiveHash.new
143
139
  end
144
140
 
145
141
  attr_reader :name, :db
146
142
 
147
- def field_controls
148
- load unless @loaded
149
- @field_controls
150
- end
151
-
152
- def value_lists
153
- load unless @loaded
154
- @value_lists
155
- end
156
-
157
143
  # Returns a ResultSet object containing _every record_ in the table associated with this layout.
158
144
  def all(options = {})
159
145
  get_records('-findall', {}, options)
@@ -223,49 +209,10 @@ module Rfm
223
209
 
224
210
  private
225
211
 
226
- def load
227
- @loaded = true
228
- fmpxmllayout = @db.server.load_layout(self).body
229
- doc = REXML::Document.new(fmpxmllayout)
230
- root = doc.root
231
-
232
- # check for errors
233
- error = root.elements['ERRORCODE'].text.to_i
234
- raise Rfm::Error::FileMakerError.getError(error) if error != 0
235
-
236
- # process valuelists
237
- if root.elements['VALUELISTS'].size > 0
238
- root.elements['VALUELISTS'].each_element('VALUELIST') { |valuelist|
239
- name = valuelist.attributes['NAME']
240
- @value_lists[name] = valuelist.elements.collect {|e| e.text}
241
- }
242
- @value_lists.freeze
243
- end
244
-
245
- # process field controls
246
- root.elements['LAYOUT'].each_element('FIELD') { |field|
247
- name = field.attributes['NAME']
248
- style = field.elements['STYLE'].attributes['TYPE']
249
- value_list_name = field.elements['STYLE'].attributes['VALUELIST']
250
- value_list = @value_lists[value_list_name] if value_list_name != ''
251
- field_control = FieldControl.new(name, style, value_list_name, value_list)
252
- existing = @field_controls[name]
253
- if existing
254
- if existing.kind_of?(Array)
255
- existing << field_control
256
- else
257
- @field_controls[name] = Array[existing, field_control]
258
- end
259
- else
260
- @field_controls[name] = field_control
261
- end
262
- }
263
- @field_controls.freeze
264
- end
265
-
266
212
  def get_records(action, extra_params = {}, options = {})
267
- Rfm::Result::ResultSet.new(@db.server, @db.server.do_action(@db.account_name,
268
- @db.password, action, params().merge(extra_params), options).body, self)
213
+ include_portals = options[:include_portals] ? options.delete(:include_portals) : nil
214
+ xml_response = @db.server.connect(@db.account_name, @db.password, action, params.merge(extra_params), options).body
215
+ Rfm::Resultset.new(@db.server, xml_response, self, include_portals)
269
216
  end
270
217
 
271
218
  def params
@@ -0,0 +1,93 @@
1
+ module Rfm
2
+ module Metadata
3
+ # The Field object represents a single FileMaker field. It *does not hold the data* in the field. Instead,
4
+ # it serves as a source of metadata about the field. For example, if you're script is trying to be highly
5
+ # dynamic about its field access, it may need to determine the data type of a field at run time. Here's
6
+ # how:
7
+ #
8
+ # field_name = "Some Field Name"
9
+ # case myRecord.fields[field_name].result
10
+ # when "text"
11
+ # # it is a text field, so handle appropriately
12
+ # when "number"
13
+ # # it is a number field, so handle appropriately
14
+ # end
15
+ #
16
+ # =Attributes
17
+ #
18
+ # The Field object has the following attributes:
19
+ #
20
+ # * *name* is the name of the field
21
+ #
22
+ # * *result* is the data type of the field; possible values include:
23
+ # * text
24
+ # * number
25
+ # * date
26
+ # * time
27
+ # * timestamp
28
+ # * container
29
+ #
30
+ # * *type* any of these:
31
+ # * normal (a normal data field)
32
+ # * calculation
33
+ # * summary
34
+ #
35
+ # * *max_repeats* is the number of repetitions (1 for a normal field, more for a repeating field)
36
+ #
37
+ # * *global* is +true+ is this is a global field, *false* otherwise
38
+ #
39
+ # Note: Field types match FileMaker's own values, but the terminology differs. The +result+ attribute
40
+ # tells you the data type of the field, regardless of whether it is a calculation, summary, or normal
41
+ # field. So a calculation field whose result type is _timestamp_ would have these attributes:
42
+ #
43
+ # * result: timestamp
44
+ # * type: calculation
45
+ #
46
+ # * *control& is a FieldControl object representing the sytle and value list information associated
47
+ # with this field on the layout.
48
+ #
49
+ # Note: Since a field can sometimes appear on a layout more than once, +control+ may be an Array.
50
+ # If you don't know ahead of time, you'll need to deal with this. One easy way is:
51
+ #
52
+ # controls = [myField.control].flatten
53
+ # controls.each {|control|
54
+ # # do something with the control here
55
+ # }
56
+ #
57
+ # The code above makes sure the control is always an array. Typically, though, you'll know up front
58
+ # if the control is an array or not, and you can code accordingly.
59
+
60
+ class Field
61
+
62
+ attr_reader :name, :result, :type, :max_repeats, :global
63
+
64
+ # Initializes a field object. You'll never need to do this. Instead, get your Field objects from
65
+ # ResultSet::fields
66
+ def initialize(field)
67
+ @name = field['name']
68
+ @result = field['result']
69
+ @type = field['type']
70
+ @max_repeats = field['max-repeats']
71
+ @global = field['global']
72
+ end
73
+
74
+ # Coerces the text value from an +fmresultset+ document into proper Ruby types based on the
75
+ # type of the field. You'll never need to do this: Rfm does it automatically for you when you
76
+ # access field data through the Record object.
77
+ def coerce(value, resultset)
78
+ return nil if value.empty?
79
+ case self.result
80
+ when "text" then value
81
+ when "number" then BigDecimal.new(value)
82
+ when "date" then Date.strptime(value, resultset.date_format)
83
+ when "time" then DateTime.strptime("1/1/-4712 #{value}", "%m/%d/%Y #{resultset.time_format}")
84
+ when "timestamp" then DateTime.strptime(value, resultset.timestamp_format)
85
+ when "container" then URI.parse("#{resultset.server.scheme}://#{resultset.server.host_name}:#{resultset.server.port}#{value}")
86
+ else nil
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,20 @@
1
+ module Rfm
2
+ module Metadata
3
+ # The Script object represents a FileMaker script. At this point, the Script object exists only so
4
+ # you can enumrate all scripts in a Database (which is a rare need):
5
+ #
6
+ # myDatabase.script.each {|script|
7
+ # puts script.name
8
+ # }
9
+ #
10
+ # If you want to _run_ a script, see the Layout object instead.
11
+ class Script
12
+ def initialize(name, db)
13
+ @name = name
14
+ @db = db
15
+ end
16
+
17
+ attr_reader :name
18
+ end
19
+ end
20
+ end