simple_record 1.0.10 → 1.1.17

Sign up to get free protection for your applications and to get access to all the features.
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
+