simple_record 1.0.10 → 1.1.17

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/README.markdown ADDED
@@ -0,0 +1,200 @@
1
+ # SimpleRecord - ActiveRecord for SimpleDB
2
+
3
+ An ActiveRecord interface for SimpleDB. Can be used as a drop in replacement for ActiveRecord in rails.
4
+
5
+ ## Discussion Group
6
+
7
+ [http://groups.google.com/group/simple-record]
8
+
9
+ ## Issue Tracking
10
+
11
+ [http://appoxy.lighthouseapp.com/projects/38366-simplerecord/overview]
12
+
13
+ ## Getting Started
14
+
15
+ 1. Install gems
16
+
17
+ ONE TIME: Be sure to add the new gemcutter source:
18
+
19
+ - gem install gemcutter
20
+ - gem tumble
21
+
22
+ THEN (if you already have http://gemcutter.org in your sources and it MUST be above rubyforge.org):
23
+
24
+ gem install aws uuidtools http_connection simple_record
25
+
26
+ 2. Create a model
27
+
28
+ require 'simple_record'
29
+
30
+ class MyModel < SimpleRecord::Base
31
+ has_attributes :name
32
+ has_ints :age
33
+ end
34
+
35
+ More about ModelAttributes below.
36
+
37
+ 3. Setup environment
38
+
39
+ AWS_ACCESS_KEY_ID='XXXX'
40
+ AWS_SECRET_ACCESS_KEY='YYYY'
41
+ SimpleRecord.establish_connection(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY)
42
+
43
+ 4. Go to town
44
+
45
+ # Store a model object to SimpleDB
46
+ mm = MyModel.new
47
+ mm.name = "Travis"
48
+ mm.age = 32
49
+ mm.save
50
+ id = mm.id
51
+
52
+ # Get an object from SimpleDB
53
+ mm2 = MyModel.find(id)
54
+ puts 'got=' + mm2.name + ' and he/she is ' + mm.age.to_s + ' years old'
55
+ # Or more advanced queries? mms = MyModel?.find(:all, ["age=?", 32], :order=>"name", :limit=>10)
56
+
57
+
58
+ ## Attributes and modifiers for models
59
+
60
+ NOTE: All objects will automatically have :id, :created, :updated attributes.
61
+
62
+ ### has_attributes
63
+
64
+ Add string attributes.
65
+
66
+ class MyModel < SimpleRecord::Base
67
+ has_attributes :name
68
+ end
69
+
70
+ ### has_ints, has_dates and has_booleans
71
+
72
+ Lets simple_record know that certain attributes defined in has_attributes should be treated as integers, dates or booleans. This is required because SimpleDB only has strings so SimpleRecord needs to know how to convert, pad, offset, etc.
73
+
74
+ class MyModel < SimpleRecord::Base
75
+ has_attributes :name
76
+ has_ints :age, :height
77
+ has_dates :birthday
78
+ has_booleans :is_nerd
79
+ end
80
+
81
+ ### belongs_to
82
+
83
+ Creates a many-to-one relationship. Can only have one per belongs_to call.
84
+
85
+ class MyModel < SimpleRecord::Base
86
+ belongs_to :school
87
+ has_attributes :name
88
+ has_ints :age, :height
89
+ has_dates :birthday
90
+ has_booleans :is_nerd
91
+ end
92
+
93
+ Which requires another class called 'School' or you can specify the class explicitly with:
94
+
95
+ belongs_to :school, :class_name => "Institution"
96
+
97
+ ### set_table_name or set_domain_name
98
+
99
+ If you want to use a custom domain for a model object, you can specify it with set_table_name (or set_domain_name).
100
+
101
+ class SomeModel < SimpleRecord::Base
102
+ set_table_name :different_model
103
+ end
104
+
105
+
106
+ ## Configuration
107
+
108
+ ### Domain Prefix
109
+
110
+ To set a global prefix across all your models, use:
111
+
112
+ SimpleRecord::Base.set_domain_prefix("myprefix_")
113
+
114
+ ### Connection Modes
115
+
116
+ There are 4 different connection modes:
117
+
118
+ * per_request (default) - opens and closes a new connection to simpledb for every simpledb request. Not the best performance, but it's safe and can handle many concurrent requests at the same time (unlike single mode).
119
+ * single - one connection across the entire application, not recommended unless the app is used by a single person.
120
+ * per_thread - a connection is used for every thread in the application. This is good, but the catch is that you have to ensure to close the connection.
121
+ * pool - NOT IMPLEMENTED YET - opens a maximum number of connections and round robins them for any simpledb request.
122
+
123
+ You set the mode when you call establish_connection:
124
+
125
+ SimpleRecord.establish_connection(AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY, :connection_mode=>:per_thread)
126
+
127
+ We recommend per_thread with explicitly closing the connection after each Rails request (not to be mistaken for a SimpleDB request) or pool for rails apps.
128
+
129
+ For rails, be sure to add this to your Application controller if using per_thread mode:
130
+
131
+ after_filter :close_sdb_connection
132
+
133
+ def close_sdb_connection
134
+ SimpleRecord.close_connection
135
+ end
136
+
137
+ ## SimpleRecord on Rails
138
+
139
+ You don't really have to do anything except have your models extends SimpleRecord::Base instead of ActiveRecord::Base, but here are some tips you can use.
140
+
141
+ ### Change Connection Mode
142
+
143
+ Use per_thread connection mode and close the connection after each request.
144
+
145
+ after_filter :close_sdb_connection
146
+
147
+ def close_sdb_connection
148
+ SimpleRecord.close_connection
149
+ end
150
+
151
+ ### Disable ActiveRecord so you don't have to setup another database
152
+
153
+ This is most helpful on windows so Rails doesn't need sqlite or mysql gems/drivers installed which are painful to install on windows. In environment.rb, add 'config.frameworks -= [ :active_record ]', should look something like:
154
+
155
+ Rails::Initializer.run do |config|
156
+ config.frameworks -= [ :active_record ]
157
+ ....
158
+ end
159
+
160
+
161
+ ## Tips and Tricks and Things to Know
162
+
163
+ ### Automagic Stuff
164
+
165
+
166
+ #### Automatic common fields
167
+
168
+ Every object will automatically get the following attributes so you don't need to define them:
169
+
170
+ * id - UUID string
171
+ * created - set when first save
172
+ * updated - set every time you save/update
173
+
174
+
175
+ #### belongs_to foreign keys/IDs are accessible without touching the database
176
+
177
+ If you had the following in your model:
178
+
179
+ belongs_to :something
180
+
181
+ Then in addition to being able to access the something object with:
182
+
183
+ o.something
184
+
185
+ or setting it with:
186
+
187
+ o.something = someo
188
+
189
+ You can also access the ID for something directly with:
190
+
191
+ o.something_id
192
+
193
+ or
194
+
195
+ o.something_id = x
196
+
197
+ ## Kudos
198
+
199
+ Special thanks to Garrett Cox for creating Activerecord2sdb which SimpleRecord is based on:
200
+ http://activrecord2sdb.rubyforge.org/
@@ -0,0 +1,137 @@
1
+ module SimpleRecord
2
+
3
+ #
4
+ # We need to make this behave as if the full set were loaded into the array.
5
+ class ResultsArray
6
+ include Enumerable
7
+
8
+ attr_reader :next_token, :clz, :params, :items, :i
9
+
10
+
11
+ def initialize(clz=nil, params=[], items=[], next_token=nil)
12
+ @clz = clz
13
+ #puts 'class=' + clz.inspect
14
+ @params = params
15
+ if @params.size <= 1
16
+ options = {}
17
+ @params[1] = options
18
+ end
19
+ @items = items
20
+ @currentset_items = items
21
+ @next_token = next_token
22
+ @i = 0
23
+ end
24
+
25
+ def << (val)
26
+ @items << val
27
+ end
28
+
29
+ def [](*i)
30
+ puts 'i.inspect=' + i.inspect
31
+ puts i.size.to_s
32
+ i.each do |x|
33
+ puts 'x=' + x.inspect + " -- " + x.class.name
34
+ end
35
+ if i.size == 1
36
+ # either fixnum or range
37
+ x = i[0]
38
+ if x.is_a?(Fixnum)
39
+ load_to(x)
40
+ else
41
+ # range
42
+ end_val = x.exclude_end? ? x.end-1 : x.end
43
+ load_to(end_val)
44
+ end
45
+ elsif i.size == 2
46
+ # two fixnums
47
+ end_val = i[0] + i[1]
48
+ load_to(end_val)
49
+ end
50
+ @items[*i]
51
+ end
52
+
53
+ # Will load items from SimpleDB up to i.
54
+ def load_to(i)
55
+ return if @items.size >= i
56
+ while @items.size < i && !@next_token.nil?
57
+ load_next_token_set
58
+ end
59
+ end
60
+
61
+ def first
62
+ @items[0]
63
+ end
64
+
65
+ def last
66
+ @items[@items.length-1]
67
+ end
68
+
69
+ def empty?
70
+ @items.empty?
71
+ end
72
+
73
+ def include?(obj)
74
+ @items.include?(obj)
75
+ end
76
+
77
+ def size
78
+ # puts 'SIZE count=' + @count.inspect
79
+ return @count if @count
80
+ params_for_count = params.dup
81
+ params_for_count[0] = :count
82
+ #puts 'params_for_count=' + params_for_count.inspect
83
+ @count = clz.find(*params_for_count)
84
+ # puts '@count=' + @count.to_s
85
+ @count
86
+ end
87
+
88
+ def length
89
+ return size
90
+ end
91
+
92
+ def each(&blk)
93
+ options = @params[1]
94
+ limit = options[:limit]
95
+
96
+ @currentset_items.each do |v|
97
+ # puts @i.to_s
98
+ yield v
99
+ @i += 1
100
+ if !limit.nil? && @i >= limit
101
+ return
102
+ end
103
+ end
104
+ return if @clz.nil?
105
+
106
+ # no more items, but is there a next token?
107
+ unless @next_token.nil?
108
+ #puts 'finding more items...'
109
+ #puts 'params in block=' + params.inspect
110
+ #puts "i from results_array = " + @i.to_s
111
+
112
+ load_next_token_set
113
+ each(&blk)
114
+ end
115
+ end
116
+
117
+ def load_next_token_set
118
+ options = @params[1]
119
+ options[:next_token] = @next_token
120
+ res = @clz.find(*@params)
121
+ @currentset_items = res.items # get the real items array from the ResultsArray
122
+ @currentset_items.each do |item|
123
+ @items << item
124
+ end
125
+ @next_token = res.next_token
126
+ end
127
+
128
+ def delete(item)
129
+ @items.delete(item)
130
+ end
131
+
132
+ def delete_at(index)
133
+ @items.delete_at(index)
134
+ end
135
+
136
+ end
137
+ end
data/lib/simple_record.rb CHANGED
@@ -22,32 +22,194 @@
22
22
  # puts 'got=' + mm2.name + ' and he/she is ' + mm.age.to_s + ' years old'
23
23
 
24
24
 
25
-
26
25
  require 'right_aws'
27
26
  require 'sdb/active_sdb'
27
+ #require 'results_array' # why the heck isn't this picking up???
28
+ require File.expand_path(File.dirname(__FILE__) + "/results_array")
29
+ require File.expand_path(File.dirname(__FILE__) + "/stats")
28
30
 
29
31
  module SimpleRecord
30
32
 
31
- VERSION = '1.0.10'
33
+ @@stats = SimpleRecord::Stats.new
34
+
35
+ def self.stats
36
+ @@stats
37
+ end
38
+
39
+ # Create a new handle to an Sdb account. All handles share the same per process or per thread
40
+ # HTTP connection to Amazon Sdb. Each handle is for a specific account.
41
+ # The +params+ are passed through as-is to RightAws::SdbInterface.new
42
+ # Params:
43
+ # { :server => 'sdb.amazonaws.com' # Amazon service host: 'sdb.amazonaws.com'(default)
44
+ # :port => 443 # Amazon service port: 80(default) or 443
45
+ # :protocol => 'https' # Amazon service protocol: 'http'(default) or 'https'
46
+ # :signature_version => '0' # The signature version : '0' or '1'(default)
47
+ # :connection_mode => :default # options are
48
+ # :default (will use best known safe (as in won't need explicit close) option, may change in the future)
49
+ # :per_request (opens and closes a connection on every request to SDB)
50
+ # :single (one thread across entire app)
51
+ # :per_thread (one connection per thread)
52
+ # :pool (uses a connection pool with a maximum number of connections - NOT IMPLEMENTED YET)
53
+ # :logger => Logger Object # Logger instance: logs to STDOUT if omitted
54
+ def self.establish_connection(aws_access_key=nil, aws_secret_key=nil, params={})
55
+ RightAws::ActiveSdb.establish_connection(aws_access_key, aws_secret_key, params)
56
+ end
57
+
58
+ def self.close_connection()
59
+ RightAws::ActiveSdb.close_connection
60
+ end
61
+
62
+ module Callbacks
63
+ #this bit of code creates a "run_blank" function for everything value in the @@callbacks array.
64
+ #this function can then be inserted in the appropriate place in the save, new, destroy, etc overrides
65
+ #basically, this is how we recreate the callback functions
66
+ @@callbacks=["before_validation", "before_validation_on_create", "before_validation_on_update",
67
+ "after_validation", "after_validation_on_create", "after_validation_on_update",
68
+ "before_save", "before_create", "before_update",
69
+ "after_create", "after_update", "after_save",
70
+ "after_destroy"]
71
+
72
+ def self.included(base)
73
+ #puts 'Callbacks included in ' + base.inspect
74
+
75
+ end
76
+ end
32
77
 
33
78
  class Base < RightAws::ActiveSdb::Base
34
79
 
80
+ include SimpleRecord::Callbacks
81
+
82
+
83
+ def initialize(attrs={})
84
+ super
85
+ # Need to deal with objects passed in. iterate through belongs_to perhaps and if in attrs, set the objects id rather than the object itself
86
+ end
87
+
88
+
89
+ # todo: move into Callbacks module
90
+ #this bit of code creates a "run_blank" function for everything value in the @@callbacks array.
91
+ #this function can then be inserted in the appropriate place in the save, new, destroy, etc overrides
92
+ #basically, this is how we recreate the callback functions
93
+ @@callbacks.each do |callback|
94
+ instance_eval <<-endofeval
95
+
96
+ #puts 'doing callback=' + callback + ' for ' + self.inspect
97
+ #we first have to make an initialized array for each of the callbacks, to prevent problems if they are not called
98
+
99
+ def #{callback}(*args)
100
+ #puts 'callback called in ' + self.inspect + ' with ' + args.inspect
101
+
102
+ #make_dirty(arg_s, value)
103
+ #self[arg.to_s]=value
104
+ #puts 'value in callback #{callback}=' + value.to_s
105
+ args.each do |arg|
106
+ cnames = callbacks['#{callback}']
107
+ #puts '\tcnames1=' + cnames.inspect + ' for class ' + self.inspect
108
+ cnames = [] if cnames.nil?
109
+ cnames << arg.to_s if cnames.index(arg.to_s).nil?
110
+ #puts '\tcnames2=' + cnames.inspect
111
+ callbacks['#{callback}'] = cnames
112
+ end
113
+ end
114
+
115
+ endofeval
116
+ end
117
+ #puts 'base methods=' + self.methods.inspect
118
+
119
+
120
+ def self.inherited(base)
121
+ #puts 'SimpleRecord::Base is inherited by ' + base.inspect
122
+ setup_callbacks(base)
123
+
124
+ base.has_dates :created, :updated
125
+ base.before_create :set_created, :set_updated
126
+ base.before_update :set_updated
127
+
128
+ end
129
+
130
+ def self.setup_callbacks(base)
131
+ instance_eval <<-endofeval
132
+
133
+ def callbacks
134
+ @callbacks ||= {}
135
+ @callbacks
136
+ end
137
+
138
+ def self.defined_attributes
139
+ #puts 'class defined_attributes'
140
+ @attributes ||= {}
141
+ @attributes
142
+ end
143
+
144
+ endofeval
145
+
146
+ @@callbacks.each do |callback|
147
+ class_eval <<-endofeval
148
+
149
+ def run_#{callback}
150
+ # puts 'CLASS CALLBACKS for ' + self.inspect + ' = ' + self.class.callbacks.inspect
151
+ return true if self.class.callbacks.nil?
152
+ cnames = self.class.callbacks['#{callback}']
153
+ cnames = [] if cnames.nil?
154
+ #cnames += super.class.callbacks['#{callback}'] unless super.class.callbacks.nil?
155
+ # puts 'cnames for #{callback} = ' + cnames.inspect
156
+ return true if cnames.nil?
157
+ cnames.each { |name|
158
+ #puts 'run_ #{name}'
159
+ if eval(name) == false # nil should be an ok return, only looking for false
160
+ return false
161
+ end
162
+ }
163
+ #super.run_#{callback}
164
+ return true
165
+ end
166
+
167
+ endofeval
168
+ end
169
+ end
170
+
171
+
172
+ # Holds information about an attribute
173
+ class Attribute
174
+ attr_accessor :type, :options
175
+
176
+ def initialize(type)
177
+ @type = type
178
+ end
179
+
180
+ end
181
+
182
+
183
+ def defined_attributes_local
184
+ #puts 'local defined_attributes'
185
+ ret = self.class.defined_attributes
186
+ ret.merge!(self.class.superclass.defined_attributes) if self.class.superclass.respond_to?(:defined_attributes)
187
+ end
188
+
189
+
35
190
  attr_accessor :errors
36
- @@domain_prefix = ''
37
- @domain_name_for_class = nil
191
+
192
+ @domain_prefix = ''
193
+ class << self;
194
+ attr_accessor :domain_prefix;
195
+ end
196
+
197
+ #@domain_name_for_class = nil
38
198
 
39
199
  @@cache_store = nil
40
200
  # Set the cache to use
41
201
  def self.cache_store=(cache)
42
202
  @@cache_store = cache
43
203
  end
204
+
44
205
  def self.cache_store
45
206
  return @@cache_store
46
207
  end
47
208
 
48
209
  # If you want a domain prefix for all your models, set it here.
49
210
  def self.set_domain_prefix(prefix)
50
- @@domain_prefix = prefix
211
+ #puts 'set_domain_prefix=' + prefix
212
+ self.domain_prefix = prefix
51
213
  end
52
214
 
53
215
  # Same as set_table_name
@@ -58,110 +220,135 @@ module SimpleRecord
58
220
  # Sets the domain name for this class
59
221
  def self.set_domain_name(table_name)
60
222
  # puts 'setting domain name for class ' + self.inspect + '=' + table_name
61
- @domain_name_for_class = table_name
223
+ #@domain_name_for_class = table_name
62
224
  super
63
225
  end
64
226
 
65
- def self.get_domain_name
227
+ =begin
228
+ def self.get_domain_name
66
229
  # puts 'returning domain_name=' + @domain_name_for_class.to_s
67
- return @domain_name_for_class
230
+ #return @domain_name_for_class
231
+ return self.domain
68
232
  end
69
233
 
234
+ =end
70
235
 
71
236
  def domain
72
237
  super # super.domain
73
238
  end
74
239
 
75
240
  def self.domain
76
- return self.get_domain_name unless self.get_domain_name.nil?
241
+ #return self.get_domain_name unless self.get_domain_name.nil?
77
242
  d = super
78
- domain_name_for_class = @@domain_prefix + d.to_s
79
- self.set_domain_name(domain_name_for_class)
243
+ #puts 'in self.domain, d=' + d.to_s + ' domain_prefix=' + SimpleRecord::Base.domain_prefix.to_s
244
+ domain_name_for_class = SimpleRecord::Base.domain_prefix + d.to_s
245
+ #self.set_domain_name(domain_name_for_class)
80
246
  domain_name_for_class
81
247
  end
82
248
 
83
- #this bit of code creates a "run_blank" function for everything value in the @@callbacks array.
84
- #this function can then be inserted in the appropriate place in the save, new, destroy, etc overrides
85
- #basically, this is how we recreate the callback functions
86
- @@callbacks=["before_save", "before_create", "after_create", "before_update", "after_update", "after_save", "after_destroy"]
87
- @@callbacks.each do |callback|
88
- #we first have to make an initialized array for each of the callbacks, to prevent problems if they are not called
89
- eval %{
90
- @@#{callback}_names=[]
91
249
 
92
- def self.#{callback}(*args)
93
- args.each do |arg|
94
- @@#{callback}_names << arg.to_s if @@#{callback}_names.index(arg.to_s).nil?
250
+ # Since SimpleDB supports multiple attributes per value, the values are an array.
251
+ # This method will return the value unwrapped if it's the only, otherwise it will return the array.
252
+ def get_attribute(arg)
253
+ arg = arg.to_s
254
+ if self[arg].class==Array
255
+ if self[arg].length==1
256
+ ret = self[arg][0]
257
+ else
258
+ ret = self[arg]
259
+ end
260
+ else
261
+ ret = self[arg]
262
+ end
263
+ ret
95
264
  end
96
- # asdf @@#{callback}_names=args.map{|arg| arg.to_s}
97
- end
98
265
 
99
- def run_#{callback}
100
- @@#{callback}_names.each { |name|
101
- unless eval(name)
102
- return false
103
- end
104
- }
105
- return true
106
- end
107
- }
266
+ def make_dirty(arg, value)
267
+ # todo: only set dirty if it changed
268
+ #puts 'making dirty arg=' + arg.to_s + ' --- ' + @dirty.inspect
269
+ @dirty[arg] = get_attribute(arg) # Store old value (not sure if we need it?)
270
+ #puts 'end making dirty ' + @dirty.inspect
108
271
  end
109
272
 
110
273
  def self.has_attributes(*args)
111
- @@attributes = args
112
274
  args.each do |arg|
275
+ defined_attributes[arg] = SimpleRecord::Base::Attribute.new(:string) if defined_attributes[arg].nil?
113
276
  # define reader method
114
- send :define_method, arg do
277
+ arg_s = arg.to_s # to get rid of all the to_s calls
278
+ send(:define_method, arg) do
115
279
  ret = nil
116
- if self[arg.to_s].class==Array
117
- if self[arg.to_s].length==1
118
- ret = self[arg.to_s][0]
119
- else
120
- ret = self[arg.to_s]
121
- end
122
- else
123
- ret = self[arg.to_s]
124
- end
280
+ ret = get_attribute(arg)
125
281
  return nil if ret.nil?
126
282
  return un_offset_if_int(arg, ret)
127
283
  end
128
284
 
129
285
  # define writer method
130
- method_name = (arg.to_s+"=")
131
- send(:define_method, method_name) do |value|
132
- self[arg.to_s]=value# end
286
+ send(:define_method, arg_s+"=") do |value|
287
+ make_dirty(arg_s, value)
288
+ self[arg_s]=value
289
+ end
290
+
291
+ # Now for dirty methods: http://api.rubyonrails.org/classes/ActiveRecord/Dirty.html
292
+ # define changed? method
293
+ send(:define_method, arg_s + "_changed?") do
294
+ @dirty.has_key?(arg_s)
295
+ end
296
+
297
+ # define change method
298
+ send(:define_method, arg_s + "_change") do
299
+ old_val = @dirty[arg_s]
300
+ return nil if old_val.nil?
301
+ [old_val, get_attribute(arg_s)]
302
+ end
303
+
304
+ # define was method
305
+ send(:define_method, arg_s + "_was") do
306
+ old_val = @dirty[arg_s]
307
+ old_val
133
308
  end
134
309
  end
135
310
  end
136
311
 
137
- @@ints = []
312
+ def self.has_strings(*args)
313
+ has_attributes(*args)
314
+ end
315
+
316
+ def self.has_ints(*args)
317
+ has_attributes(*args)
318
+ are_ints(*args)
319
+ end
320
+
321
+ def self.has_dates(*args)
322
+ has_attributes(*args)
323
+ are_dates(*args)
324
+ end
325
+
326
+ def self.has_booleans(*args)
327
+ has_attributes(*args)
328
+ are_booleans(*args)
329
+ end
330
+
138
331
  def self.are_ints(*args)
139
332
  # puts 'calling are_ints: ' + args.inspect
140
333
  args.each do |arg|
141
- # todo: maybe @@ints and @@dates should be maps for quicker lookups
142
- @@ints << arg if @@ints.index(arg).nil?
334
+ defined_attributes[arg].type = :int
143
335
  end
144
- # @@ints = args
145
- # puts 'ints=' + @@ints.inspect
146
336
  end
147
337
 
148
- @@dates = []
149
338
  def self.are_dates(*args)
150
339
  args.each do |arg|
151
- @@dates << arg if @@dates.index(arg).nil?
340
+ defined_attributes[arg].type = :date
152
341
  end
153
- # @@dates = args
154
- # puts 'dates=' + @@dates.inspect
155
342
  end
156
343
 
157
- @@booleans = []
158
344
  def self.are_booleans(*args)
159
345
  args.each do |arg|
160
- @@booleans << arg if @@booleans.index(arg).nil?
346
+ defined_attributes[arg].type = :boolean
161
347
  end
162
348
  end
163
349
 
164
350
  @@virtuals=[]
351
+
165
352
  def self.has_virtuals(*args)
166
353
  @@virtuals = args
167
354
  args.each do |arg|
@@ -170,14 +357,17 @@ module SimpleRecord
170
357
  end
171
358
  end
172
359
 
173
- @@belongs_to_map = {}
174
360
  # One belongs_to association per call. Call multiple times if there are more than one.
175
361
  #
176
362
  # This method will also create an {association)_id method that will return the ID of the foreign object
177
363
  # without actually materializing it.
178
364
  def self.belongs_to(association_id, options = {})
179
- @@belongs_to_map[association_id] = options
365
+ attribute = SimpleRecord::Base::Attribute.new(:belongs_to)
366
+ defined_attributes[association_id] = attribute
367
+ attribute.options = options
368
+ #@@belongs_to_map[association_id] = options
180
369
  arg = association_id
370
+ arg_s = arg.to_s
181
371
  arg_id = arg.to_s + '_id'
182
372
 
183
373
  # todo: should also handle foreign_key http://74.125.95.132/search?q=cache:KqLkxuXiBBQJ:wiki.rubyonrails.org/rails/show/belongs_to+rails+belongs_to&hl=en&ct=clnk&cd=1&gl=us
@@ -187,8 +377,13 @@ module SimpleRecord
187
377
 
188
378
  # Define reader method
189
379
  send(:define_method, arg) do
190
- options2 = @@belongs_to_map[arg]
380
+ attribute = defined_attributes_local[arg]
381
+ options2 = attribute.options # @@belongs_to_map[arg]
191
382
  class_name = options2[:class_name] || arg.to_s[0...1].capitalize + arg.to_s[1...arg.to_s.length]
383
+
384
+ # Camelize classnames with underscores (ie my_model.rb --> MyModel)
385
+ class_name = class_name.camelize
386
+
192
387
  # puts "attr=" + @attributes[arg_id].inspect
193
388
  # puts 'val=' + @attributes[arg_id][0].inspect unless @attributes[arg_id].nil?
194
389
  ret = nil
@@ -202,7 +397,7 @@ module SimpleRecord
202
397
  # puts 'belongs_to incache=' + ret.inspect
203
398
  end
204
399
  if ret.nil?
205
- to_eval = "#{class_name}.find(@attributes['#{arg_id}'][0], :auto_load=>true)"
400
+ to_eval = "#{class_name}.find(@attributes['#{arg_id}'][0])"
206
401
  # puts 'to eval=' + to_eval
207
402
  begin
208
403
  ret = eval(to_eval) # (defined? #{arg}_id)
@@ -220,7 +415,21 @@ module SimpleRecord
220
415
  return ret
221
416
  end
222
417
 
223
- # Define reader ID method
418
+
419
+ # Define writer method
420
+ send(:define_method, arg.to_s + "=") do |value|
421
+ arg_id = arg.to_s + '_id'
422
+ if value.nil?
423
+ make_dirty(arg_id, nil)
424
+ self[arg_id]=nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
425
+ else
426
+ make_dirty(arg_id, value.id)
427
+ self[arg_id]=value.id
428
+ end
429
+ end
430
+
431
+
432
+ # Define ID reader method for reading the associated objects id without getting the entire object
224
433
  send(:define_method, arg_id) do
225
434
  if !@attributes[arg_id].nil? && @attributes[arg_id].size > 0 && @attributes[arg_id][0] != nil && @attributes[arg_id][0] != ''
226
435
  return @attributes[arg_id][0]
@@ -228,13 +437,12 @@ module SimpleRecord
228
437
  return nil
229
438
  end
230
439
 
231
- # Define writer method
232
- send(:define_method, arg.to_s + "=") do |value|
233
- arg_id = arg.to_s + '_id'
440
+ # Define writer method for setting the _id directly without the associated object
441
+ send(:define_method, arg_id + "=") do |value|
234
442
  if value.nil?
235
- self[arg_id]=nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
443
+ self[arg_id] = nil unless self[arg_id].nil? # if it went from something to nil, then we have to remember and remove attribute on save
236
444
  else
237
- self[arg_id]=value.id
445
+ self[arg_id] = value
238
446
  end
239
447
  end
240
448
 
@@ -266,27 +474,8 @@ module SimpleRecord
266
474
 
267
475
  end
268
476
 
269
- has_attributes :created, :updated
270
- before_create :set_created, :set_updated
271
- before_update :set_updated
272
- are_dates :created, :updated
273
-
274
- def set_created
275
- # puts 'SETTING CREATED'
276
- # @created = DateTime.now
277
- self[:created] = DateTime.now
278
- # @tester = 'some test value'
279
- # self[:tester] = 'some test value'
280
- end
281
-
282
- def set_updated
283
- # puts 'SETTING UPDATED'
284
- # @updated = DateTime.now
285
- self[:updated] = DateTime.now
286
- # @tester = 'some test value updated'
287
- end
288
-
289
477
  def initialize(*params)
478
+
290
479
  if params[0]
291
480
  #we have to handle the virtuals. Right now, this assumes that all parameters are passed from inside an array
292
481
  #this is the usually the case when the parameters are passed passed via POST and obtained from the params array
@@ -301,6 +490,34 @@ module SimpleRecord
301
490
  super()
302
491
  end
303
492
  @errors=SimpleRecord_errors.new
493
+ @dirty = {}
494
+
495
+
496
+ end
497
+
498
+ def clear_errors
499
+ @errors=SimpleRecord_errors.new
500
+ end
501
+
502
+ def []=(attribute, values)
503
+ make_dirty(attribute, values)
504
+ super
505
+ end
506
+
507
+
508
+ def set_created
509
+ # puts 'SETTING CREATED'
510
+ # @created = DateTime.now
511
+ self[:created] = DateTime.now
512
+ # @tester = 'some test value'
513
+ # self[:tester] = 'some test value'
514
+ end
515
+
516
+ def set_updated
517
+ # puts 'SETTING UPDATED'
518
+ # @updated = DateTime.now
519
+ self[:updated] = DateTime.now
520
+ # @tester = 'some test value updated'
304
521
  end
305
522
 
306
523
 
@@ -310,7 +527,7 @@ module SimpleRecord
310
527
 
311
528
  def self.pad_and_offset(x)
312
529
  # todo: add Float, etc
313
- # puts 'padding=' + x.class.name + " -- " + x.inspect
530
+ # puts 'padding=' + x.class.name + " -- " + x.inspect
314
531
  if x.kind_of? Integer
315
532
  x += @@offset
316
533
  x_str = x.to_s
@@ -318,7 +535,7 @@ module SimpleRecord
318
535
  x_str = '0' + x_str while x_str.size < 20
319
536
  return x_str
320
537
  elsif x.respond_to?(:iso8601)
321
- # puts x.class.name + ' responds to iso8601'
538
+ # puts x.class.name + ' responds to iso8601'
322
539
  #
323
540
  # There is an issue here where Time.iso8601 on an incomparable value to DateTime.iso8601.
324
541
  # Amazon suggests: 2008-02-10T16:52:01.000-05:00
@@ -329,7 +546,7 @@ module SimpleRecord
329
546
  else
330
547
  x_str = x.getutc.strftime(@@date_format)
331
548
  end
332
- # puts 'utc=' + x_str
549
+ # puts 'utc=' + x_str
333
550
  return x_str
334
551
  else
335
552
  return x
@@ -345,24 +562,71 @@ module SimpleRecord
345
562
  return false
346
563
  end
347
564
 
565
+ def valid?
566
+ errors.clear
567
+
568
+ # run_callbacks(:validate)
569
+ validate
570
+
571
+ if new_record?
572
+ # run_callbacks(:validate_on_create)
573
+ validate_on_create
574
+ else
575
+ # run_callbacks(:validate_on_update)
576
+ validate_on_update
577
+ end
578
+
579
+ errors.empty?
580
+ end
581
+
582
+ def new_record?
583
+ # todo: new_record in activesdb should align with how we're defining a new record here, ie: if id is nil
584
+ super
585
+ end
586
+
587
+ def invalid?
588
+ !valid?
589
+ end
590
+
591
+ def validate
592
+ true
593
+ end
594
+
595
+ def validate_on_create
596
+ true
597
+ end
598
+
599
+ def validate_on_update
600
+ true
601
+ end
602
+
348
603
  @create_domain_called = false
349
604
 
350
- def save(*params)
605
+ # Options:
606
+ # - :except => Array of attributes to NOT save
607
+ # - :dirty => true - Will only store attributes that were modified
608
+ #
609
+ def save(options={})
351
610
  # puts 'SAVING: ' + self.inspect
611
+ clear_errors
352
612
  is_create = self[:id].nil?
353
- ok = pre_save(*params)
613
+ ok = pre_save(options)
354
614
  if ok
355
615
  begin
356
616
  # puts 'is frozen? ' + self.frozen?.to_s + ' - ' + self.inspect
357
- to_delete = get_atts_to_delete
358
- if super(*params)
617
+ if options[:dirty] # Only used in simple_record right now
618
+ options[:dirty_atts] = @dirty
619
+ end
620
+ to_delete = get_atts_to_delete # todo: this should use the @dirty hash now
621
+ # puts 'saving'
622
+ if super(options)
359
623
  # puts 'SAVED super'
360
624
  self.class.cache_results(self)
361
625
  delete_niled(to_delete)
362
- if run_after_save && is_create ? run_after_create : run_after_update
626
+ if (is_create ? run_after_create : run_after_update) && run_after_save
627
+ # puts 'all good?'
363
628
  return true
364
629
  else
365
- #I thought about calling destroy here, but rails doesn't behave that way, so neither will I
366
630
  return false
367
631
  end
368
632
  else
@@ -373,7 +637,7 @@ module SimpleRecord
373
637
  if (domain_ok($!))
374
638
  if !@create_domain_called
375
639
  @create_domain_called = true
376
- save(*params)
640
+ save(options)
377
641
  else
378
642
  raise $!
379
643
  end
@@ -388,54 +652,57 @@ module SimpleRecord
388
652
  end
389
653
 
390
654
  def pad_and_offset_ints_to_sdb()
391
- if !@@ints.nil?
392
- for i in @@ints
655
+
656
+ defined_attributes_local.each_pair do |name, att_meta|
393
657
  # puts 'int encoding: ' + i.to_s
394
- if !self[i.to_s].nil?
658
+ if att_meta.type == :int && !self[name.to_s].nil?
395
659
  # puts 'before: ' + self[i.to_s].inspect
396
- # puts @attributes.inspect
397
- # puts @attributes[i.to_s].inspect
398
- arr = @attributes[i.to_s]
399
- arr.collect!{ |x| self.class.pad_and_offset(x) }
400
- @attributes[i.to_s] = arr
660
+ # puts @attributes.inspect
661
+ # puts @attributes[i.to_s].inspect
662
+ arr = @attributes[name.to_s]
663
+ arr.collect!{ |x| self.class.pad_and_offset(x) }
664
+ @attributes[name.to_s] = arr
401
665
  # puts 'after: ' + @attributes[i.to_s].inspect
402
- else
403
- # puts 'was nil'
404
- end
405
666
  end
406
667
  end
407
668
  end
408
- def convert_dates_to_sdb()
409
- if !@@dates.nil?
410
- for i in @@dates
669
+
670
+ def convert_dates_to_sdb()
671
+
672
+ defined_attributes_local.each_pair do |name, att_meta|
411
673
  # puts 'int encoding: ' + i.to_s
412
- if !self[i.to_s].nil?
674
+ if att_meta.type == :date && !self[name.to_s].nil?
413
675
  # puts 'before: ' + self[i.to_s].inspect
414
- # puts @attributes.inspect
415
- # puts @attributes[i.to_s].inspect
416
- arr = @attributes[i.to_s]
417
- #puts 'padding date=' + i.to_s
418
- arr.collect!{ |x| self.class.pad_and_offset(x) }
419
- @attributes[i.to_s] = arr
676
+ # puts @attributes.inspect
677
+ # puts @attributes[i.to_s].inspect
678
+ arr = @attributes[name.to_s]
679
+ #puts 'padding date=' + i.to_s
680
+ arr.collect!{ |x| self.class.pad_and_offset(x) }
681
+ @attributes[name.to_s] = arr
420
682
  # puts 'after: ' + @attributes[i.to_s].inspect
421
- else
422
- # puts 'was nil'
423
- end
683
+ else
684
+ # puts 'was nil'
424
685
  end
425
686
  end
426
687
  end
427
688
 
428
- def pre_save(*params)
429
- if respond_to?('validate')
430
- validate
689
+ def pre_save(options)
690
+
691
+ is_create = self[:id].nil?
692
+ ok = run_before_validation && (is_create ? run_before_validation_on_create : run_before_validation_on_update)
693
+ return false unless ok
694
+
695
+ validate
696
+ is_create ? validate_on_create : validate_on_update
431
697
  # puts 'AFTER VALIDATIONS, ERRORS=' + errors.inspect
432
- if (!@errors.nil? && @errors.length > 0 )
698
+ if (!@errors.nil? && @errors.length > 0 )
433
699
  # puts 'THERE ARE ERRORS, returning false'
434
- return false
435
- end
700
+ return false
436
701
  end
437
702
 
438
- is_create = self[:id].nil?
703
+ ok = run_after_validation && (is_create ? run_after_validation_on_create : run_after_validation_on_update)
704
+ return false unless ok
705
+
439
706
  ok = respond_to?('before_save') ? before_save : true
440
707
  if ok
441
708
  if is_create && respond_to?('before_create')
@@ -445,7 +712,7 @@ module SimpleRecord
445
712
  end
446
713
  end
447
714
  if ok
448
- ok = run_before_save && is_create ? run_before_create : run_before_update
715
+ ok = run_before_save && (is_create ? run_before_create : run_before_update)
449
716
  end
450
717
  if ok
451
718
  # puts 'ABOUT TO SAVE: ' + self.inspect
@@ -465,6 +732,7 @@ module SimpleRecord
465
732
  end
466
733
 
467
734
  def get_atts_to_delete
735
+ # todo: this should use the @dirty hash now
468
736
  to_delete = []
469
737
  @attributes.each do |key, value|
470
738
  # puts 'value=' + value.inspect
@@ -477,20 +745,24 @@ module SimpleRecord
477
745
 
478
746
  # Run pre_save on each object, then runs batch_put_attributes
479
747
  # Returns
480
- def self.batch_save(objects)
748
+ def self.batch_save(objects, options={})
481
749
  results = []
482
750
  to_save = []
483
751
  if objects && objects.size > 0
484
752
  objects.each do |o|
485
- ok = o.pre_save
753
+ ok = o.pre_save(options)
486
754
  raise "Pre save failed on object [" + o.inspect + "]" if !ok
487
755
  results << ok
488
- next if !ok
756
+ next if !ok # todo: this shouldn't be here should it? raises above
489
757
  o.pre_save2
490
758
  to_save << RightAws::SdbInterface::Item.new(o.id, o.attributes, true)
759
+ if to_save.size == 25 # Max amount SDB will accept
760
+ connection.batch_put_attributes(domain, to_save)
761
+ to_save.clear
762
+ end
491
763
  end
492
764
  end
493
- connection.batch_put_attributes(domain, to_save)
765
+ connection.batch_put_attributes(domain, to_save) if to_save.size > 0
494
766
  results
495
767
  end
496
768
 
@@ -510,51 +782,14 @@ module SimpleRecord
510
782
  end
511
783
 
512
784
  def un_offset_if_int(arg, x)
513
- if !@@ints.nil?
514
- for i in @@ints
515
- # puts 'unpadding: ' + i.to_s
516
- # unpad and unoffset
517
- if i == arg
518
- # puts 'unoffsetting ' + x.to_s
519
- x = un_offset_int(x)
520
- end
521
- end
522
- end
523
- if !@@dates.nil?
524
- for d in @@dates
525
- # puts 'converting created: ' + self['created'].inspect
526
- if d == arg
527
- x = to_date(x)
528
- end
529
- # if !self[d].nil?
530
- # self[d].collect!{ |d2|
531
- # if d2.is_a?(String)
532
- # DateTime.parse(d2)
533
- # else
534
- # d2
535
- # end
536
- # }
537
- # end
538
- # puts 'after=' + self['created'].inspect
539
- end
540
- end
541
- if !@@booleans.nil?
542
- for b in @@booleans
543
- # puts 'converting created: ' + self['created'].inspect
544
- if b == arg
545
- x = to_bool(x)
546
- end
547
- # if !self[d].nil?
548
- # self[d].collect!{ |d2|
549
- # if d2.is_a?(String)
550
- # DateTime.parse(d2)
551
- # else
552
- # d2
553
- # end
554
- # }
555
- # end
556
- # puts 'after=' + self['created'].inspect
557
- end
785
+ att_meta = defined_attributes_local[arg]
786
+ # puts 'int encoding: ' + i.to_s
787
+ if att_meta.type == :int
788
+ x = un_offset_int(x)
789
+ elsif att_meta.type == :date
790
+ x = to_date(x)
791
+ elsif att_meta.type == :boolean
792
+ x = to_bool(x)
558
793
  end
559
794
  x
560
795
  end
@@ -606,40 +841,15 @@ module SimpleRecord
606
841
  end
607
842
 
608
843
  def unpad_self
609
- if !@@ints.nil?
610
- for i in @@ints
611
- # puts 'unpadding: ' + i.to_s
612
- # unpad and unoffset
613
-
614
- unpad(i, @attributes)
844
+ defined_attributes_local.each_pair do |name, att_meta|
845
+ if att_meta.type == :int
846
+ unpad(name, @attributes)
615
847
  end
616
848
  end
617
849
  end
618
850
 
619
851
  def reload
620
852
  super()
621
- # puts 'decoding...'
622
-
623
- =begin
624
- This is done on getters now
625
- if !@@dates.nil?
626
- for d in @@dates
627
- # puts 'converting created: ' + self['created'].inspect
628
- if !self[d].nil?
629
- self[d].collect!{ |d2|
630
- if d2.is_a?(String)
631
- DateTime.parse(d2)
632
- else
633
- d2
634
- end
635
- }
636
- end
637
- # puts 'after=' + self['created'].inspect
638
- end
639
- end
640
- =end
641
-
642
- # unpad_self
643
853
  end
644
854
 
645
855
  def update_attributes(*params)
@@ -678,31 +888,48 @@ This is done on getters now
678
888
  end
679
889
  end
680
890
 
891
+ @@regex_no_id = /.*Couldn't find.*with ID.*/
892
+
893
+ #
894
+ # Usage:
895
+ # Find by ID:
896
+ # MyModel.find(ID)
897
+ #
898
+ # Query:
899
+ # MyModel.find(:all, ["name = ?", name], :order=>"created desc", :limit=>10)
900
+ #
681
901
  def self.find(*params)
682
- reload=true
683
- first=false
684
- all=false
685
- select=false
902
+ #puts 'params=' + params.inspect
903
+ q_type = :all
686
904
  select_attributes=[]
687
905
 
688
906
  if params.size > 0
689
- all = params[0] == :all
690
- first = params[0] == :first
907
+ q_type = params[0]
691
908
  end
692
909
 
693
-
694
910
  # Pad and Offset number attributes
695
- options = params[1]
696
- # puts 'options=' + options.inspect
697
- convert_condition_params(options)
698
- # puts 'after collect=' + options.inspect
911
+ options = {}
912
+ if params.size > 1
913
+ options = params[1]
914
+ #puts 'options=' + options.inspect
915
+ #puts 'after collect=' + options.inspect
916
+ convert_condition_params(options)
917
+ end
918
+ #puts 'params2=' + params.inspect
699
919
 
700
- results = all ? [] : nil
920
+ results = q_type == :all ? [] : nil
701
921
  begin
702
922
  results=super(*params)
703
- cache_results(results)
923
+ #puts 'params3=' + params.inspect
924
+ SimpleRecord.stats.selects += 1
925
+ if q_type != :count
926
+ cache_results(results)
927
+ if results.is_a?(Array)
928
+ results = SimpleRecord::ResultsArray.new(self, params, results, next_token)
929
+ end
930
+ end
704
931
  rescue RightAws::AwsError, RightAws::ActiveSdb::ActiveSdbError
705
- puts "RESCUED: " + $!
932
+ puts "RESCUED: " + $!.message
706
933
  if ($!.message().index("NoSuchDomain") != nil)
707
934
  # this is ok
708
935
  elsif ($!.message() =~ @@regex_no_id)
@@ -714,48 +941,20 @@ This is done on getters now
714
941
  return results
715
942
  end
716
943
 
717
- @@regex_no_id = /.*Couldn't find.*with ID.*/
718
944
  def self.select(*params)
719
- first=false
720
- all=false
721
- select=false
722
- select_attributes=[]
723
-
724
- if params.size > 0
725
- all = params[0] == :all
726
- first = params[0] == :first
727
- end
728
-
729
- options = params[1]
730
- convert_condition_params(options)
731
-
732
- results = all ? [] : nil
733
- begin
734
- results=super(*params)
735
- cache_results(results)
736
- rescue RightAws::AwsError, RightAws::ActiveSdb::ActiveSdbError
737
- if ($!.message().index("NoSuchDomain") != nil)
738
- # this is ok
739
- elsif ($!.message() =~ @@regex_no_id)
740
- results = nil
741
- else
742
- raise $!
743
- end
744
- end
745
- return results
746
-
945
+ return find(*params)
747
946
  end
748
947
 
749
948
  def self.convert_condition_params(options)
750
- if !options.nil? && options.size > 0
751
- conditions = options[:conditions]
752
- if !conditions.nil? && conditions.size > 1
753
- # all after first are values
754
- conditions.collect! { |x|
755
- self.pad_and_offset(x)
756
- }
757
- end
949
+ return if options.nil?
950
+ conditions = options[:conditions]
951
+ if !conditions.nil? && conditions.size > 1
952
+ # all after first are values
953
+ conditions.collect! { |x|
954
+ self.pad_and_offset(x)
955
+ }
758
956
  end
957
+
759
958
  end
760
959
 
761
960
  def self.cache_results(results)
@@ -766,8 +965,8 @@ This is done on getters now
766
965
  class_name = results.class.name
767
966
  id = results.id
768
967
  cache_key = self.cache_key(class_name, id)
769
- # puts 'caching result at ' + cache_key + ': ' + results.inspect
770
- @@cache_store.write(cache_key, results, :expires_in =>10*60)
968
+ #puts 'caching result at ' + cache_key + ': ' + results.inspect
969
+ @@cache_store.write(cache_key, results, :expires_in =>30)
771
970
  end
772
971
  end
773
972
  end
@@ -776,9 +975,8 @@ This is done on getters now
776
975
  return class_name + "/" + id.to_s
777
976
  end
778
977
 
779
-
780
-
781
978
  @@debug=""
979
+
782
980
  def self.debug
783
981
  @@debug
784
982
  end
@@ -788,7 +986,27 @@ This is done on getters now
788
986
  end
789
987
 
790
988
  def self.table_name
791
- return @@domain_prefix + self.class.name.tableize
989
+ return domain
990
+ end
991
+
992
+ def changed
993
+ return @dirty.keys
994
+ end
995
+
996
+ def changed?
997
+ return @dirty.size > 0
998
+ end
999
+
1000
+ def changes
1001
+ ret = {}
1002
+ #puts 'in CHANGES=' + @dirty.inspect
1003
+ @dirty.each_pair {|key, value| ret[key] = [value, get_attribute(key)]}
1004
+ return ret
1005
+ end
1006
+
1007
+ def mark_as_old
1008
+ super
1009
+ @dirty = {}
792
1010
  end
793
1011
 
794
1012
  end
@@ -822,6 +1040,14 @@ This is done on getters now
822
1040
  def full_messages
823
1041
  return @errors
824
1042
  end
1043
+
1044
+ def clear
1045
+ @errors.clear
1046
+ end
1047
+
1048
+ def empty?
1049
+ @errors.empty?
1050
+ end
825
1051
  end
826
1052
 
827
1053
  class Activerecordtosdb_subrecord_array
@@ -852,6 +1078,14 @@ This is done on getters now
852
1078
  return load << ob
853
1079
  end
854
1080
 
1081
+ def count
1082
+ return load.count
1083
+ end
1084
+
1085
+ def size
1086
+ return count
1087
+ end
1088
+
855
1089
  def each(*params, &block)
856
1090
  return load.each(*params){|record| block.call(record)}
857
1091
  end
@@ -899,3 +1133,4 @@ This is done on getters now
899
1133
 
900
1134
  end
901
1135
  end
1136
+