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,96 @@
1
+ module Rfm
2
+ # The Database object represents a single FileMaker Pro database. When you retrieve a Database
3
+ # object from a server, its account name and password are set to the account name and password you
4
+ # used when initializing the Server object. You can override this of course:
5
+ #
6
+ # myDatabase = myServer["Customers"]
7
+ # myDatabase.account_name = "foo"
8
+ # myDatabase.password = "bar"
9
+ #
10
+ # =Accessing Layouts
11
+ #
12
+ # All interaction with FileMaker happens through a Layout object. You can get a Layout object
13
+ # from the Database object like this:
14
+ #
15
+ # myLayout = myDatabase["Details"]
16
+ #
17
+ # This code gets the Layout object representing the layout called Details in the database.
18
+ #
19
+ # Note: RFM does not talk to the server when you retrieve a Layout object in this way. Instead, it
20
+ # simply assumes you know what you're talking about. If the layout you specify does not exist, you
21
+ # will get no error at this point. Instead, you'll get an error when you use the Layout object methods
22
+ # to talk to FileMaker. This makes debugging a little less convenient, but it would introduce too much
23
+ # overhead to hit the server at this point.
24
+ #
25
+ # The Database object has a +layout+ attribute that provides alternate access to Layout objects. It acts
26
+ # like a hash of Layout objects, one for each accessible layout in the database. So, for example, you
27
+ # can do this if you want to print out a list of all layouts:
28
+ #
29
+ # myDatabase.layout.each {|layout|
30
+ # puts layout.name
31
+ # }
32
+ #
33
+ # The Database::layout attribute is actually a LayoutFactory object, although it subclasses hash, so it
34
+ # should work in all the ways you expect. Note, though, that it is completely empty until the first time
35
+ # you attempt to access its elements. At that (lazy) point, it hits FileMaker, loads in the list of layouts,
36
+ # and constructs a Layout object for each one. In other words, it incurrs no overhead until you use it.
37
+ #
38
+ # =Accessing Scripts
39
+ #
40
+ # If for some reason you need to enumerate the scripts in a database, you can do so:
41
+ #
42
+ # myDatabase.script.each {|script|
43
+ # puts script.name
44
+ # }
45
+ #
46
+ # The Database::script attribute is actually a ScriptFactory object, although it subclasses hash, so it
47
+ # should work in all the ways you expect. Note, though, that it is completely empty until the first time
48
+ # you attempt to access its elements. At that (lazy) point, it hits FileMaker, loads in the list of scripts,
49
+ # and constructs a Script object for each one. In other words, it incurrs no overhead until you use it.
50
+ #
51
+ # Note: You don't need a Script object to _run_ a script (see the Layout object instead).
52
+ #
53
+ # =Attributes
54
+ #
55
+ # In addition to the +layout+ attribute, Server has a few other useful attributes:
56
+ #
57
+ # * *server* is the Server object this database comes from
58
+ # * *name* is the name of this database
59
+ # * *state* is a hash of all server options used to initialize this server
60
+ class Database
61
+
62
+ # Initialize a database object. You never really need to do this. Instead, just do this:
63
+ #
64
+ # myServer = Rfm::Server.new(...)
65
+ # myDatabase = myServer["Customers"]
66
+ #
67
+ # This sample code gets a database object representing the Customers database on the FileMaker server.
68
+ def initialize(name, server)
69
+ @name = name
70
+ @server = server
71
+ @account_name = server.state[:account_name] or ""
72
+ @password = server.state[:password] or ""
73
+ @layout = Rfm::Factory::LayoutFactory.new(server, self)
74
+ @script = Rfm::Factory::ScriptFactory.new(server, self)
75
+ end
76
+
77
+ attr_reader :server, :name, :account_name, :password, :layout, :script
78
+ attr_writer :account_name, :password
79
+
80
+ # Access the Layout object representing a layout in this database. For example:
81
+ #
82
+ # myDatabase['Details']
83
+ #
84
+ # would return a Layout object representing the _Details_
85
+ # layout in the database.
86
+ #
87
+ # Note: RFM never talks to the server until you perform an action. The Layout object
88
+ # returned is created on the fly and assumed to refer to a valid layout, but you will
89
+ # get no error at this point if the layout you specify doesn't exist. Instead, you'll
90
+ # receive an error when you actually try to perform some action it.
91
+ def [](layout_name)
92
+ self.layout[layout_name]
93
+ end
94
+
95
+ end
96
+ end
@@ -0,0 +1,50 @@
1
+ module Rfm
2
+ # The FieldControl object represents a field on a FileMaker layout. You can find out what field
3
+ # style the field uses, and the value list attached to it.
4
+ #
5
+ # =Attributes
6
+ #
7
+ # * *name* is the name of the field
8
+ #
9
+ # * *style* is any one of:
10
+ # * * :edit_box - a normal editable field
11
+ # * * :scrollable - an editable field with scroll bar
12
+ # * * :popup_menu - a pop-up menu
13
+ # * * :checkbox_set - a set of checkboxes
14
+ # * * :radio_button_set - a set of radio buttons
15
+ # * * :popup_list - a pop-up list
16
+ # * * :calendar - a pop-up calendar
17
+ #
18
+ # * *value_list_name* is the name of the attached value list, if any
19
+ #
20
+ # * *value_list* is an array of strings representing the value list items, or nil
21
+ # if this field has no attached value list
22
+ class FieldControl
23
+ def initialize(name, style, value_list_name, value_list)
24
+ @name = name
25
+ case style
26
+ when "EDITTEXT"
27
+ @style = :edit_box
28
+ when "POPUPMENU"
29
+ @style = :popup_menu
30
+ when "CHECKBOX"
31
+ @style = :checkbox_set
32
+ when "RADIOBUTTONS"
33
+ @style = :radio_button_set
34
+ when "POPUPLIST"
35
+ @style = :popup_list
36
+ when "CALENDAR"
37
+ @style = :calendar
38
+ when "SCROLLTEXT"
39
+ @style = :scrollable
40
+ else
41
+ nil
42
+ end
43
+ @value_list_name = value_list_name
44
+ @value_list = value_list
45
+ end
46
+
47
+ attr_reader :name, :style, :value_list_name, :value_list
48
+
49
+ end
50
+ end
@@ -0,0 +1,275 @@
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::Utility::CaseInsensitiveHash.new
142
+ @value_lists = Rfm::Utility::CaseInsensitiveHash.new
143
+ end
144
+
145
+ attr_reader :name, :db
146
+
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
+ # Returns a ResultSet object containing _every record_ in the table associated with this layout.
158
+ def all(options = {})
159
+ get_records('-findall', {}, options)
160
+ end
161
+
162
+ # Returns a ResultSet containing a single random record from the table associated with this layout.
163
+ def any(options = {})
164
+ get_records('-findany', {}, options)
165
+ end
166
+
167
+ # Finds a record. Typically you will pass in a hash of field names and values. For example:
168
+ #
169
+ # myLayout.find({"First Name" => "Bill"})
170
+ #
171
+ # Values in the hash work just like value in FileMaker's Find mode. You can use any special
172
+ # symbols (+==+, +...+, +>+, etc...).
173
+ #
174
+ # If you pass anything other than a hash as the first parameter, it is converted to a string and
175
+ # assumed to be FileMaker's internal id for a record (the recid).
176
+ def find(hash_or_recid, options = {})
177
+ if hash_or_recid.kind_of? Hash
178
+ get_records('-find', hash_or_recid, options)
179
+ else
180
+ get_records('-find', {'-recid' => hash_or_recid.to_s}, options)
181
+ end
182
+ end
183
+
184
+ # Updates the contents of the record whose internal +recid+ is specified. Send in a hash of new
185
+ # data in the +values+ parameter. Returns a RecordSet containing the modified record. For example:
186
+ #
187
+ # recid = myLayout.find({"First Name" => "Bill"})[0].record_id
188
+ # myLayout.edit(recid, {"First Name" => "Steve"})
189
+ #
190
+ # The above code would find the first record with _Bill_ in the First Name field and change the
191
+ # first name to _Steve_.
192
+ def edit(recid, values, options = {})
193
+ get_records('-edit', {'-recid' => recid}.merge(values), options)
194
+ end
195
+
196
+ # Creates a new record in the table associated with this layout. Pass field data as a hash in the
197
+ # +values+ parameter. Returns the newly created record in a RecordSet. You can use the returned
198
+ # record to, ie, discover the values in auto-enter fields (like serial numbers).
199
+ #
200
+ # For example:
201
+ #
202
+ # result = myLayout.create({"First Name" => "Jerry", "Last Name" => "Robin"})
203
+ # id = result[0]["ID"]
204
+ #
205
+ # The above code adds a new record with first name _Jerry_ and last name _Robin_. It then
206
+ # puts the value from the ID field (a serial number) into a ruby variable called +id+.
207
+ def create(values, options = {})
208
+ get_records('-new', values, options)
209
+ end
210
+
211
+ # Deletes the record with the specified internal recid. Returns a ResultSet with the deleted record.
212
+ #
213
+ # For example:
214
+ #
215
+ # recid = myLayout.find({"First Name" => "Bill"})[0].record_id
216
+ # myLayout.delete(recid)
217
+ #
218
+ # The above code finds every record with _Bill_ in the First Name field, then deletes the first one.
219
+ def delete(recid, options = {})
220
+ get_records('-delete', {'-recid' => recid}, options)
221
+ return nil
222
+ end
223
+
224
+ private
225
+
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
+ 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)
269
+ end
270
+
271
+ def params
272
+ {"-db" => @db.name, "-lay" => self.name}
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,18 @@
1
+ module Rfm
2
+ # The Script object represents a FileMaker script. At this point, the Script object exists only so
3
+ # you can enumrate all scripts in a Database (which is a rare need):
4
+ #
5
+ # myDatabase.script.each {|script|
6
+ # puts script.name
7
+ # }
8
+ #
9
+ # If you want to _run_ a script, see the Layout object instead.
10
+ class Script
11
+ def initialize(name, db)
12
+ @name = name
13
+ @db = db
14
+ end
15
+
16
+ attr_reader :name
17
+ end
18
+ end