ginjo-rfm 1.4.4 → 2.0.pre31

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.
data/lib/rfm.rb CHANGED
@@ -3,34 +3,66 @@ module Rfm
3
3
  $LOAD_PATH.unshift(PATH) unless $LOAD_PATH.include?(PATH)
4
4
  end
5
5
 
6
+ require 'thread' # some versions of ActiveSupport will raise error about Mutex unless 'thread' is loaded.
7
+ require 'active_support'
8
+ # ActiveSupport appears to load these automatcially
9
+ #require 'active_support/core_ext/object/blank'
10
+ #require 'active_support/ordered_hash'
11
+ require 'active_support/version'
12
+ require 'rfm/utilities/core_ext'
6
13
  require 'rfm/utilities/case_insensitive_hash'
7
- require 'rfm/utilities/factory'
8
- require 'rfm/version.rb'
9
-
14
+ #require 'rfm/utilities/config'
15
+ #require 'rfm/utilities/factory'
16
+ #require 'rfm/version.rb'
10
17
 
11
18
  module Rfm
12
-
13
- if $0.to_s.match(/irb|rails|bundle/) # was ENV['_']
14
- puts "Using gem ginjo-rfm version: #{::Rfm::VERSION}"
15
- end
16
19
 
17
20
  class CommunicationError < StandardError; end
18
21
  class ParameterError < StandardError; end
19
22
  class AuthenticationError < StandardError; end
20
23
 
21
- autoload :Error, 'rfm/error'
22
- autoload :Server, 'rfm/server'
23
- autoload :Database, 'rfm/database'
24
- autoload :Layout, 'rfm/layout'
25
- autoload :Resultset, 'rfm/resultset'
26
- autoload :Record, 'rfm/record'
24
+ autoload :Error, 'rfm/error'
25
+ autoload :Server, 'rfm/server'
26
+ autoload :Database, 'rfm/database'
27
+ autoload :Layout, 'rfm/layout'
28
+ autoload :Resultset, 'rfm/resultset'
29
+ autoload :Record, 'rfm/record'
30
+ autoload :Base, 'rfm/base'
31
+ autoload :XmlParser, 'rfm/utilities/xml_parser'
32
+ autoload :ComplexQuery, 'rfm/utilities/complex_query'
33
+ autoload :Config, 'rfm/utilities/config'
34
+ autoload :Factory, 'rfm/utilities/factory'
35
+ autoload :VERSION, 'rfm/version'
27
36
 
28
37
  module Metadata
29
- autoload :Script, 'rfm/metadata/script'
30
- autoload :Field, 'rfm/metadata/field'
31
- autoload :FieldControl, 'rfm/metadata/field_control'
32
- autoload :ValueListItem, 'rfm/metadata/value_list_item'
38
+ autoload :Script, 'rfm/metadata/script'
39
+ autoload :Field, 'rfm/metadata/field'
40
+ autoload :FieldControl, 'rfm/metadata/field_control'
41
+ autoload :ValueListItem, 'rfm/metadata/value_list_item'
33
42
  end
34
-
35
- end
36
43
 
44
+ def info
45
+ rslt = <<-EEOOFF
46
+ Name: ginjo-rfm
47
+ Version: #{VERSION}
48
+ ActiveSupport Version: #{ActiveSupport::VERSION::STRING}
49
+ ActiveModel Loaded? #{defined?(ActiveModel) ? 'true' : 'false'}
50
+ ActiveModel Loadable? #{begin; require 'active_model'; rescue LoadError; $!; end}
51
+ XML Parser: #{XmlParser.backend}
52
+ EEOOFF
53
+ rslt.gsub!(/^[ \t]*/, '')
54
+ rescue
55
+ "Could not retrieve info: #{$!}"
56
+ end
57
+
58
+ def info_short
59
+ "Using ginjo-rfm version #{::Rfm::VERSION} with #{XmlParser.backend}"
60
+ end
61
+
62
+ def_delegators 'Rfm::Factory', :servers, :server, :db, :database, :layout, :models, :modelize
63
+ def_delegators 'Rfm::XmlParser', :backend, :backend=
64
+ def_delegators 'Rfm::Config', :config, :get_config, :config_clear
65
+
66
+ extend self
67
+
68
+ end # Rfm
@@ -1 +1 @@
1
- 1.4.4
1
+ 2.0.pre31
@@ -0,0 +1,416 @@
1
+ module Rfm
2
+ #
3
+ # Adds ability to create Rfm::Base model classes that behave similar to ActiveRecord::Base models.
4
+ # If you set your Rfm.config (or RFM_CONFIG) with your host, database, account, password, and
5
+ # any other server/database options, you can provide your models with nothing more than a layout.
6
+ #
7
+ # class Person < Rfm::Base
8
+ # config :layout => 'mylayout'
9
+ # end
10
+ #
11
+ # And similar to ActiveRecord, you can define callbacks, validations, attributes, and methods on your model.
12
+ #
13
+ # class Account < Rfm::Base
14
+ # config :layout=>'account_xml'
15
+ # before_create :encrypt_password
16
+ # validates :email, :presence => true
17
+ # validates :username, :presence => true
18
+ # attr_accessor :password
19
+ # end
20
+ #
21
+ # Then in your project, you can use these models just like ActiveRecord models.
22
+ # The query syntax and options are still Rfm under the hood. Treat your model
23
+ # classes like Rfm::Layout objects, with a few enhancements.
24
+ #
25
+ # @account = Account.new :username => 'bill', :password => 'pass'
26
+ # @account.email = 'my@email.com'
27
+ # @account.save!
28
+ #
29
+ # @person = Person.find({:name => 'mike'}, :max_records => 50)[0]
30
+ # @person.update_attributes(:name => 'Michael', :title => "Senior Partner")
31
+ # @person.save
32
+ #
33
+ #
34
+ #
35
+ #
36
+ # :nodoc: TODO: make sure all methods return something (a record?) if successful, otherwise nil or error
37
+ # :nodoc: TODO: move rfm methods & patches from fetch_mail_with_ruby to this file
38
+ #
39
+ # TODO: Are these really needed here?
40
+ require 'rfm/database'
41
+ require 'rfm/layout'
42
+ require 'rfm/record'
43
+ require 'rfm/utilities/factory'
44
+ require 'delegate'
45
+
46
+
47
+
48
+ class Layout
49
+
50
+ class SubLayout < DelegateClass(Layout)
51
+ include Layout::LayoutModule
52
+ attr_accessor :model, :parent_layout
53
+
54
+ def initialize(master)
55
+ super(master)
56
+ self.parent_layout = master
57
+ end
58
+ end # SubLayout
59
+
60
+ attr_accessor :subs
61
+
62
+ alias_method :main_init, :initialize
63
+ def initialize(*args)
64
+ @subs ||= []
65
+ main_init(*args)
66
+ end
67
+
68
+ def sublayout
69
+ if self.is_a?(Rfm::Layout)
70
+ sub = SubLayout.new(self); subs << sub; sub
71
+ else
72
+ self
73
+ end
74
+ end
75
+
76
+ # Creates new class with layout name, subclassed from Rfm::Base, and links the new model to a SubLayout instance.
77
+ def modelize
78
+ model_name = name.to_s.gsub(/\W/, '_').classify.gsub(/_/,'')
79
+ (return model_name.constantize) rescue nil
80
+ sub = sublayout
81
+ sub.instance_eval do
82
+ model_class = eval("::" + model_name + "= Class.new(Rfm::Base)")
83
+ model_class.class_exec(self) do |layout_obj|
84
+ @layout = layout_obj
85
+ end
86
+ @model = model_class
87
+ end
88
+ sub.model.to_s.constantize
89
+ rescue StandardError, SyntaxError
90
+ nil
91
+ end
92
+
93
+ def models
94
+ subs.collect{|s| s.model}
95
+ end
96
+
97
+ end # Layout
98
+
99
+
100
+
101
+
102
+
103
+ class Record
104
+ class << self
105
+ def new(*args)
106
+ #puts "Creating new record from RECORD. Layout: #{args[3].class} #{args[3].object_id}"
107
+ args[3].model.new(*args)
108
+ rescue
109
+ #puts "RECORD failed to send 'new' to MODEL"
110
+ super
111
+ #allocate.send(:initialize, *args)
112
+ end
113
+ end # class << self
114
+ end # class Record
115
+
116
+
117
+
118
+
119
+
120
+ class Database
121
+ def_delegators :layouts, :modelize, :models
122
+ end
123
+
124
+
125
+
126
+
127
+ module Factory
128
+ @models ||= []
129
+
130
+ class << self
131
+ attr_accessor :models
132
+
133
+ # Shortcut to Factory.db().layouts.modelize()
134
+ # If first parameter is regex, it is used for modelize filter.
135
+ # Otherwise, parameters are passed to Factory.database
136
+ def modelize(*args)
137
+ regx = args[0].is_a?(Regexp) ? args.shift : /.*/
138
+ db(*args).layouts.modelize(regx)
139
+ end
140
+ end # class << self
141
+
142
+ class LayoutFactory
143
+ def modelize(filter = /.*/)
144
+ all.values.each{|lay| lay.modelize if lay.name.match(filter)}
145
+ models
146
+ end
147
+
148
+ def models
149
+ rslt = {}
150
+ each do |k,lay|
151
+ layout_models = lay.models
152
+ rslt[k] = layout_models if !layout_models.blank?
153
+ end
154
+ rslt
155
+ end
156
+
157
+ end # LayoutFactory
158
+ end # Factory
159
+
160
+
161
+
162
+
163
+ class Base < Rfm::Record #Hash
164
+ extend Config
165
+ config :parent => 'Rfm::Config'
166
+
167
+ begin
168
+ require 'active_model'
169
+ include ActiveModel::Validations
170
+ include ActiveModel::Serialization
171
+ extend ActiveModel::Callbacks
172
+ define_model_callbacks(:create, :update, :destroy)
173
+ rescue LoadError, StandardError
174
+ def run_callbacks(*args)
175
+ yield
176
+ end
177
+ end
178
+
179
+ def initialize(record={}, resultset_obj=[], field_meta='', layout_obj=self.class.layout, portal=nil)
180
+ if resultset_obj == [] and !record.has_key? 'field'
181
+ @mods = Rfm::CaseInsensitiveHash.new
182
+ @layout = layout_obj
183
+ @resultset = Resultset.allocate
184
+ # loop thru each layout field, creating hash keys with nil values
185
+ layout_obj.field_names.each do |field|
186
+ field_name = field.to_s
187
+ self[field_name] = nil
188
+ end
189
+ self.update_attributes(record) unless record == {}
190
+ self.merge!(@mods) unless @mods == {}
191
+ @loaded = true
192
+ else
193
+ super
194
+ end
195
+ end
196
+
197
+
198
+ class << self
199
+
200
+ # Access layout functions from base model
201
+ def_delegators :layout, :db, :server, :field_controls, :field_names, :value_lists, :total_count,
202
+ :query, :all, :delete, :portal_meta, :portal_names, :database, :table
203
+
204
+ def inherited(model)
205
+ (Rfm::Factory.models << model).uniq unless Rfm::Factory.models.include? model
206
+ model.config :parent=>'Rfm::Base'
207
+ end
208
+
209
+ # Build a new record without saving
210
+ def new(*args)
211
+ # Without this method, infinite recursion will happen from Record.new
212
+ #puts "Creating new record from BASE"
213
+ rec = self.allocate
214
+ rec.send(:initialize, *args)
215
+ rec
216
+ end
217
+
218
+ def config(*args)
219
+ super(*args){|strings| @config.merge!(:layout=>strings[0]) if strings[0]}
220
+ end
221
+
222
+ # Access/create the layout object associated with this model
223
+ def layout
224
+ return @layout if @layout
225
+ cnf = get_config
226
+ return unless cnf[:layout]
227
+ @layout = Rfm::Factory.layout(get_config).sublayout
228
+ @layout.model = self
229
+ @layout
230
+ end
231
+
232
+ # Just like Layout#find, but searching by record_id will return a record, not a resultset.
233
+ def find(*args)
234
+ r = layout.find(*args)
235
+ if args[0].class != Hash and r.size == 1
236
+ r[0]
237
+ else
238
+ r
239
+ end
240
+ rescue Rfm::Error::RecordMissingError
241
+ nil
242
+ end
243
+
244
+ # Layout#any, returns single record, not resultset
245
+ def any(*args)
246
+ layout.any(*args)[0]
247
+ end
248
+
249
+ # New record, save, (with callbacks & validations if ActiveModel is loaded)
250
+ def create(*args)
251
+ new(*args).send :create
252
+ end
253
+
254
+ # Using this method will skip callbacks. Use instance method +#update+ instead
255
+ def edit(*args)
256
+ layout.edit(*args)[0]
257
+ end
258
+
259
+ end # class << self
260
+
261
+
262
+
263
+
264
+
265
+
266
+ # Is this a newly created record, not saved yet?
267
+ def new_record?
268
+ return true if self.record_id.blank?
269
+ end
270
+
271
+ # Reload record from database
272
+ # TODO: handle error when record has been deleted
273
+ def reload(force=false)
274
+ if (@mods.empty? or force) and record_id
275
+ self.replace self.class.find(self.record_id)
276
+ end
277
+ end
278
+
279
+ # Mass update of record attributes, without saving.
280
+ # TODO: return error or nil if input hash contains no recognizable keys.
281
+ def update_attributes(new_attr)
282
+ # creates new special hash
283
+ input_hash = Rfm::CaseInsensitiveHash.new
284
+ # populate new hash with input, coercing keys to strings
285
+ new_attr.each{|k,v| input_hash.merge! k.to_s=>v}
286
+ # loop thru each layout field, adding data to @mods
287
+ self.class.field_controls.keys.each do |field|
288
+ field_name = field.to_s
289
+ if input_hash.has_key?(field_name)
290
+ @mods.merge! field_name=>(input_hash[field_name] || '')
291
+ end
292
+ end
293
+ # loop thru each input key-value,
294
+ # creating new attribute if key doesn't exist in model.
295
+ input_hash.each do |k,v|
296
+ if !self.class.field_controls.keys.include?(k) and self.respond_to?(k)
297
+ self.instance_variable_set("@#{k}", v)
298
+ end
299
+ end
300
+ self.merge!(@mods) unless @mods == {}
301
+ end
302
+
303
+ # Mass update of record attributes, with saving.
304
+ def update_attributes!(new_attr)
305
+ self.update_attributes(new_attr)
306
+ self.save!
307
+ end
308
+
309
+ # Save record modifications to database (with callbacks & validations). If record cannot be saved will raise error.
310
+ def save!
311
+ #return unless @mods.size > 0
312
+ raise "Record Invalid" unless valid? rescue nil
313
+ if @record_id
314
+ self.update
315
+ else
316
+ self.create
317
+ end
318
+ end
319
+
320
+ # Same as save!, but will not raise error.
321
+ def save
322
+ save!
323
+ rescue
324
+ (self.errors[:base] rescue []) << $!
325
+ return nil
326
+ end
327
+
328
+ # Just like Layout#save_if_not_modified, but with callbacks & validations.
329
+ def save_if_not_modified
330
+ update(@mod_id) if @mods.size > 0
331
+ end
332
+
333
+ # Delete record from database, with callbacks & validations.
334
+ def destroy
335
+ return unless record_id
336
+ run_callbacks :destroy do
337
+ self.class.delete(record_id)
338
+ @destroyed = true
339
+ @mods.clear
340
+ end
341
+ self.freeze
342
+ #self
343
+ end
344
+
345
+ def destroyed?
346
+ @destroyed
347
+ end
348
+
349
+ # For ActiveModel compatibility
350
+ def to_model
351
+ self
352
+ end
353
+
354
+ def persisted?
355
+ record_id ? true : false
356
+ end
357
+
358
+ def to_key
359
+ record_id ? [record_id] : nil
360
+ end
361
+
362
+ def to_param
363
+ record_id
364
+ end
365
+
366
+
367
+ protected # Base
368
+
369
+ def self.create_from_new(*args)
370
+ layout.create(*args)[0]
371
+ end
372
+
373
+ # shunt for callbacks when not using ActiveModel
374
+ def callback_deadend (*args)
375
+ yield #(*args)
376
+ end
377
+
378
+ def create
379
+ #return unless @mods.size > 0
380
+ run_callbacks :create do
381
+ return unless @mods.size > 0
382
+ merge_rfm_result self.class.create_from_new(@mods)
383
+ end
384
+ self
385
+ end
386
+
387
+ def update(mod_id=nil)
388
+ #return unless @mods.size > 0 and record_id
389
+ return false unless record_id
390
+ run_callbacks :update do
391
+ return unless @mods.size > 0
392
+ unless mod_id
393
+ # regular save
394
+ merge_rfm_result self.class.send :edit, record_id, @mods
395
+ else
396
+ # save_if_not_modified
397
+ merge_rfm_result self.class.send :edit, record_id, @mods, :modification_id=>mod_id
398
+ end
399
+ end
400
+ self
401
+ end
402
+
403
+ def merge_rfm_result(result_record)
404
+ return unless @mods.size > 0
405
+ @record_id ||= result_record.record_id
406
+ self.merge! result_record
407
+ @mods.clear
408
+ self || {}
409
+ end
410
+
411
+ end # Base
412
+
413
+ end # Rfm
414
+
415
+
416
+