ginjo-rfm 2.1.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,45 @@
1
+ require 'delegate'
2
+ module Rfm
3
+ module Metadata
4
+
5
+ class Datum #< DelegateClass(Field)
6
+
7
+ def get_mapped_name(name, resultset)
8
+ (resultset && resultset.layout && resultset.layout.field_mapping[name]) || name
9
+ end
10
+
11
+ def main_callback(cursor)
12
+ resultset = cursor.top.object
13
+ name = get_mapped_name(@attributes['name'].to_s, resultset)
14
+ field = resultset.field_meta[name]
15
+ data = @attributes['data']
16
+ cursor.parent.object[name.downcase] = field.coerce(data)
17
+ end
18
+
19
+ def portal_callback(cursor)
20
+ resultset = cursor.top.object
21
+ table, name = @attributes['name'].to_s.split('::')
22
+ #puts ['DATUM_portal_callback_01', table, name].join(', ')
23
+ name = get_mapped_name(name, resultset)
24
+ field = resultset.portal_meta[table.downcase][name.downcase]
25
+ data = @attributes['data']
26
+ #puts ['DATUM_portal_callback_02', resultset.class, table, name, field, data].join(', ')
27
+ #(y resultset.portal_meta) unless field
28
+ cursor.parent.object[name.downcase] = field.coerce(data)
29
+ end
30
+
31
+ # Should return value only.
32
+ def handler_callback(cursor)
33
+ record = cursor.parent.object
34
+ resultset = cursor.top.object
35
+
36
+ name = get_mapped_name(@attributes['name'].to_s, resultset)
37
+ field = resultset.field_meta[name]
38
+ data = @attributes['data']
39
+ #puts ["\nDATUM", name, record.class, resultset.class, data]
40
+ record[name] = field.coerce(data)
41
+ end
42
+
43
+ end # Field
44
+ end # Metadata
45
+ end # Rfm
@@ -60,33 +60,53 @@ module Rfm
60
60
  class Field
61
61
 
62
62
  attr_reader :name, :result, :type, :max_repeats, :global
63
-
63
+ meta_attr_accessor :resultset
64
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, options={})
67
- @name = options[:field_mapping][field.name] || field.name rescue field.name #['name']
68
- @result = field.result #['result']
69
- @type = field.type #['type']
70
- @max_repeats = field.max_repeats #['max-repeats']
71
- @global = field.global #['global']
72
- end
65
+ # Resultset::field_meta
66
+ def initialize(attributes)
67
+ _attach_as_instance_variables attributes
68
+ self
69
+ end
73
70
 
74
71
  # Coerces the text value from an +fmresultset+ document into proper Ruby types based on the
75
72
  # type of the field. You'll never need to do this: Rfm does it automatically for you when you
76
73
  # access field data through the Record object.
77
- def coerce(value, resultset)
74
+ def coerce(value)
78
75
  return nil if (value.nil? or value.empty?)
79
- case self.result.downcase
76
+ case result
80
77
  when "text" then value
81
78
  when "number" then BigDecimal.new(value.to_s)
82
79
  when "date" then Date.strptime(value, resultset.date_format)
83
80
  when "time" then DateTime.strptime("1/1/-4712 #{value}", "%m/%d/%Y #{resultset.time_format}")
84
81
  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}")
82
+ when "container" then
83
+ resultset_meta = resultset.instance_variable_get(:@meta)
84
+ if resultset_meta && resultset_meta['doctype'] && value.to_s[/\?/]
85
+ URI.parse(resultset_meta['doctype'].last.to_s).tap{|uri| uri.path, uri.query = value.split('?')}
86
+ else
87
+ value
88
+ end
86
89
  else nil
87
90
  end
88
-
91
+ # rescue
92
+ # puts("ERROR in Field#coerce:", name, value, result, $!)
93
+ # nil
94
+ end
95
+
96
+ def get_mapped_name
97
+ (resultset && resultset.layout && resultset.layout.field_mapping[name]) || name
89
98
  end
99
+
100
+ def main_callback(cursor)
101
+ self.resultset = cursor.top.object
102
+ resultset.field_meta[get_mapped_name.to_s.downcase] = self
103
+ end
104
+
105
+ def portal_callback(cursor)
106
+ self.resultset = cursor.top.object
107
+ cursor.parent.object[get_mapped_name.split('::').last.to_s.downcase] = self
108
+ #puts ['FIELD_portal_callback', name, cursor.parent.object.object_id, cursor.parent.tag, cursor.parent.object[name.split('::').last.to_s.downcase]].join(', ')
109
+ end
90
110
 
91
111
  end # Field
92
112
  end # Metadata
@@ -22,33 +22,65 @@ module Rfm
22
22
  # * *value_list* is an array of strings representing the value list items, or nil
23
23
  # if this field has no attached value list
24
24
  class FieldControl
25
- def initialize(name, style, value_list_name, value_list)
26
- @name = name
27
- case style
28
- when "EDITTEXT"
29
- @style = :edit_box
30
- when "POPUPMENU"
31
- @style = :popup_menu
32
- when "CHECKBOX"
33
- @style = :checkbox_set
34
- when "RADIOBUTTONS"
35
- @style = :radio_button_set
36
- when "POPUPLIST"
37
- @style = :popup_list
38
- when "CALENDAR"
39
- @style = :calendar
40
- when "SCROLLTEXT"
41
- @style = :scrollable
42
- else
43
- nil
44
- end
45
- @value_list_name = value_list_name
46
- rfm_metaclass.instance_variable_set :@value_list, value_list
47
- end
48
-
49
25
  attr_reader :name, :style, :value_list_name
50
- meta_attr_reader :value_list
26
+ meta_attr_accessor :layout_meta
51
27
 
28
+ def initialize(attributes, meta)
29
+ self.layout_meta = meta
30
+ _attach_as_instance_variables attributes
31
+ self
32
+ end
33
+
34
+ # Handle manual attachment of STYLE element.
35
+ def handle_style_element(attributes)
36
+ _attach_as_instance_variables attributes, :key_translator=>method(:translate_value_list_key), :value_translator=>method(:translate_style_value)
37
+ end
38
+
39
+ def translate_style_value(raw)
40
+ #puts ["TRANSLATE_STYLE", raw].join(', ')
41
+ {
42
+ 'EDITTEXT' => :edit_box,
43
+ 'POPUPMENU' => :popup_menu,
44
+ 'CHECKBOX' => :checkbox_set,
45
+ 'RADIOBUTTONS' => :radio_button_set,
46
+ 'POPUPLIST' => :popup_list,
47
+ 'CALENDAR' => :calendar,
48
+ 'SCROLLTEXT' => :scrollable,
49
+ }[raw] || raw
50
+ end
51
+
52
+ def translate_value_list_key(raw)
53
+ {'valuelist'=>'value_list_name'}[raw] || raw
54
+ end
55
+
56
+ def value_list
57
+ layout_meta.value_lists[value_list_name]
58
+ end
59
+
60
+ # def initialize(name, style, value_list_name, value_list)
61
+ # @name = name
62
+ # case style
63
+ # when "EDITTEXT"
64
+ # @style = :edit_box
65
+ # when "POPUPMENU"
66
+ # @style = :popup_menu
67
+ # when "CHECKBOX"
68
+ # @style = :checkbox_set
69
+ # when "RADIOBUTTONS"
70
+ # @style = :radio_button_set
71
+ # when "POPUPLIST"
72
+ # @style = :popup_list
73
+ # when "CALENDAR"
74
+ # @style = :calendar
75
+ # when "SCROLLTEXT"
76
+ # @style = :scrollable
77
+ # else
78
+ # nil
79
+ # end
80
+ # @value_list_name = value_list_name
81
+ # rfm_metaclass.instance_variable_set :@value_list, value_list
82
+ # end
83
+
52
84
  end
53
85
  end
54
86
  end
@@ -0,0 +1,38 @@
1
+ module Rfm
2
+ module Metadata
3
+ class LayoutMeta < CaseInsensitiveHash
4
+
5
+ def initialize(layout)
6
+ @layout = layout
7
+ end
8
+
9
+ def field_controls
10
+ self['field_controls'] ||= CaseInsensitiveHash.new
11
+ end
12
+
13
+ def field_names
14
+ field_controls.values.collect{|v| v.name}
15
+ end
16
+
17
+ def field_keys
18
+ field_controls.keys
19
+ end
20
+
21
+ def value_lists
22
+ self['value_lists'] ||= CaseInsensitiveHash.new
23
+ end
24
+
25
+ def handle_new_field_control(attributes)
26
+ name = attributes['name']
27
+ field_control = FieldControl.new(attributes, self)
28
+ field_controls[get_mapped_name(name)] = field_control
29
+ end
30
+
31
+ # Should this be in FieldControl object?
32
+ def get_mapped_name(name)
33
+ (@layout.field_mapping[name]) || name
34
+ end
35
+
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,66 @@
1
+ module Rfm
2
+ module Metadata
3
+ class ResultsetMeta < CaseInsensitiveHash
4
+
5
+ def field_meta
6
+ self['field_meta'] ||= CaseInsensitiveHash.new
7
+ end
8
+
9
+ def portal_meta
10
+ self['portal_meta'] ||= CaseInsensitiveHash.new
11
+ end
12
+
13
+ def date_format
14
+ self['date_format']
15
+ end
16
+
17
+ def time_format
18
+ self['time_format']
19
+ end
20
+
21
+ def timestamp_format
22
+ self['timestamp_format']
23
+ end
24
+
25
+ def total_count
26
+ self['total_count'].to_i
27
+ end
28
+
29
+ def foundset_count
30
+ self['count'].to_i
31
+ end
32
+
33
+ def fetch_size
34
+ self['fetch_size'].to_i
35
+ end
36
+
37
+ def table
38
+ self['table']
39
+ end
40
+
41
+ def error
42
+ self['error']
43
+ end
44
+
45
+ def field_names
46
+ field_meta ? field_meta.values.collect{|v| v.name} : []
47
+ end
48
+
49
+ def field_keys
50
+ field_meta ? field_meta.keys : []
51
+ end
52
+
53
+ def portal_names
54
+ portal_meta ? portal_meta.keys : []
55
+ end
56
+
57
+ def handle_new_field(attributes)
58
+ f = Field.new(attributes)
59
+ # TODO: Re-enable these when you stop using the before_close callback.
60
+ # name = attributes['name']
61
+ # self[name] = f
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -15,14 +15,15 @@ module Rfm
15
15
  #
16
16
  # * *#value_list_name* is the name of the parent value list, if any
17
17
  class ValueListItem < String
18
+ # TODO: re-instate saving of value_list_name.
18
19
  attr_reader :value, :display, :value_list_name
19
20
 
20
- def initialize(value, display, value_list_name)
21
- @value_list_name = value_list_name
22
- @value = value.to_s
23
- @display = display.to_s
24
- self.replace @value
25
- end
21
+ # def initialize(value, display, value_list_name)
22
+ # @value_list_name = value_list_name
23
+ # @value = value.to_s
24
+ # @display = display.to_s
25
+ # self.replace @value
26
+ # end
26
27
 
27
28
  end # ValueListItem
28
29
 
@@ -108,68 +108,48 @@ module Rfm
108
108
  # copy of the same record
109
109
  class Record < Rfm::CaseInsensitiveHash
110
110
 
111
- attr_accessor :layout, :resultset
111
+ attr_accessor :layout #, :resultset
112
112
  attr_reader :record_id, :mod_id, :portals
113
- def_delegators :resultset, :field_meta, :portal_names
114
- def_delegators :layout, :db, :database, :server
115
-
116
- def initialize(record, resultset_obj, field_meta, layout_obj, portal=nil)
113
+ def_delegators :layout, :db, :database, :server, :field_meta, :portal_meta, :field_names, :portal_names
117
114
 
118
- @layout = layout_obj
119
- @resultset = resultset_obj
120
- @record_id = record.record_id rescue nil
121
- @mod_id = record.mod_id rescue nil
122
- @mods = {}
123
- @portals ||= Rfm::CaseInsensitiveHash.new
115
+ # This is called during the parsing process, but only to allow creation of the correct type of model instance.
116
+ # This is also called by the end-user when constructing a new empty record, but it is called from the model subclass.
117
+ def self.new(*args) # resultset
118
+ record = case
119
+
120
+ # Get model from layout, then allocate record.
121
+ when args[0].is_a?(Resultset) && args[0].layout
122
+ args[0].layout.modelize.allocate
123
+
124
+ # Allocate instance of Rfm::Record.
125
+ else
126
+ self.allocate
127
+ end
128
+ record.send(:initialize, *args)
129
+ record
130
+ # rescue
131
+ # puts "Record.new bombed and is defaulting to super.new. Error: #{$!}"
132
+ # super
133
+ end
124
134
 
125
- relatedsets = !portal && resultset_obj.instance_variable_get(:@include_portals) ? record.portals : []
126
-
127
- record.columns.each do |field|
128
- next unless field
129
- field_name = @layout.field_mapping[field.name] || field.name rescue field.name
130
- field_name.gsub!(Regexp.new(portal + '::'), '') if portal
131
- datum = []
132
- data = field.data #['data']; data = data.is_a?(Hash) ? [data] : data
133
- data.each do |x|
134
- next unless field_meta[field_name]
135
- begin
136
- datum.push(field_meta[field_name].coerce(x, resultset_obj)) #(x['__content__'], resultset_obj))
137
- rescue StandardError => error
138
- self.errors.add(field_name, error) if self.respond_to? :errors
139
- raise error unless @layout.ignore_bad_data
140
- end
141
- end if data
142
-
143
- if datum.length == 1
144
- rfm_super[field_name] = datum[0]
145
- elsif datum.length == 0
146
- rfm_super[field_name] = nil
147
- else
148
- rfm_super[field_name] = datum
135
+ def initialize(*args) # resultset, attributes
136
+ @mods ||= {}
137
+ @portals ||= Rfm::CaseInsensitiveHash.new
138
+ options = args.rfm_extract_options!
139
+ if args[0].is_a?(Resultset)
140
+ @layout = args[0].layout
141
+ elsif self.is_a?(Base)
142
+ @layout = self.class.layout
143
+ @layout.field_keys.each do |field|
144
+ self[field] = nil
149
145
  end
146
+ self.update_attributes(options) unless options == {}
147
+ self.merge!(@mods) unless @mods == {}
148
+ @loaded = true
150
149
  end
151
-
152
- unless relatedsets.empty?
153
- relatedsets.each do |relatedset|
154
- next if relatedset.blank?
155
- tablename, records = relatedset.table, []
156
-
157
- relatedset.records.each do |record|
158
- next unless record
159
- records << self.class.new(record, resultset_obj, resultset_obj.portal_meta[tablename], layout_obj, tablename)
160
- end
161
-
162
- @portals[tablename] = records
163
- end
164
- end
165
-
166
- @loaded = true
167
- end
168
-
169
- def self.build_records(records, resultset_obj, field_meta, layout_obj, portal=nil)
170
- records.each do |record|
171
- resultset_obj << self.new(record, resultset_obj, field_meta, layout_obj, portal)
172
- end
150
+ _attach_as_instance_variables args[1]
151
+ #@loaded = true
152
+ self
173
153
  end
174
154
 
175
155
  # Saves local changes to the Record object back to Filemaker. For example:
@@ -186,7 +166,8 @@ module Rfm
186
166
  # to optimize on your end. Just save, and if you've changed the record it will be saved. If not, no
187
167
  # server hit is incurred.
188
168
  def save
189
- self.merge!(layout.edit(self.record_id, @mods)[0]) if @mods.size > 0
169
+ # self.merge!(layout.edit(self.record_id, @mods)[0]) if @mods.size > 0
170
+ self.replace_with_fresh_data(layout.edit(self.record_id, @mods)[0]) if @mods.size > 0
190
171
  @mods.clear
191
172
  end
192
173
 
@@ -194,7 +175,8 @@ module Rfm
194
175
  # modified after the record was fetched but before it was saved. In other words, prevents you from
195
176
  # accidentally overwriting changes someone else made to the record.
196
177
  def save_if_not_modified
197
- self.merge!(layout.edit(@record_id, @mods, {:modification_id => @mod_id})[0]) if @mods.size > 0
178
+ # self.merge!(layout.edit(@record_id, @mods, {:modification_id => @mod_id})[0]) if @mods.size > 0
179
+ self.replace_with_fresh_data(layout.edit(@record_id, @mods, {:modification_id => @mod_id})[0]) if @mods.size > 0
198
180
  @mods.clear
199
181
  end
200
182
 
@@ -212,6 +194,8 @@ module Rfm
212
194
  # When you do, the change is noted, but *the data is not updated in FileMaker*. You must call
213
195
  # Record::save or Record::save_if_not_modified to actually save the data.
214
196
  def [](key)
197
+ # Added by wbr, 2013-03-31
198
+ return super unless @loaded
215
199
  return fetch(key.to_s.downcase)
216
200
  rescue IndexError
217
201
  raise Rfm::ParameterError, "#{key} does not exists as a field in the current Filemaker layout." unless key.to_s == '' #unless (!layout or self.key?(key_string))
@@ -241,24 +225,20 @@ module Rfm
241
225
  end
242
226
  super(key, val)
243
227
  end
244
- #
245
- # alias_method :old_setter, '[]='
246
- # def []=(key,val)
247
- # old_setter(key,val)
248
- # return val unless [Date, Time, DateTime].member? val.class
249
- # field_type = layout.field_meta[key.to_sym].result
250
- # @mods[key] = case field_type
251
- # when 'time'; val.strftime(layout.time_format)
252
- # when 'date'; val.strftime(layout.date_format)
253
- # when 'timestamp'; val.strftime(layout.timestamp_format)
254
- # else val
255
- # end
256
- # end
257
228
 
258
229
  def field_names
259
- resultset.field_names rescue layout.field_names
230
+ layout.field_names
260
231
  end
261
-
232
+
233
+ def replace_with_fresh_data(record)
234
+ self.replace record
235
+ [:@mod_id, :@record_id, :@portals, :@mods].each do |var|
236
+ self.instance_variable_set var, record.instance_variable_get(var) || {}
237
+ end
238
+ self
239
+ end
240
+
241
+
262
242
 
263
243
  private
264
244