ginjo-rfm 2.1.7 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/CHANGELOG.md +45 -16
- data/README.md +251 -274
- data/lib/rfm.rb +42 -20
- data/lib/rfm/VERSION +1 -1
- data/lib/rfm/base.rb +63 -196
- data/lib/rfm/database.rb +15 -16
- data/lib/rfm/layout.rb +244 -271
- data/lib/rfm/metadata/datum.rb +45 -0
- data/lib/rfm/metadata/field.rb +33 -13
- data/lib/rfm/metadata/field_control.rb +57 -25
- data/lib/rfm/metadata/layout_meta.rb +38 -0
- data/lib/rfm/metadata/resultset_meta.rb +66 -0
- data/lib/rfm/metadata/value_list_item.rb +7 -6
- data/lib/rfm/record.rb +54 -74
- data/lib/rfm/resultset.rb +63 -112
- data/lib/rfm/server.rb +6 -172
- data/lib/rfm/utilities/config.rb +100 -55
- data/lib/rfm/utilities/connection.rb +209 -0
- data/lib/rfm/utilities/core_ext.rb +14 -1
- data/lib/rfm/utilities/factory.rb +68 -65
- data/lib/rfm/utilities/sax_parser.rb +1039 -0
- metadata +154 -206
- data/lib/rfm/utilities/fmpxmlresult.rb +0 -167
- data/lib/rfm/utilities/fmresultset.rb +0 -153
- data/lib/rfm/utilities/xml_parser.rb +0 -124
- data/lib/rfm/xml_mini/hpricot.rb +0 -133
- data/lib/rfm/xml_mini/ox_sax.rb +0 -91
- data/lib/rfm/xml_mini/rexml_sax.rb +0 -81
data/lib/rfm/database.rb
CHANGED
@@ -67,23 +67,13 @@ module Rfm
|
|
67
67
|
#
|
68
68
|
# This sample code gets a database object representing the Customers database on the FileMaker server.
|
69
69
|
def initialize(*args) #name, server_obj, acnt=nil, pass=nil
|
70
|
-
|
71
|
-
rfm_metaclass.instance_variable_set :@server, (options[:objects].delete_at(0) || options[:server_object])
|
72
|
-
options = get_config(options)
|
73
|
-
|
74
|
-
config sanitize_config(options, {}, true)
|
75
|
-
config :parent=> 'server'
|
76
|
-
config :database=> options[:strings].delete_at(0) || options[:database]
|
77
|
-
config :account_name=> options[:strings].delete_at(0) || options[:account_name]
|
78
|
-
config :password=> options[:strings].delete_at(0) || options[:password]
|
79
|
-
|
70
|
+
config(*args)
|
80
71
|
raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Database has no name. Attempted name '#{state[:database]}'.") if state[:database].to_s == ''
|
81
|
-
|
82
72
|
@layouts = Rfm::Factory::LayoutFactory.new(server, self)
|
83
73
|
@scripts = Rfm::Factory::ScriptFactory.new(server, self)
|
84
74
|
end
|
85
75
|
|
86
|
-
|
76
|
+
meta_attr_accessor :server
|
87
77
|
attr_reader :layouts, :scripts
|
88
78
|
# Not sure if these writers are ever used
|
89
79
|
#attr_writer :account_name, :password
|
@@ -97,9 +87,18 @@ module Rfm
|
|
97
87
|
def password; state[:password]; end
|
98
88
|
def password=(x); config :password=>x; end
|
99
89
|
|
100
|
-
|
101
|
-
|
102
|
-
|
90
|
+
def config(*args)
|
91
|
+
super(:capture_strings_with=>[:database, :account_name, :password])
|
92
|
+
super(*args) do |params|
|
93
|
+
(self.server = params[:objects][0]) if params && params[:objects] && params[:objects][0] && params[:objects][0].is_a?(Rfm::Server)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
alias_method :server_orig, :server
|
98
|
+
def server
|
99
|
+
server_orig || (self.server = Rfm::Server.new(state[:host], state[:account_name], state[:password], self))
|
100
|
+
end
|
101
|
+
|
103
102
|
|
104
103
|
# Access the Layout object representing a layout in this database. For example:
|
105
104
|
#
|
@@ -115,7 +114,7 @@ module Rfm
|
|
115
114
|
# def [](layout_name)
|
116
115
|
# self.layout[layout_name]
|
117
116
|
# end
|
118
|
-
|
117
|
+
def_delegators :layouts, :[], :modelize, :models # modelize & models acquired from Rfm::Base
|
119
118
|
|
120
119
|
end
|
121
120
|
end
|
data/lib/rfm/layout.rb
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
require 'delegate'
|
1
2
|
module Rfm
|
2
3
|
# The Layout object represents a single FileMaker Pro layout. You use it to interact with
|
3
4
|
# records in FileMaker. *All* access to FileMaker data is done through a layout, and this
|
@@ -120,6 +121,41 @@ module Rfm
|
|
120
121
|
|
121
122
|
class Layout
|
122
123
|
include Config
|
124
|
+
|
125
|
+
meta_attr_accessor :db
|
126
|
+
attr_reader :field_mapping
|
127
|
+
attr_writer :resultset_meta
|
128
|
+
def_delegator :db, :server
|
129
|
+
#alias_method :database, :db # This fails if db object hasn't been set yet with meta_attr_accessor
|
130
|
+
def database; db; end
|
131
|
+
attr_accessor :model #, :parent_layout, :subs
|
132
|
+
def_delegators :meta, :field_controls, :value_lists
|
133
|
+
def_delegators :resultset_meta, :date_format, :time_format, :timestamp_format, :field_meta, :portal_meta, :table
|
134
|
+
|
135
|
+
# Methods that must be kept after rewrite!!!
|
136
|
+
#
|
137
|
+
# field_mapping
|
138
|
+
# db (database)
|
139
|
+
# name
|
140
|
+
# resultset_meta
|
141
|
+
# date_format
|
142
|
+
# time_format
|
143
|
+
# timestamp_format
|
144
|
+
# field_meta
|
145
|
+
# field_controls
|
146
|
+
# field_names
|
147
|
+
# field_names_no_load
|
148
|
+
# value_lists
|
149
|
+
# count
|
150
|
+
# total_count
|
151
|
+
# portal_meta
|
152
|
+
# portal_meta_no_load
|
153
|
+
# portal_names
|
154
|
+
# table
|
155
|
+
# table_no_load
|
156
|
+
# server
|
157
|
+
|
158
|
+
|
123
159
|
|
124
160
|
# Initialize a layout object. You never really need to do this. Instead, just do this:
|
125
161
|
#
|
@@ -135,305 +171,219 @@ module Rfm
|
|
135
171
|
# myServer = Rfm::Server.new(...)
|
136
172
|
# myLayout = myServer["Customers"]["Details"]
|
137
173
|
def initialize(*args) #name, db_obj
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
options = get_config(options)
|
142
|
-
|
143
|
-
config sanitize_config(options, {}, true)
|
144
|
-
config :layout => options[:strings].delete_at(0) || options[:layout]
|
145
|
-
|
146
|
-
raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Layout has no name. Attempted name '#{state[:layout]}'.") if state[:layout].to_s == ''
|
147
|
-
|
174
|
+
# self.subs ||= []
|
175
|
+
config(*args)
|
176
|
+
raise Rfm::Error::RfmError.new(0, "New instance of Rfm::Layout has no name. Attempted name '#{state[:layout]}'.") if state[:layout].to_s == ''
|
148
177
|
@loaded = false
|
149
|
-
@
|
150
|
-
|
151
|
-
# @portal_meta = nil
|
152
|
-
# @field_names = nil
|
153
|
-
#@ignore_bad_data = (db_obj.server.state[:ignore_bad_data] rescue nil)
|
178
|
+
@meta = Metadata::LayoutMeta.new(self)
|
179
|
+
self
|
154
180
|
end
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
# This method may be obsolete, since the option can now be set with #config.
|
163
|
-
def ignore_bad_data(val = nil)
|
164
|
-
(config :ignore_bad_data => val) unless val.nil?
|
165
|
-
state[:ignore_bad_data]
|
181
|
+
|
182
|
+
def config(*args)
|
183
|
+
super(:capture_strings_with=>[:layout])
|
184
|
+
super(*args) do |params|
|
185
|
+
(self.name = params[:strings][0]) if params && params[:strings] && params[:strings].any?
|
186
|
+
(self.db = params[:objects][0]) if params && params[:objects] && params[:objects][0] && params[:objects][0].is_a?(Rfm::Database)
|
187
|
+
end
|
166
188
|
end
|
167
189
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
# Do not get or set instance variables in Layout from other objects directly,
|
173
|
-
# always use getter & setter methods.
|
174
|
-
# This all means that any chain of methods that want to refer ultimately to Sublayout, must all be defined or included in Sublayout
|
175
|
-
module LayoutModule
|
190
|
+
alias_method :db_orig, :db
|
191
|
+
def db
|
192
|
+
db_orig || (self.db = Rfm::Database.new(state[:database], state[:account_name], state[:password], self))
|
193
|
+
end
|
176
194
|
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
# Returns a ResultSet containing a single random record from the table associated with this layout.
|
183
|
-
def any(options = {})
|
184
|
-
get_records('-findany', {}, options)
|
185
|
-
end
|
186
|
-
|
187
|
-
# Finds a record. Typically you will pass in a hash of field names and values. For example:
|
188
|
-
#
|
189
|
-
# myLayout.find({"First Name" => "Bill"})
|
190
|
-
#
|
191
|
-
# Values in the hash work just like value in FileMaker's Find mode. You can use any special
|
192
|
-
# symbols (+==+, +...+, +>+, etc...).
|
193
|
-
#
|
194
|
-
# Create a Filemaker 'omit' request by including an :omit key with a value of true.
|
195
|
-
#
|
196
|
-
# myLayout.find :field1 => 'val1', :field2 => 'val2', :omit => true
|
197
|
-
#
|
198
|
-
# Create multiple Filemaker find requests by passing an array of hashes to the #find method.
|
199
|
-
#
|
200
|
-
# myLayout.find [{:field1 => 'bill', :field2 => 'admin'}, {:field3 => 'inactive', :omit => true}, ...]
|
201
|
-
#
|
202
|
-
# If the value of a field in a find request is an array of strings, the string values will be logically OR'd in the query.
|
203
|
-
#
|
204
|
-
# myLayout.find :fieldOne => ['bill','mike','bob'], :fieldTwo =>'staff'
|
205
|
-
#
|
206
|
-
# If you pass anything other than a hash or an array as the first parameter, it is converted to a string and
|
207
|
-
# assumed to be FileMaker's internal id for a record (the recid).
|
208
|
-
#
|
209
|
-
# myLayout.find 54321
|
210
|
-
#
|
211
|
-
def find(find_criteria, options = {})
|
212
|
-
#puts "layout.find-#{self.object_id}"
|
213
|
-
options.merge!({:field_mapping => field_mapping.invert}) if field_mapping
|
214
|
-
get_records(*Rfm::CompoundQuery.new(find_criteria, options))
|
215
|
-
end
|
216
|
-
|
217
|
-
# Access to raw -findquery command.
|
218
|
-
def query(query_hash, options = {})
|
219
|
-
get_records('-findquery', query_hash, options)
|
220
|
-
end
|
221
|
-
|
222
|
-
# Updates the contents of the record whose internal +recid+ is specified. Send in a hash of new
|
223
|
-
# data in the +values+ parameter. Returns a RecordSet containing the modified record. For example:
|
224
|
-
#
|
225
|
-
# recid = myLayout.find({"First Name" => "Bill"})[0].record_id
|
226
|
-
# myLayout.edit(recid, {"First Name" => "Steve"})
|
227
|
-
#
|
228
|
-
# The above code would find the first record with _Bill_ in the First Name field and change the
|
229
|
-
# first name to _Steve_.
|
230
|
-
def edit(recid, values, options = {})
|
231
|
-
get_records('-edit', {'-recid' => recid}.merge(values), options)
|
232
|
-
#get_records('-edit', {'-recid' => recid}.merge(expand_repeats(values)), options) # attempt to set repeating fields.
|
233
|
-
end
|
234
|
-
|
235
|
-
# Creates a new record in the table associated with this layout. Pass field data as a hash in the
|
236
|
-
# +values+ parameter. Returns the newly created record in a RecordSet. You can use the returned
|
237
|
-
# record to, ie, discover the values in auto-enter fields (like serial numbers).
|
238
|
-
#
|
239
|
-
# For example:
|
240
|
-
#
|
241
|
-
# result = myLayout.create({"First Name" => "Jerry", "Last Name" => "Robin"})
|
242
|
-
# id = result[0]["ID"]
|
243
|
-
#
|
244
|
-
# The above code adds a new record with first name _Jerry_ and last name _Robin_. It then
|
245
|
-
# puts the value from the ID field (a serial number) into a ruby variable called +id+.
|
246
|
-
def create(values, options = {})
|
247
|
-
get_records('-new', values, options)
|
248
|
-
end
|
249
|
-
|
250
|
-
# Deletes the record with the specified internal recid. Returns a ResultSet with the deleted record.
|
251
|
-
#
|
252
|
-
# For example:
|
253
|
-
#
|
254
|
-
# recid = myLayout.find({"First Name" => "Bill"})[0].record_id
|
255
|
-
# myLayout.delete(recid)
|
256
|
-
#
|
257
|
-
# The above code finds every record with _Bill_ in the First Name field, then deletes the first one.
|
258
|
-
def delete(recid, options = {})
|
259
|
-
get_records('-delete', {'-recid' => recid}, options)
|
260
|
-
return nil
|
261
|
-
end
|
262
|
-
|
263
|
-
# Retrieves metadata only, with an empty resultset.
|
264
|
-
def view(options = {})
|
265
|
-
get_records('-view', {}, options)
|
266
|
-
end
|
267
|
-
|
268
|
-
def get_records(action, extra_params = {}, options = {})
|
269
|
-
# TODO: The grammar stuff here won't work properly until you handle config between
|
270
|
-
# models/sublayouts/layout/server (Is this done now?).
|
271
|
-
grammar_option = state(options)[:grammar]
|
272
|
-
options.merge!(:grammar=>grammar_option) if grammar_option
|
273
|
-
#include_portals = options[:include_portals] ? options.delete(:include_portals) : nil
|
274
|
-
include_portals = !options[:ignore_portals]
|
275
|
-
|
276
|
-
# Apply mapping from :field_mapping, to send correct params in URL.
|
277
|
-
prms = params.merge(extra_params)
|
278
|
-
map = field_mapping.invert
|
279
|
-
options.merge!({:field_mapping => map}) if map
|
280
|
-
# TODO: Make this part handle string AND symbol keys.
|
281
|
-
#map.each{|k,v| prms[k]=prms.delete(v) if prms[v]}
|
282
|
-
prms.dup.each_key{|k| prms[map[k.to_s]]=prms.delete(k) if map[k.to_s]}
|
283
|
-
|
284
|
-
xml_response = server.connect(state[:account_name], state[:password], action, prms, options).body
|
285
|
-
Rfm::Resultset.new(xml_response, self, include_portals)
|
286
|
-
end
|
287
|
-
|
288
|
-
def params
|
289
|
-
{"-db" => db.name, "-lay" => self.name}
|
290
|
-
end
|
291
|
-
|
292
|
-
|
293
|
-
# Intended to set each repeat individually but doesn't work with FM
|
294
|
-
def expand_repeats(hash)
|
295
|
-
hash.each do |key,val|
|
296
|
-
if val.kind_of? Array
|
297
|
-
val.each_with_index{|v, i| hash["#{key}(#{i+1})"] = v}
|
298
|
-
hash.delete(key)
|
299
|
-
end
|
300
|
-
end
|
301
|
-
hash
|
302
|
-
end
|
303
|
-
|
304
|
-
# Intended to brute-force repeat setting but doesn't work with FM
|
305
|
-
def join_repeats(hash)
|
306
|
-
hash.each do |key,val|
|
307
|
-
if val.kind_of? Array
|
308
|
-
hash[key] = val.join('\x1D')
|
309
|
-
end
|
310
|
-
end
|
311
|
-
hash
|
312
|
-
end
|
313
|
-
|
314
|
-
def name; state[:layout].to_s; end
|
315
|
-
|
316
|
-
def state(*args)
|
317
|
-
get_config(*args)
|
318
|
-
end
|
319
|
-
|
320
|
-
end # LayoutModule
|
321
|
-
|
322
|
-
include LayoutModule
|
323
|
-
|
324
|
-
|
325
|
-
###
|
326
|
-
def view_meta
|
327
|
-
@view_meta ||= view
|
328
|
-
end
|
329
|
-
def date_format
|
330
|
-
@date_format ||= view_meta.date_format
|
331
|
-
end
|
332
|
-
def time_format
|
333
|
-
@time_format ||= view_meta.time_format
|
334
|
-
end
|
335
|
-
def timestamp_format
|
336
|
-
@timestamp_format ||= view_meta.timestamp_format
|
337
|
-
end
|
338
|
-
def field_meta
|
339
|
-
@field_meta ||= view_meta.field_meta
|
340
|
-
end
|
341
|
-
###
|
195
|
+
# Returns a ResultSet object containing _every record_ in the table associated with this layout.
|
196
|
+
def all(options = {})
|
197
|
+
get_records('-findall', {}, options)
|
198
|
+
end
|
342
199
|
|
200
|
+
# Returns a ResultSet containing a single random record from the table associated with this layout.
|
201
|
+
def any(options = {})
|
202
|
+
get_records('-findany', {}, options)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Finds a record. Typically you will pass in a hash of field names and values. For example:
|
206
|
+
#
|
207
|
+
# myLayout.find({"First Name" => "Bill"})
|
208
|
+
#
|
209
|
+
# Values in the hash work just like value in FileMaker's Find mode. You can use any special
|
210
|
+
# symbols (+==+, +...+, +>+, etc...).
|
211
|
+
#
|
212
|
+
# Create a Filemaker 'omit' request by including an :omit key with a value of true.
|
213
|
+
#
|
214
|
+
# myLayout.find :field1 => 'val1', :field2 => 'val2', :omit => true
|
215
|
+
#
|
216
|
+
# Create multiple Filemaker find requests by passing an array of hashes to the #find method.
|
217
|
+
#
|
218
|
+
# myLayout.find [{:field1 => 'bill', :field2 => 'admin'}, {:field3 => 'inactive', :omit => true}, ...]
|
219
|
+
#
|
220
|
+
# If the value of a field in a find request is an array of strings, the string values will be logically OR'd in the query.
|
221
|
+
#
|
222
|
+
# myLayout.find :fieldOne => ['bill','mike','bob'], :fieldTwo =>'staff'
|
223
|
+
#
|
224
|
+
# If you pass anything other than a hash or an array as the first parameter, it is converted to a string and
|
225
|
+
# assumed to be FileMaker's internal id for a record (the recid).
|
226
|
+
#
|
227
|
+
# myLayout.find 54321
|
228
|
+
#
|
229
|
+
def find(find_criteria, options = {})
|
230
|
+
#puts "layout.find-#{self.object_id}"
|
231
|
+
options.merge!({:field_mapping => field_mapping.invert}) if field_mapping
|
232
|
+
get_records(*Rfm::CompoundQuery.new(find_criteria, options))
|
233
|
+
end
|
234
|
+
|
235
|
+
# Access to raw -findquery command.
|
236
|
+
def query(query_hash, options = {})
|
237
|
+
get_records('-findquery', query_hash, options)
|
238
|
+
end
|
239
|
+
|
240
|
+
# Updates the contents of the record whose internal +recid+ is specified. Send in a hash of new
|
241
|
+
# data in the +values+ parameter. Returns a RecordSet containing the modified record. For example:
|
242
|
+
#
|
243
|
+
# recid = myLayout.find({"First Name" => "Bill"})[0].record_id
|
244
|
+
# myLayout.edit(recid, {"First Name" => "Steve"})
|
245
|
+
#
|
246
|
+
# The above code would find the first record with _Bill_ in the First Name field and change the
|
247
|
+
# first name to _Steve_.
|
248
|
+
def edit(recid, values, options = {})
|
249
|
+
get_records('-edit', {'-recid' => recid}.merge(values), options)
|
250
|
+
#get_records('-edit', {'-recid' => recid}.merge(expand_repeats(values)), options) # attempt to set repeating fields.
|
251
|
+
end
|
343
252
|
|
344
|
-
|
345
|
-
|
346
|
-
|
253
|
+
# Creates a new record in the table associated with this layout. Pass field data as a hash in the
|
254
|
+
# +values+ parameter. Returns the newly created record in a RecordSet. You can use the returned
|
255
|
+
# record to, ie, discover the values in auto-enter fields (like serial numbers).
|
256
|
+
#
|
257
|
+
# For example:
|
258
|
+
#
|
259
|
+
# result = myLayout.create({"First Name" => "Jerry", "Last Name" => "Robin"})
|
260
|
+
# id = result[0]["ID"]
|
261
|
+
#
|
262
|
+
# The above code adds a new record with first name _Jerry_ and last name _Robin_. It then
|
263
|
+
# puts the value from the ID field (a serial number) into a ruby variable called +id+.
|
264
|
+
def create(values, options = {})
|
265
|
+
get_records('-new', values, options)
|
347
266
|
end
|
348
267
|
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
268
|
+
# Deletes the record with the specified internal recid. Returns a ResultSet with the deleted record.
|
269
|
+
#
|
270
|
+
# For example:
|
271
|
+
#
|
272
|
+
# recid = myLayout.find({"First Name" => "Bill"})[0].record_id
|
273
|
+
# myLayout.delete(recid)
|
274
|
+
#
|
275
|
+
# The above code finds every record with _Bill_ in the First Name field, then deletes the first one.
|
276
|
+
def delete(recid, options = {})
|
277
|
+
get_records('-delete', {'-recid' => recid}, options)
|
278
|
+
return nil
|
279
|
+
end
|
357
280
|
|
358
|
-
|
359
|
-
|
360
|
-
|
281
|
+
# Retrieves metadata only, with an empty resultset.
|
282
|
+
def view(options = {})
|
283
|
+
get_records('-view', {}, options)
|
361
284
|
end
|
362
285
|
|
286
|
+
# Get the foundset_count only given criteria & options.
|
363
287
|
def count(find_criteria, options={})
|
364
288
|
find(find_criteria, options.merge({:max_records => 0})).foundset_count
|
365
289
|
end
|
366
290
|
|
367
|
-
|
368
|
-
|
291
|
+
def get_records(action, extra_params = {}, options = {})
|
292
|
+
# TODO: The grammar stuff here won't work properly until you handle config between
|
293
|
+
# models/sublayouts/layout/server (Is this done now?).
|
294
|
+
grammar_option = state(options)[:grammar]
|
295
|
+
options.merge!(:grammar=>grammar_option) if grammar_option
|
296
|
+
template = options.delete :template
|
297
|
+
|
298
|
+
# # TODO: Remove this code it is no longer used.
|
299
|
+
# #include_portals = options[:include_portals] ? options.delete(:include_portals) : nil
|
300
|
+
# include_portals = !options[:ignore_portals]
|
301
|
+
|
302
|
+
# Apply mapping from :field_mapping, to send correct params in URL.
|
303
|
+
prms = params.merge(extra_params)
|
304
|
+
map = field_mapping.invert
|
305
|
+
options.merge!({:field_mapping => map}) if map && !map.empty?
|
306
|
+
# TODO: Make this part handle string AND symbol keys.
|
307
|
+
#map.each{|k,v| prms[k]=prms.delete(v) if prms[v]}
|
308
|
+
prms.dup.each_key{|k| prms[map[k.to_s]]=prms.delete(k) if map[k.to_s]}
|
309
|
+
|
310
|
+
#c = Connection.new(action, prms, options, state.merge(:parent=>self))
|
311
|
+
c = Connection.new(action, prms, options, self)
|
312
|
+
rslt = c.parse(template || :fmresultset, Rfm::Resultset.new(self, self))
|
313
|
+
capture_resultset_meta(rslt) unless @resultset_meta
|
314
|
+
rslt
|
315
|
+
end
|
316
|
+
|
317
|
+
def params
|
318
|
+
{"-db" => state[:database], "-lay" => self.name}
|
319
|
+
end
|
320
|
+
|
321
|
+
def name; state[:layout].to_s; end
|
322
|
+
|
323
|
+
|
324
|
+
|
325
|
+
|
326
|
+
### Metadata from Layout ###
|
327
|
+
|
328
|
+
def meta
|
329
|
+
@loaded ? @meta : load_layout
|
330
|
+
end
|
331
|
+
|
332
|
+
def field_names
|
333
|
+
case
|
334
|
+
when @loaded; meta.field_names
|
335
|
+
when @resultset_meta; resultset_meta.field_names
|
336
|
+
else meta.field_names
|
337
|
+
end
|
369
338
|
end
|
370
339
|
|
371
|
-
def
|
372
|
-
|
340
|
+
def field_keys
|
341
|
+
case
|
342
|
+
when @loaded; @meta.field_keys
|
343
|
+
when @resultset_meta; @resultset_meta.field_keys
|
344
|
+
else meta.field_keys
|
345
|
+
end
|
373
346
|
end
|
374
347
|
|
375
|
-
def portal_meta_no_load
|
376
|
-
@portal_meta
|
377
|
-
end
|
378
348
|
|
349
|
+
|
350
|
+
|
351
|
+
### Metadata from Resultset ###
|
352
|
+
|
353
|
+
def resultset_meta
|
354
|
+
@resultset_meta || view.meta
|
355
|
+
end
|
356
|
+
|
357
|
+
# Should always refresh
|
358
|
+
def total_count
|
359
|
+
view.total_count
|
360
|
+
end
|
361
|
+
|
362
|
+
def capture_resultset_meta(resultset)
|
363
|
+
(@resultset_meta = resultset.clone.replace([])) #unless @resultset_meta
|
364
|
+
@resultset_meta = resultset.meta
|
365
|
+
end
|
366
|
+
|
379
367
|
def portal_names
|
380
|
-
|
368
|
+
return 'UNDER-CONTSTRUCTION'
|
381
369
|
end
|
370
|
+
|
382
371
|
|
383
|
-
def table
|
384
|
-
@table ||= view.table
|
385
|
-
end
|
386
372
|
|
387
|
-
|
388
|
-
|
389
|
-
end
|
373
|
+
|
374
|
+
### Utility ###
|
390
375
|
|
391
|
-
def
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
raise Rfm::Error::FileMakerError.getError(error) if error != 0
|
399
|
-
|
400
|
-
# process valuelists
|
401
|
-
vlists = [doc['FMPXMLLAYOUT']['VALUELISTS']['VALUELIST']].flatten.compact
|
402
|
-
if !vlists.blank? #root.elements['VALUELISTS'].size > 0
|
403
|
-
vlists.each {|valuelist|
|
404
|
-
name = valuelist['NAME']
|
405
|
-
@value_lists[name] = [valuelist['VALUE']].flatten.collect{|value|
|
406
|
-
Rfm::Metadata::ValueListItem.new(value['__content__'], value['DISPLAY'], name)
|
407
|
-
} rescue []
|
408
|
-
}
|
409
|
-
@value_lists.freeze
|
410
|
-
end
|
411
|
-
|
412
|
-
# process field controls
|
413
|
-
[doc['FMPXMLLAYOUT']['LAYOUT']['FIELD']].flatten.compact.each {|field|
|
414
|
-
name = field_mapping[field['NAME']] || field['NAME']
|
415
|
-
style = field['STYLE']
|
416
|
-
type = style['TYPE']
|
417
|
-
value_list_name = style['VALUELIST']
|
418
|
-
value_list = @value_lists[value_list_name] if !value_list_name.blank?
|
419
|
-
field_control = Rfm::Metadata::FieldControl.new(name, type, value_list_name, value_list)
|
420
|
-
existing = @field_controls[name]
|
421
|
-
if existing
|
422
|
-
if existing.kind_of?(Array)
|
423
|
-
existing << field_control
|
424
|
-
else
|
425
|
-
@field_controls[name] = Array[existing, field_control]
|
426
|
-
end
|
427
|
-
else
|
428
|
-
@field_controls[name] = field_control
|
429
|
-
end
|
430
|
-
}
|
431
|
-
@field_names ||= @field_controls.collect{|k,v| v.name rescue v[0].name}
|
432
|
-
@field_controls.freeze
|
376
|
+
def load_layout
|
377
|
+
@loaded = true # This is first so parsing call to 'meta' wont cause infinite loop.
|
378
|
+
connection = Connection.new('-view', {'-db' => state[:database], '-lay' => name}, {:grammar=>'FMPXMLLAYOUT'}, self)
|
379
|
+
rslt = connection.parse(:fmpxmllayout, self)
|
380
|
+
#puts ["LAYOUT#load_layout result", rslt.class].join(', ')
|
381
|
+
# @loaded = true
|
382
|
+
@meta
|
433
383
|
end
|
434
384
|
|
435
385
|
def field_mapping
|
436
|
-
@field_mapping ||= load_field_mapping(
|
386
|
+
@field_mapping ||= load_field_mapping(state[:field_mapping])
|
437
387
|
end
|
438
388
|
|
439
389
|
def load_field_mapping(mapping={})
|
@@ -443,8 +393,31 @@ module Rfm
|
|
443
393
|
end
|
444
394
|
mapping
|
445
395
|
end
|
396
|
+
|
397
|
+
# Creates new class with layout name.
|
398
|
+
def modelize
|
399
|
+
@model ||= (
|
400
|
+
model_name = name.to_s.gsub(/\W|_/, ' ').title_case.gsub(/\s/,'')
|
401
|
+
#model_class = eval("::" + model_name + "= Class.new(Rfm::Base)")
|
402
|
+
model_class = Rfm.const_defined?(model_name) ? Rfm.const_get(model_name) : Rfm.const_set(model_name, Class.new(Rfm::Base))
|
403
|
+
model_class.class_exec(self) do |layout_obj|
|
404
|
+
@layout = layout_obj
|
405
|
+
end
|
406
|
+
model_class.config :parent=>'@layout'
|
407
|
+
model_class
|
408
|
+
)
|
409
|
+
# rescue StandardError, SyntaxError
|
410
|
+
# puts "Error in layout#modelize: #{$!}"
|
411
|
+
# nil
|
412
|
+
end
|
413
|
+
|
414
|
+
def models
|
415
|
+
#subs.collect{|s| s.model}
|
416
|
+
[@model]
|
417
|
+
end
|
418
|
+
|
446
419
|
|
447
|
-
private :
|
420
|
+
private :load_layout, :get_records, :params
|
448
421
|
|
449
422
|
|
450
423
|
end # Layout
|