bigrecord 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +44 -0
  3. data/Rakefile +17 -0
  4. data/VERSION +1 -0
  5. data/doc/bigrecord_specs.rdoc +36 -0
  6. data/doc/getting_started.rdoc +157 -0
  7. data/examples/bigrecord.yml +25 -0
  8. data/generators/bigrecord/bigrecord_generator.rb +17 -0
  9. data/generators/bigrecord/templates/bigrecord.rake +47 -0
  10. data/generators/bigrecord_migration/bigrecord_migration_generator.rb +13 -0
  11. data/generators/bigrecord_migration/templates/migration.rb +9 -0
  12. data/generators/bigrecord_model/bigrecord_model_generator.rb +28 -0
  13. data/generators/bigrecord_model/templates/migration.rb +13 -0
  14. data/generators/bigrecord_model/templates/model.rb +7 -0
  15. data/generators/bigrecord_model/templates/model_spec.rb +12 -0
  16. data/init.rb +9 -0
  17. data/install.rb +22 -0
  18. data/lib/big_record/abstract_base.rb +1088 -0
  19. data/lib/big_record/action_view_extensions.rb +266 -0
  20. data/lib/big_record/ar_associations/association_collection.rb +194 -0
  21. data/lib/big_record/ar_associations/association_proxy.rb +158 -0
  22. data/lib/big_record/ar_associations/belongs_to_association.rb +57 -0
  23. data/lib/big_record/ar_associations/belongs_to_many_association.rb +57 -0
  24. data/lib/big_record/ar_associations/has_and_belongs_to_many_association.rb +164 -0
  25. data/lib/big_record/ar_associations/has_many_association.rb +191 -0
  26. data/lib/big_record/ar_associations/has_one_association.rb +80 -0
  27. data/lib/big_record/ar_associations.rb +1608 -0
  28. data/lib/big_record/ar_reflection.rb +223 -0
  29. data/lib/big_record/attribute_methods.rb +75 -0
  30. data/lib/big_record/base.rb +618 -0
  31. data/lib/big_record/br_associations/association_collection.rb +194 -0
  32. data/lib/big_record/br_associations/association_proxy.rb +153 -0
  33. data/lib/big_record/br_associations/belongs_to_association.rb +52 -0
  34. data/lib/big_record/br_associations/belongs_to_many_association.rb +293 -0
  35. data/lib/big_record/br_associations/cached_item_proxy.rb +194 -0
  36. data/lib/big_record/br_associations/cached_item_proxy_factory.rb +62 -0
  37. data/lib/big_record/br_associations/has_and_belongs_to_many_association.rb +168 -0
  38. data/lib/big_record/br_associations/has_one_association.rb +80 -0
  39. data/lib/big_record/br_associations.rb +978 -0
  40. data/lib/big_record/br_reflection.rb +151 -0
  41. data/lib/big_record/callbacks.rb +367 -0
  42. data/lib/big_record/connection_adapters/abstract/connection_specification.rb +279 -0
  43. data/lib/big_record/connection_adapters/abstract/database_statements.rb +175 -0
  44. data/lib/big_record/connection_adapters/abstract/quoting.rb +58 -0
  45. data/lib/big_record/connection_adapters/abstract_adapter.rb +190 -0
  46. data/lib/big_record/connection_adapters/column.rb +491 -0
  47. data/lib/big_record/connection_adapters/hbase_adapter.rb +432 -0
  48. data/lib/big_record/connection_adapters/view.rb +27 -0
  49. data/lib/big_record/connection_adapters.rb +10 -0
  50. data/lib/big_record/deletion.rb +73 -0
  51. data/lib/big_record/dynamic_schema.rb +92 -0
  52. data/lib/big_record/embedded.rb +71 -0
  53. data/lib/big_record/embedded_associations/association_proxy.rb +148 -0
  54. data/lib/big_record/family_span_columns.rb +89 -0
  55. data/lib/big_record/fixtures.rb +1025 -0
  56. data/lib/big_record/migration.rb +380 -0
  57. data/lib/big_record/routing_ext.rb +65 -0
  58. data/lib/big_record/timestamp.rb +51 -0
  59. data/lib/big_record/validations.rb +830 -0
  60. data/lib/big_record.rb +125 -0
  61. data/lib/bigrecord.rb +1 -0
  62. data/rails/init.rb +9 -0
  63. data/spec/connections/bigrecord.yml +13 -0
  64. data/spec/connections/cassandra/connection.rb +2 -0
  65. data/spec/connections/hbase/connection.rb +2 -0
  66. data/spec/debug.log +281 -0
  67. data/spec/integration/br_associations_spec.rb +80 -0
  68. data/spec/lib/animal.rb +12 -0
  69. data/spec/lib/book.rb +10 -0
  70. data/spec/lib/broken_migrations/duplicate_name/20090706182535_add_animals_table.rb +14 -0
  71. data/spec/lib/broken_migrations/duplicate_name/20090706193019_add_animals_table.rb +9 -0
  72. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_books_table.rb +9 -0
  73. data/spec/lib/broken_migrations/duplicate_version/20090706190623_add_companies_table.rb +9 -0
  74. data/spec/lib/company.rb +14 -0
  75. data/spec/lib/embedded/web_link.rb +12 -0
  76. data/spec/lib/employee.rb +33 -0
  77. data/spec/lib/migrations/20090706182535_add_animals_table.rb +13 -0
  78. data/spec/lib/migrations/20090706190623_add_books_table.rb +15 -0
  79. data/spec/lib/migrations/20090706193019_add_companies_table.rb +14 -0
  80. data/spec/lib/migrations/20090706194512_add_employees_table.rb +13 -0
  81. data/spec/lib/migrations/20090706195741_add_zoos_table.rb +13 -0
  82. data/spec/lib/novel.rb +5 -0
  83. data/spec/lib/zoo.rb +17 -0
  84. data/spec/spec.opts +4 -0
  85. data/spec/spec_helper.rb +55 -0
  86. data/spec/unit/abstract_base_spec.rb +287 -0
  87. data/spec/unit/adapters/abstract_adapter_spec.rb +56 -0
  88. data/spec/unit/adapters/adapter_shared_spec.rb +51 -0
  89. data/spec/unit/adapters/hbase_adapter_spec.rb +15 -0
  90. data/spec/unit/ar_associations_spec.rb +8 -0
  91. data/spec/unit/base_spec.rb +6 -0
  92. data/spec/unit/br_associations_spec.rb +58 -0
  93. data/spec/unit/embedded_spec.rb +43 -0
  94. data/spec/unit/find_spec.rb +34 -0
  95. data/spec/unit/hash_helper_spec.rb +44 -0
  96. data/spec/unit/migration_spec.rb +144 -0
  97. data/spec/unit/model_spec.rb +315 -0
  98. data/spec/unit/validations_spec.rb +182 -0
  99. data/tasks/bigrecord_tasks.rake +47 -0
  100. data/tasks/data_store.rb +46 -0
  101. data/tasks/gem.rb +22 -0
  102. data/tasks/rdoc.rb +8 -0
  103. data/tasks/spec.rb +34 -0
  104. metadata +189 -0
@@ -0,0 +1,1088 @@
1
+ ## The sub-classes of this class must implement the abstract method 'column_names' of this class.
2
+ module BigRecord
3
+ class BigRecordError < StandardError #:nodoc:
4
+ end
5
+ class SubclassNotFound < BigRecordError #:nodoc:
6
+ end
7
+ class AssociationTypeMismatch < BigRecordError #:nodoc:
8
+ end
9
+ class WrongAttributeDataType < BigRecordError #:nodoc:
10
+ end
11
+ class AttributeMissing < BigRecordError #:nodoc:
12
+ end
13
+ class UnknownAttribute < BigRecordError #:nodoc:
14
+ end
15
+ class AdapterNotSpecified < BigRecordError # :nodoc:
16
+ end
17
+ class AdapterNotFound < BigRecordError # :nodoc:
18
+ end
19
+ class ConnectionNotEstablished < BigRecordError #:nodoc:
20
+ end
21
+ class ConnectionFailed < BigRecordError #:nodoc:
22
+ end
23
+ class RecordNotFound < BigRecordError #:nodoc:
24
+ end
25
+ class RecordNotSaved < BigRecordError #:nodoc:
26
+ end
27
+ class StatementInvalid < BigRecordError #:nodoc:
28
+ end
29
+ class PreparedStatementInvalid < BigRecordError #:nodoc:
30
+ end
31
+ class StaleObjectError < BigRecordError #:nodoc:
32
+ end
33
+ class ConfigurationError < StandardError #:nodoc:
34
+ end
35
+ class ReadOnlyRecord < StandardError #:nodoc:
36
+ end
37
+ class NotImplemented < BigRecordError #:nodoc:
38
+ end
39
+ class ColumnNotFound < BigRecordError #:nodoc:
40
+ end
41
+ class AttributeAssignmentError < BigRecordError #:nodoc:
42
+ attr_reader :exception, :attribute
43
+ def initialize(message, exception, attribute)
44
+ @exception = exception
45
+ @attribute = attribute
46
+ @message = message
47
+ end
48
+ end
49
+ class MultiparameterAssignmentErrors < BigRecordError #:nodoc:
50
+ attr_reader :errors
51
+ def initialize(errors)
52
+ @errors = errors
53
+ end
54
+ end
55
+
56
+ class AbstractBase
57
+ require 'rubygems'
58
+ require 'uuidtools'
59
+
60
+ # Accepts a logger conforming to the interface of Log4r or the default Ruby 1.8+ Logger class, which is then passed
61
+ # on to any new database connections made and which can be retrieved on both a class and instance level by calling +logger+.
62
+ cattr_accessor :logger, :instance_writer => false
63
+
64
+ # Constants for special characters in generated IDs. An ID might then look
65
+ # like this: 'United_States-Hawaii-Oahu-Honolulu-b9cef848-a4e0-11dc-a7ba-0018f3137ea8'
66
+ ID_FIELD_SEPARATOR = '-'
67
+ ID_WHITE_SPACE_CHAR = '_'
68
+
69
+ def self.inherited(child) #:nodoc:
70
+ @@subclasses[self] ||= []
71
+ @@subclasses[self] << child
72
+ child.set_table_name child.name.tableize if child.superclass == BigRecord::Base
73
+ super
74
+ end
75
+
76
+ def self.reset_subclasses #:nodoc:
77
+ nonreloadables = []
78
+ subclasses.each do |klass|
79
+ unless Dependencies.autoloaded? klass
80
+ nonreloadables << klass
81
+ next
82
+ end
83
+ klass.instance_variables.each { |var| klass.send(:remove_instance_variable, var) }
84
+ klass.instance_methods(false).each { |m| klass.send :undef_method, m }
85
+ end
86
+ @@subclasses = {}
87
+ nonreloadables.each { |klass| (@@subclasses[klass.superclass] ||= []) << klass }
88
+ end
89
+
90
+ @@subclasses = {}
91
+
92
+ def self.store_primary_key?
93
+ false
94
+ end
95
+
96
+ cattr_accessor :configurations, :instance_writer => false
97
+ @@configurations = {}
98
+
99
+ # Accessor for the name of the prefix string to prepend to every table name. So if set to "basecamp_", all
100
+ # table names will be named like "basecamp_projects", "basecamp_people", etc. This is a convenient way of creating a namespace
101
+ # for tables in a shared database. By default, the prefix is the empty string.
102
+ cattr_accessor :table_name_prefix, :instance_writer => false
103
+ @@table_name_prefix = ""
104
+
105
+ # Works like +table_name_prefix+, but appends instead of prepends (set to "_basecamp" gives "projects_basecamp",
106
+ # "people_basecamp"). By default, the suffix is the empty string.
107
+ cattr_accessor :table_name_suffix, :instance_writer => false
108
+ @@table_name_suffix = ""
109
+
110
+ # Indicates whether table names should be the pluralized versions of the corresponding class names.
111
+ # If true, the default table name for a +Product+ class will be +products+. If false, it would just be +product+.
112
+ # See table_name for the full rules on table/class naming. This is true, by default.
113
+ cattr_accessor :pluralize_table_names, :instance_writer => false
114
+ @@pluralize_table_names = true
115
+
116
+ # Determines whether or not to use ANSI codes to colorize the logging statements committed by the connection adapter. These colors
117
+ # make it much easier to overview things during debugging (when used through a reader like +tail+ and on a black background), but
118
+ # may complicate matters if you use software like syslog. This is true, by default.
119
+ cattr_accessor :colorize_logging, :instance_writer => false
120
+ @@colorize_logging = true
121
+
122
+ # Determines whether to use Time.local (using :local) or Time.utc (using :utc) when pulling dates and times from the database.
123
+ # This is set to :local by default.
124
+ cattr_accessor :default_timezone, :instance_writer => false
125
+ @@default_timezone = :local
126
+
127
+ # Determines whether to speed up access by generating optimized reader
128
+ # methods to avoid expensive calls to method_missing when accessing
129
+ # attributes by name. You might want to set this to false in development
130
+ # mode, because the methods would be regenerated on each request.
131
+ cattr_accessor :generate_read_methods, :instance_writer => false
132
+ @@generate_read_methods = false
133
+
134
+ # Determines whether or not to use a connection for each thread, or a single shared connection for all threads.
135
+ # Defaults to false. Set to true if you're writing a threaded application.
136
+ cattr_accessor :allow_concurrency, :instance_writer => false
137
+ @@allow_concurrency = false
138
+
139
+ # New objects can be instantiated as either empty (pass no construction parameter) or pre-set with
140
+ # attributes but not yet saved (pass a hash with key names matching the associated table column names).
141
+ # In both instances, valid attribute keys are determined by the column names of the associated table --
142
+ # hence you can't have attributes that aren't part of the table columns.
143
+ def initialize(attrs = nil)
144
+ preinitialize(attrs)
145
+ @attributes = attributes_from_column_definition
146
+ self.attributes = attrs unless attrs.nil?
147
+ end
148
+
149
+ # Callback method meant to be overriden by subclasses if they need to preload some
150
+ # attributes before initializing the record. (usefull when using a meta model where
151
+ # the list of columns depends on the value of an attribute)
152
+ def preinitialize(attrs = nil)
153
+ @attributes = {}
154
+ end
155
+
156
+ def deserialize(attrs = nil)
157
+ end
158
+
159
+ # Safe version of attributes= so that objects can be instantiated even
160
+ # if columns are removed.
161
+ def safe_attributes=(new_attributes, guard_protected_attributes = true)
162
+ return if new_attributes.nil?
163
+ attributes = new_attributes.dup
164
+ attributes.stringify_keys!
165
+
166
+ multi_parameter_attributes = []
167
+ attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
168
+
169
+ attributes.each do |k, v|
170
+ begin
171
+ k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
172
+ rescue
173
+ logger.debug "#{__FILE__}:#{__LINE__} Warning! Ignoring attribute '#{k}' because it doesn't exist anymore"
174
+ end
175
+ end
176
+
177
+ begin
178
+ assign_multiparameter_attributes(multi_parameter_attributes)
179
+ rescue
180
+ logger.debug "#{__FILE__}:#{__LINE__} Warning! Ignoring multiparameter attributes because some don't exist anymore"
181
+ end
182
+ end
183
+
184
+ # A model instance's primary key is always available as model.id
185
+ # whether you name it the default 'id' or set it to something else.
186
+ def id
187
+ attr_name = self.class.primary_key
188
+ c = column_for_attribute(attr_name)
189
+ define_read_method(:id, attr_name, c) if self.class.generate_read_methods
190
+ read_attribute(attr_name)
191
+ end
192
+
193
+ def to_s
194
+ id
195
+ end
196
+
197
+ def attributes()
198
+ @attributes.dup
199
+ end
200
+
201
+ # Reloads the attributes of this object from the database.
202
+ # The optional options argument is passed to find when reloading so you
203
+ # may do e.g. record.reload(:lock => true) to reload the same record with
204
+ # an exclusive row lock.
205
+ def reload(options = nil)
206
+ @attributes.update(self.class.find(self.id, options).instance_variable_get('@attributes'))
207
+ self
208
+ end
209
+
210
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
211
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
212
+ # (Alias for the protected read_attribute method).
213
+ def [](attr_name)
214
+ read_attribute(attr_name)
215
+ end
216
+
217
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+.
218
+ # (Alias for the protected write_attribute method).
219
+ def []=(attr_name, value)
220
+ write_attribute(attr_name, value)
221
+ end
222
+
223
+ # Allows you to set all the attributes at once by passing in a hash with keys
224
+ # matching the attribute names (which again matches the column names). Sensitive attributes can be protected
225
+ # from this form of mass-assignment by using the +attr_protected+ macro. Or you can alternatively
226
+ # specify which attributes *can* be accessed in with the +attr_accessible+ macro. Then all the
227
+ # attributes not included in that won't be allowed to be mass-assigned.
228
+ def attributes=(new_attributes, guard_protected_attributes = true)
229
+ return if new_attributes.nil?
230
+ attributes = new_attributes.dup
231
+ attributes.stringify_keys!
232
+
233
+ multi_parameter_attributes = []
234
+ attributes = remove_attributes_protected_from_mass_assignment(attributes) if guard_protected_attributes
235
+
236
+ attributes.each do |k, v|
237
+ k.include?("(") ? multi_parameter_attributes << [ k, v ] : send(k + "=", v)
238
+ end
239
+
240
+ assign_multiparameter_attributes(multi_parameter_attributes)
241
+ end
242
+
243
+ def all_attributes_loaded=(loaded)
244
+ @all_attributes_loaded = loaded
245
+ end
246
+
247
+ def all_attributes_loaded?
248
+ @all_attributes_loaded
249
+ end
250
+
251
+ # Format attributes nicely for inspect.
252
+ def attribute_for_inspect(attr_name)
253
+ value = read_attribute(attr_name)
254
+
255
+ if value.is_a?(String) && value.length > 50
256
+ "#{value[0..50]}...".inspect
257
+ elsif value.is_a?(Date) || value.is_a?(Time)
258
+ %("#{value.to_s(:db)}")
259
+ else
260
+ value.inspect
261
+ end
262
+ end
263
+
264
+ # Returns true if the specified +attribute+ has been set by the user or by a database load and is neither
265
+ # nil nor empty? (the latter only applies to objects that respond to empty?, most notably Strings).
266
+ def attribute_present?(attribute)
267
+ value = read_attribute(attribute)
268
+ !value.blank? or value == 0
269
+ end
270
+
271
+ # Returns true if the given attribute is in the attributes hash
272
+ def has_attribute?(attr_name)
273
+ @attributes.has_key?(attr_name.to_s)
274
+ end
275
+
276
+ # Returns an array of names for the attributes available on this object sorted alphabetically.
277
+ def attribute_names
278
+ @attributes.keys.sort
279
+ end
280
+
281
+ def human_attribute_name(attribute_key)
282
+ self.class.human_attribute_name(attribute_key)
283
+ end
284
+
285
+ # Overridden by FamilySpanColumns
286
+ # Returns the column object for the named attribute.
287
+ def column_for_attribute(name)
288
+ self.class.columns_hash[name.to_s]
289
+ end
290
+
291
+ # Returns true if the +comparison_object+ is the same object, or is of the same type and has the same id.
292
+ def ==(comparison_object)
293
+ comparison_object.equal?(self) ||
294
+ (comparison_object.instance_of?(self.class) &&
295
+ comparison_object.id == id &&
296
+ !comparison_object.new_record?)
297
+ end
298
+
299
+ # Delegates to ==
300
+ def eql?(comparison_object)
301
+ self == (comparison_object)
302
+ end
303
+
304
+ # Delegates to id in order to allow two records of the same type and id to work with something like:
305
+ # [ Person.find(1), Person.find(2), Person.find(3) ] & [ Person.find(1), Person.find(4) ] # => [ Person.find(1) ]
306
+ def hash
307
+ id.hash
308
+ end
309
+
310
+ # For checking respond_to? without searching the attributes (which is faster).
311
+ alias_method :respond_to_without_attributes?, :respond_to?
312
+
313
+ # A Person object with a name attribute can ask person.respond_to?("name"), person.respond_to?("name="), and
314
+ # person.respond_to?("name?") which will all return true.
315
+ def respond_to?(method, include_priv = false)
316
+ if @attributes.nil?
317
+ return super
318
+ elsif attr_name = self.class.column_methods_hash[method.to_sym]
319
+ return true if @attributes.include?(attr_name) || attr_name == self.class.primary_key
320
+ return false if self.class.read_methods.include?(attr_name)
321
+ elsif @attributes.include?(method.to_s)
322
+ return true
323
+ elsif md = self.class.match_attribute_method?(method.to_s)
324
+ return true if @attributes.include?(md.pre_match)
325
+ end
326
+ # super must be called at the end of the method, because the inherited respond_to?
327
+ # would return true for generated readers, even if the attribute wasn't present
328
+ super
329
+ end
330
+
331
+ # Just freeze the attributes hash, such that associations are still accessible even on destroyed records.
332
+ def freeze
333
+ @attributes.freeze; self
334
+ end
335
+
336
+ def frozen?
337
+ @attributes.frozen?
338
+ end
339
+
340
+ def quoted_id #:nodoc:
341
+ quote_value(id, column_for_attribute(self.class.primary_key))
342
+ end
343
+
344
+ # Sets the primary ID.
345
+ def id=(value)
346
+ write_attribute(self.class.primary_key, value)
347
+ end
348
+
349
+ # Returns true if this object hasn't been saved yet -- that is, a record for the object doesn't exist yet.
350
+ def new_record?
351
+ false
352
+ end
353
+
354
+ # * No record exists: Creates a new record with values matching those of the object attributes.
355
+ # * A record does exist: Updates the record with values matching those of the object attributes.
356
+ def save
357
+ raise NotImplemented
358
+ end
359
+
360
+ # Attempts to save the record, but instead of just returning false if it couldn't happen, it raises a
361
+ # RecordNotSaved exception
362
+ def save!
363
+ raise NotImplemented
364
+ end
365
+
366
+ # Deletes the record in the database and freezes this instance to reflect that no changes should
367
+ # be made (since they can't be persisted).
368
+ def destroy
369
+ raise NotImplemented
370
+ end
371
+
372
+ # Updates a single attribute and saves the record. This is especially useful for boolean flags on existing records.
373
+ # Note: This method is overwritten by the Validation module that'll make sure that updates made with this method
374
+ # doesn't get subjected to validation checks. Hence, attributes can be updated even if the full object isn't valid.
375
+ def update_attribute(name, value)
376
+ raise NotImplemented
377
+ end
378
+
379
+ # Updates all the attributes from the passed-in Hash and saves the record. If the object is invalid, the saving will
380
+ # fail and false will be returned.
381
+ def update_attributes(attributes)
382
+ raise NotImplemented
383
+ end
384
+
385
+ # Updates an object just like Base.update_attributes but calls save! instead of save so an exception is raised if the record is invalid.
386
+ def update_attributes!(attributes)
387
+ raise NotImplemented
388
+ end
389
+
390
+ def connection
391
+ self.class.connection
392
+ end
393
+
394
+ # Records loaded through joins with piggy-back attributes will be marked as read only as they cannot be saved and return true to this query.
395
+ def readonly?
396
+ @readonly == true
397
+ end
398
+
399
+ def readonly! #:nodoc:
400
+ @readonly = true
401
+ end
402
+
403
+ # Returns the contents of the record as a nicely formatted string.
404
+ def inspect
405
+ attributes_as_nice_string = self.class.column_names.collect { |name|
406
+ if has_attribute?(name) || new_record?
407
+ "#{name}: #{attribute_for_inspect(name)}"
408
+ end
409
+ }.compact.join(", ")
410
+ "#<#{self.class} #{attributes_as_nice_string}>"
411
+ end
412
+
413
+ protected
414
+ def clone_in_persistence_format
415
+ validate_attributes_schema
416
+
417
+ data = {}
418
+
419
+ # normalized attributes without the id
420
+ @attributes.keys.each do |key|
421
+ next if !self.class.store_primary_key? and (key == self.class.primary_key)
422
+ value = read_attribute(key)
423
+ if value.kind_of?(Embedded)
424
+ data[key] = value.clone_in_persistence_format
425
+ elsif value.is_a?(Array)
426
+ data[key] = value.collect do |e|
427
+ if e.kind_of?(Embedded)
428
+ e.clone_in_persistence_format
429
+ else
430
+ e
431
+ end
432
+ end
433
+ else
434
+ data[key] = value
435
+ end
436
+ end
437
+ data
438
+ end
439
+
440
+ # Validate the type of the values in the attributes hash
441
+ def validate_attributes_schema
442
+ @attributes.keys.each do |key|
443
+ value = read_attribute(key)
444
+ next unless value
445
+ # type validation
446
+ if (column = column_for_attribute(key)) and !key.ends_with?(":")
447
+ if column.collection?
448
+ unless value.is_a?(Array)
449
+ raise WrongAttributeDataType, "#{human_attribute_name(column.name)} has the wrong type. Expected collection of #{column.klass}. Record is #{value.class}"
450
+ end
451
+ value.each do |v|
452
+ validate_attribute_type(v, column)
453
+ end
454
+ else
455
+ validate_attribute_type(value, column)
456
+ end
457
+ else
458
+ # Don't save attributes set in a previous schema version
459
+ @attributes.delete(key)
460
+ end
461
+ end
462
+ end
463
+
464
+ def validate_attribute_type(value, column)
465
+ unless (value == nil) or value.kind_of?(column.klass)
466
+ raise WrongAttributeDataType, "#{human_attribute_name(column.name)} has the wrong type. Expected #{column.klass}. Record is #{value.class}"
467
+ end
468
+ end
469
+
470
+ # Generate a new id. Override this to use custom ids.
471
+ def generate_new_id
472
+ UUIDTools::UUID.random_create.to_s
473
+ end
474
+
475
+ def self.random_id #not necessarily unique! -- this is strictly for 'stumbling', not for assigning to new entities
476
+ [8,4,4,4,12].map{|l| "%0#{l}x" % rand(1 << l*4) }.join('-')
477
+ end
478
+
479
+ # Initializes the attributes array with keys matching the columns from the linked table and
480
+ # the values matching the corresponding default value of that column, so
481
+ # that a new instance, or one populated from a passed-in Hash, still has all the attributes
482
+ # that instances loaded from the database would.
483
+ def attributes_from_column_definition
484
+ self.class.columns.inject({}) do |attributes, column|
485
+ unless column.name == self.class.primary_key
486
+ attributes[column.name] = column.default
487
+ end
488
+ attributes
489
+ end
490
+ end
491
+
492
+ # Instantiates objects for all attribute classes that needs more than one constructor parameter. This is done
493
+ # by calling new on the column type or aggregation type (through composed_of) object with these parameters.
494
+ # So having the pairs written_on(1) = "2004", written_on(2) = "6", written_on(3) = "24", will instantiate
495
+ # written_on (a date type) with Date.new("2004", "6", "24"). You can also specify a typecast character in the
496
+ # parentheses to have the parameters typecasted before they're used in the constructor. Use i for Fixnum, f for Float,
497
+ # s for String, and a for Array. If all the values for a given attribute are empty, the attribute will be set to nil.
498
+ def assign_multiparameter_attributes(pairs)
499
+ execute_callstack_for_multiparameter_attributes(
500
+ extract_callstack_for_multiparameter_attributes(pairs)
501
+ )
502
+ end
503
+
504
+ # Includes an ugly hack for Time.local instead of Time.new because the latter is reserved by Time itself.
505
+ def execute_callstack_for_multiparameter_attributes(callstack)
506
+ errors = []
507
+ callstack.each do |name, values|
508
+ # TODO: handle aggregation reflections
509
+ # klass = (self.class.reflect_on_aggregation(name.to_sym) || column_for_attribute(name)).klass
510
+ column = column_for_attribute(name)
511
+ if column
512
+ klass = column.klass
513
+
514
+ # Ugly fix for time selectors so that when any value is invalid the value is considered invalid, hence nil
515
+ if values.empty? or (column.type == :time and !values[-2..-1].all?) or ([:date, :datetime].include?(column.type) and !values.all?)
516
+ send(name + "=", nil)
517
+ else
518
+ # End of the ugly time fix...
519
+ values = [2000, 1, 1, values[-2], values[-1]] if column.type == :time and !values[0..2].all?
520
+ begin
521
+ send(name + "=", Time == klass ? (@@default_timezone == :utc ? klass.utc(*values) : klass.local(*values)) : klass.new(*values))
522
+ rescue => ex
523
+ errors << AttributeAssignmentError.new("error on assignment #{values.inspect} to #{name}", ex, name)
524
+ end
525
+ end
526
+ end
527
+ end
528
+ unless errors.empty?
529
+ raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes"
530
+ end
531
+ end
532
+
533
+ def extract_callstack_for_multiparameter_attributes(pairs)
534
+ attributes = { }
535
+
536
+ for pair in pairs
537
+ multiparameter_name, value = pair
538
+ attribute_name = multiparameter_name.split("(").first
539
+ attributes[attribute_name] = [] unless attributes.include?(attribute_name)
540
+
541
+ position = find_parameter_position(multiparameter_name)
542
+ value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
543
+ attributes[attribute_name] << [position, value]
544
+ end
545
+ attributes.each { |name, values| attributes[name] = values.sort_by{ |v| v.first }.collect { |v| v.last } }
546
+ end
547
+
548
+ def type_cast_attribute_value(multiparameter_name, value)
549
+ multiparameter_name =~ /\([0-9]*([a-z])\)/ ? value.send("to_" + $1) : value
550
+ end
551
+
552
+ def find_parameter_position(multiparameter_name)
553
+ multiparameter_name.scan(/\(([0-9]*).*\)/).first.first
554
+ end
555
+
556
+ # Quote strings appropriately for SQL statements.
557
+ def quote_value(value, column = nil)
558
+ self.class.connection.quote(value, column)
559
+ end
560
+
561
+ def create_or_update
562
+ raise NotImplemented
563
+ end
564
+
565
+ # Creates a record with values matching those of the instance attributes
566
+ # and returns its id. Generate a UUID as the row key.
567
+ def create
568
+ raise NotImplemented
569
+ end
570
+
571
+ # Updates the associated record with values matching those of the instance attributes.
572
+ # Returns the number of affected rows.
573
+ def update
574
+ raise NotImplemented
575
+ end
576
+
577
+ # Update this record in hbase. Cannot be directly in the method 'update' because it would trigger callbacks and
578
+ # therefore weird behaviors.
579
+ def update_bigrecord
580
+ timestamp = self.respond_to?(:updated_at) ? self.updated_at.to_bigrecord_timestamp : Time.now.to_bigrecord_timestamp
581
+ connection.update(self.class.table_name, id, clone_in_persistence_format, timestamp)
582
+ end
583
+
584
+ # Allows access to the object attributes, which are held in the @attributes hash, as were
585
+ # they first-class methods. So a Person class with a name attribute can use Person#name and
586
+ # Person#name= and never directly use the attributes hash -- except for multiple assigns with
587
+ # ActiveRecord#attributes=. A Milestone class can also ask Milestone#completed? to test that
588
+ # the completed attribute is not nil or 0.
589
+ #
590
+ # It's also possible to instantiate related objects, so a Client class belonging to the clients
591
+ # table with a master_id foreign key can instantiate master through Client#master.
592
+ def method_missing(method_id, *args, &block)
593
+ method_name = method_id.to_s
594
+ if column_for_attribute(method_name) or
595
+ ((md = /\?$/.match(method_name)) and
596
+ column_for_attribute(query_method_name = md.pre_match) and
597
+ method_name = query_method_name)
598
+ define_read_methods if self.class.read_methods.empty? && self.class.generate_read_methods
599
+ md ? query_attribute(method_name) : read_attribute(method_name)
600
+ elsif self.class.primary_key.to_s == method_name
601
+ id
602
+ elsif (md = self.class.match_attribute_method?(method_name))
603
+ attribute_name, method_type = md.pre_match, md.to_s
604
+ if column_for_attribute(attribute_name)
605
+ __send__("attribute#{method_type}", attribute_name, *args, &block)
606
+ else
607
+ super
608
+ end
609
+ else
610
+ super
611
+ end
612
+ end
613
+
614
+ def remove_attributes_protected_from_mass_assignment(attributes)
615
+ safe_attributes =
616
+ if self.class.accessible_attributes.nil? && self.class.protected_attributes.nil?
617
+ attributes.reject { |key, value| attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
618
+ elsif self.class.protected_attributes.nil?
619
+ attributes.reject { |key, value| !self.class.accessible_attributes.include?(key.gsub(/\(.+/, "")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
620
+ elsif self.class.accessible_attributes.nil?
621
+ attributes.reject { |key, value| self.class.protected_attributes.include?(key.gsub(/\(.+/,"")) || attributes_protected_by_default.include?(key.gsub(/\(.+/, "")) }
622
+ else
623
+ raise "Declare either attr_protected or attr_accessible for #{self.class}, but not both."
624
+ end
625
+
626
+ if !self.new_record? && !self.class.create_accessible_attributes.nil?
627
+ safe_attributes = safe_attributes.delete_if{ |key, value| self.class.create_accessible_attributes.include?(key.gsub(/\(.+/,"")) }
628
+ end
629
+
630
+ removed_attributes = attributes.keys - safe_attributes.keys
631
+
632
+ if removed_attributes.any?
633
+ log_protected_attribute_removal(removed_attributes)
634
+ end
635
+
636
+ safe_attributes
637
+ end
638
+
639
+ # Removes attributes which have been marked as readonly.
640
+ def remove_readonly_attributes(attributes)
641
+ unless self.class.readonly_attributes.nil?
642
+ attributes.delete_if { |key, value| self.class.readonly_attributes.include?(key.gsub(/\(.+/,"")) }
643
+ else
644
+ attributes
645
+ end
646
+ end
647
+
648
+ def log_protected_attribute_removal(*attributes)
649
+ logger.debug "WARNING: Can't mass-assign these protected attributes: #{attributes.join(', ')}"
650
+ end
651
+
652
+ # The primary key and inheritance column can never be set by mass-assignment for security reasons.
653
+ def attributes_protected_by_default
654
+ # default = [ self.class.primary_key, self.class.inheritance_column ]
655
+ # default << 'id' unless self.class.primary_key.eql? 'id'
656
+ # default
657
+ []
658
+ end
659
+
660
+ protected
661
+ # Returns the value of the attribute identified by <tt>attr_name</tt> after it has been typecast (for example,
662
+ # "2004-12-12" in a data column is cast to a date object, like Date.new(2004, 12, 12)).
663
+ def read_attribute(attr_name)
664
+ attr_name = attr_name.to_s
665
+ if !(value = @attributes[attr_name]).nil?
666
+ if column = column_for_attribute(attr_name)
667
+ write_attribute(attr_name, column.type_cast(value))
668
+ else
669
+ value
670
+ end
671
+ else
672
+ nil
673
+ end
674
+ end
675
+
676
+ def read_attribute_before_type_cast(attr_name)
677
+ @attributes[attr_name.to_s]
678
+ end
679
+
680
+ private
681
+ # Called on first read access to any given column and generates reader
682
+ # methods for all columns in the columns_hash if
683
+ # ActiveRecord::Base.generate_read_methods is set to true.
684
+ def define_read_methods
685
+ self.class.columns_hash.each do |name, column|
686
+ unless respond_to_without_attributes?(name)
687
+ define_read_method(name.to_sym, name, column)
688
+ end
689
+
690
+ unless respond_to_without_attributes?("#{name}?")
691
+ define_question_method(name)
692
+ end
693
+ end
694
+ end
695
+
696
+ # Define an attribute reader method. Cope with nil column.
697
+ def define_read_method(symbol, attr_name, column)
698
+ cast_code = column.type_cast_code('v') if column
699
+ access_code = cast_code ? "(v=@attributes['#{attr_name}']) && #{cast_code}" : "@attributes['#{attr_name}']"
700
+
701
+ unless attr_name.to_s == self.class.primary_key.to_s
702
+ access_code = access_code.insert(0, "raise NoMethodError, 'missing attribute: #{attr_name}', caller unless @attributes.has_key?('#{attr_name}'); ")
703
+ self.class.read_methods << attr_name
704
+ end
705
+
706
+ evaluate_read_method attr_name, "def #{symbol}; #{access_code}; end"
707
+ end
708
+
709
+ # Define an attribute ? method.
710
+ def define_question_method(attr_name)
711
+ unless attr_name.to_s == self.class.primary_key.to_s
712
+ self.class.read_methods << "#{attr_name}?"
713
+ end
714
+
715
+ evaluate_read_method attr_name, "def #{attr_name}?; query_attribute('#{attr_name}'); end"
716
+ end
717
+
718
+ # Evaluate the definition for an attribute reader or ? method
719
+ def evaluate_read_method(attr_name, method_definition)
720
+ begin
721
+ self.class.class_eval(method_definition)
722
+ rescue SyntaxError => err
723
+ self.class.read_methods.delete(attr_name)
724
+ if logger
725
+ logger.warn "Exception occurred during reader method compilation."
726
+ logger.warn "Maybe #{attr_name} is not a valid Ruby identifier?"
727
+ logger.warn "#{err.message}"
728
+ end
729
+ end
730
+ end
731
+
732
+ # Updates the attribute identified by <tt>attr_name</tt> with the specified +value+. Empty strings for fixnum and float
733
+ # columns are turned into nil.
734
+ def write_attribute(attr_name, value)
735
+ attr_name = attr_name.to_s
736
+ column = column_for_attribute(attr_name)
737
+
738
+ raise "Invalid column for this bigrecord object (e.g., you tried to set a predicate value for an entity that is out of the predicate scope)" if column == nil
739
+
740
+ if column.number?
741
+ @attributes[attr_name] = convert_number_column_value(value)
742
+ else
743
+ @attributes[attr_name] = value
744
+ end
745
+ end
746
+
747
+ def convert_number_column_value(value)
748
+ case value
749
+ when FalseClass then 0
750
+ when TrueClass then 1
751
+ when '' then nil
752
+ else value
753
+ end
754
+ end
755
+
756
+ public
757
+ class << self
758
+
759
+ # Evaluate the name of the column of the primary key only once
760
+ def primary_key
761
+ raise NotImplemented
762
+ end
763
+
764
+ # # Returns a string like 'Post id:integer, title:string, body:text'
765
+ # def inspect
766
+ # if self == Base
767
+ # super
768
+ # elsif abstract_class?
769
+ # "#{super}(abstract)"
770
+ # elsif table_exists?
771
+ # attr_list = columns.map { |c| "#{c.name}: #{c.type}" } * ', '
772
+ # "#{super}(#{attr_list})"
773
+ # else
774
+ # "#{super}(Table doesn't exist)"
775
+ # end
776
+ # end
777
+
778
+ # Log and benchmark multiple statements in a single block. Example:
779
+ #
780
+ # Project.benchmark("Creating project") do
781
+ # project = Project.create("name" => "stuff")
782
+ # project.create_manager("name" => "David")
783
+ # project.milestones << Milestone.find(:all)
784
+ # end
785
+ #
786
+ # The benchmark is only recorded if the current level of the logger matches the <tt>log_level</tt>, which makes it
787
+ # easy to include benchmarking statements in production software that will remain inexpensive because the benchmark
788
+ # will only be conducted if the log level is low enough.
789
+ #
790
+ # The logging of the multiple statements is turned off unless <tt>use_silence</tt> is set to false.
791
+ def benchmark(title, log_level = Logger::DEBUG, use_silence = true)
792
+ if logger && logger.level == log_level
793
+ result = nil
794
+ seconds = Benchmark.realtime { result = use_silence ? silence { yield } : yield }
795
+ logger.add(log_level, "#{title} (#{'%.5f' % seconds})")
796
+ result
797
+ else
798
+ yield
799
+ end
800
+ end
801
+
802
+ # Silences the logger for the duration of the block.
803
+ def silence
804
+ old_logger_level, logger.level = logger.level, Logger::ERROR if logger
805
+ yield
806
+ ensure
807
+ logger.level = old_logger_level if logger
808
+ end
809
+
810
+ # Overwrite the default class equality method to provide support for association proxies.
811
+ def ===(object)
812
+ object.is_a?(self)
813
+ end
814
+
815
+ def base_class
816
+ raise NotImplemented
817
+ end
818
+
819
+ # Override this method in the subclasses to add new columns. This is different from ActiveRecord because
820
+ # the number of columns in a Hbase table is variable.
821
+ def columns
822
+ @columns = columns_hash.values
823
+ end
824
+
825
+ # Returns a hash of column objects for the table associated with this class.
826
+ def columns_hash
827
+ unless @all_columns_hash
828
+ # add default hbase columns
829
+ @all_columns_hash =
830
+ if self == base_class
831
+ if @columns_hash
832
+ default_columns.merge(@columns_hash)
833
+ else
834
+ @columns_hash = default_columns
835
+ end
836
+ else
837
+ if @columns_hash
838
+ superclass.columns_hash.merge(@columns_hash)
839
+ else
840
+ superclass.columns_hash
841
+ end
842
+ end
843
+ end
844
+ @all_columns_hash
845
+ end
846
+
847
+ # Returns an array of column names as strings.
848
+ def column_names
849
+ @column_names = columns_hash.keys
850
+ end
851
+
852
+ # Returns an array of column objects where the primary id, all columns ending in "_id" or "_count",
853
+ # and columns used for single table inheritance have been removed.
854
+ def content_columns
855
+ @content_columns ||= columns.reject{|c| c.primary || "id"}
856
+ end
857
+
858
+ # Returns a hash of all the methods added to query each of the columns in the table with the name of the method as the key
859
+ # and true as the value. This makes it possible to do O(1) lookups in respond_to? to check if a given method for attribute
860
+ # is available.
861
+ def column_methods_hash #:nodoc:
862
+ @dynamic_methods_hash ||= column_names.inject(Hash.new(false)) do |methods, attr|
863
+ attr_name = attr.to_s
864
+ methods[attr.to_sym] = attr_name
865
+ methods["#{attr}=".to_sym] = attr_name
866
+ methods["#{attr}?".to_sym] = attr_name
867
+ methods["#{attr}_before_type_cast".to_sym] = attr_name
868
+ methods
869
+ end
870
+ end
871
+
872
+ def column(name, type, options={})
873
+ name = name.to_s
874
+
875
+ @columns_hash = default_columns unless @columns_hash
876
+
877
+ # The other variables that are cached and depend on @columns_hash need to be reloaded
878
+ invalidate_columns
879
+
880
+ c = create_column(name, type, options)
881
+ @columns_hash[c.name] = c
882
+
883
+ alias_attribute c.alias, c.name if c.alias
884
+
885
+ c
886
+ end
887
+
888
+ def create_column(name, type, options)
889
+ ConnectionAdapters::Column.new(name, type, options)
890
+ end
891
+
892
+ # Define aliases to the fully qualified attributes
893
+ def alias_attribute(alias_name, fully_qualified_name)
894
+ self.class_eval <<-EOF
895
+ def #{alias_name}
896
+ read_attribute("#{fully_qualified_name}")
897
+ end
898
+ def #{alias_name}=(value)
899
+ write_attribute("#{fully_qualified_name}", value)
900
+ end
901
+ EOF
902
+ end
903
+
904
+ # Contains the names of the generated reader methods.
905
+ def read_methods #:nodoc:
906
+ @read_methods ||= Set.new
907
+ end
908
+
909
+ # Transforms attribute key names into a more humane format, such as "First name" instead of "first_name". Example:
910
+ # Person.human_attribute_name("first_name") # => "First name"
911
+ # Deprecated in favor of just calling "first_name".humanize
912
+ def human_attribute_name(attribute_key_name) #:nodoc:
913
+ attribute_key_name.humanize
914
+ end
915
+
916
+ def quote_value(value, c = nil) #:nodoc:
917
+ connection.quote(value,c)
918
+ end
919
+
920
+ # Finder methods must instantiate through this method.
921
+ def instantiate(raw_record)
922
+ record = self.allocate
923
+ record.deserialize(raw_record)
924
+ record.preinitialize(raw_record)
925
+ record.instance_variable_set(:@new_record, false)
926
+ record.send("safe_attributes=", raw_record, false)
927
+ record
928
+ end
929
+
930
+ # Attributes named in this macro are protected from mass-assignment,
931
+ # such as <tt>new(attributes)</tt>,
932
+ # <tt>update_attributes(attributes)</tt>, or
933
+ # <tt>attributes=(attributes)</tt>.
934
+ #
935
+ # Mass-assignment to these attributes will simply be ignored, to assign
936
+ # to them you can use direct writer methods. This is meant to protect
937
+ # sensitive attributes from being overwritten by malicious users
938
+ # tampering with URLs or forms.
939
+ #
940
+ # class Customer < ActiveRecord::Base
941
+ # attr_protected :credit_rating
942
+ # end
943
+ #
944
+ # customer = Customer.new("name" => David, "credit_rating" => "Excellent")
945
+ # customer.credit_rating # => nil
946
+ # customer.attributes = { "description" => "Jolly fellow", "credit_rating" => "Superb" }
947
+ # customer.credit_rating # => nil
948
+ #
949
+ # customer.credit_rating = "Average"
950
+ # customer.credit_rating # => "Average"
951
+ #
952
+ # To start from an all-closed default and enable attributes as needed,
953
+ # have a look at +attr_accessible+.
954
+ def attr_protected(*attributes)
955
+ write_inheritable_attribute(:attr_protected, Set.new(attributes.map {|a| a.to_s}) + (protected_attributes || []))
956
+ end
957
+
958
+ # Returns an array of all the attributes that have been protected from mass-assignment.
959
+ def protected_attributes # :nodoc:
960
+ read_inheritable_attribute(:attr_protected)
961
+ end
962
+
963
+ # Specifies a white list of model attributes that can be set via
964
+ # mass-assignment, such as <tt>new(attributes)</tt>,
965
+ # <tt>update_attributes(attributes)</tt>, or
966
+ # <tt>attributes=(attributes)</tt>
967
+ #
968
+ # This is the opposite of the +attr_protected+ macro: Mass-assignment
969
+ # will only set attributes in this list, to assign to the rest of
970
+ # attributes you can use direct writer methods. This is meant to protect
971
+ # sensitive attributes from being overwritten by malicious users
972
+ # tampering with URLs or forms. If you'd rather start from an all-open
973
+ # default and restrict attributes as needed, have a look at
974
+ # +attr_protected+.
975
+ #
976
+ # class Customer < ActiveRecord::Base
977
+ # attr_accessible :name, :nickname
978
+ # end
979
+ #
980
+ # customer = Customer.new(:name => "David", :nickname => "Dave", :credit_rating => "Excellent")
981
+ # customer.credit_rating # => nil
982
+ # customer.attributes = { :name => "Jolly fellow", :credit_rating => "Superb" }
983
+ # customer.credit_rating # => nil
984
+ #
985
+ # customer.credit_rating = "Average"
986
+ # customer.credit_rating # => "Average"
987
+ def attr_accessible(*attributes)
988
+ write_inheritable_attribute(:attr_accessible, Set.new(attributes.map(&:to_s)) + (accessible_attributes || []))
989
+ end
990
+
991
+ # Returns an array of all the attributes that have been made accessible to mass-assignment.
992
+ def accessible_attributes # :nodoc:
993
+ read_inheritable_attribute(:attr_accessible)
994
+ end
995
+
996
+ # Attributes listed as readonly can be set for a new record, but will be ignored in database updates afterwards.
997
+ def attr_readonly(*attributes)
998
+ write_inheritable_attribute(:attr_readonly, Set.new(attributes.map(&:to_s)) + (readonly_attributes || []))
999
+ end
1000
+
1001
+ # Returns an array of all the attributes that have been specified as readonly.
1002
+ def readonly_attributes
1003
+ read_inheritable_attribute(:attr_readonly)
1004
+ end
1005
+
1006
+ # Attributes listed as create_accessible work with mass assignment ONLY on creation. After that, any updates
1007
+ # to that attribute will be protected from mass assignment. This differs from attr_readonly since that macro
1008
+ # prevents attributes from ever being changed (even with the explicit setters) after the record is created.
1009
+ #
1010
+ # class Customer < BigRecord::Base
1011
+ # attr_create_accessible :name
1012
+ # end
1013
+ #
1014
+ # customer = Customer.new(:name => "Greg")
1015
+ # customer.name # => "Greg"
1016
+ # customer.save # => true
1017
+ #
1018
+ # customer.attributes = { :name => "Nerd" }
1019
+ # customer.name # => "Greg"
1020
+ # customer.name = "Nerd"
1021
+ # customer.name # => "Nerd"
1022
+ #
1023
+ def attr_create_accessible(*attributes)
1024
+ write_inheritable_attribute(:attr_create_accessible, Set.new(attributes.map(&:to_s)) + (create_accessible_attributes || []))
1025
+ end
1026
+
1027
+ # Returns an array of all the attributes that have been specified as create_accessible.
1028
+ def create_accessible_attributes
1029
+ read_inheritable_attribute(:attr_create_accessible)
1030
+ end
1031
+
1032
+ protected
1033
+ def invalidate_views
1034
+ @views = nil
1035
+ @view_names = nil
1036
+ end
1037
+
1038
+ def invalidate_columns
1039
+ @columns = nil
1040
+ @column_names = nil
1041
+ @content_columns = nil
1042
+ end
1043
+
1044
+ def default_columns
1045
+ raise NotImplemented
1046
+ end
1047
+
1048
+ def default_views
1049
+ {:all=>ConnectionAdapters::View.new('all', nil, self), :default=>ConnectionAdapters::View.new('default', nil, self)}
1050
+ end
1051
+
1052
+ def subclasses #:nodoc:
1053
+ @@subclasses[self] ||= []
1054
+ @@subclasses[self] + @@subclasses[self].inject([]) {|list, subclass| list + subclass.subclasses }
1055
+ end
1056
+
1057
+ # Returns the class type of the record using the current module as a prefix. So descendents of
1058
+ # MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
1059
+ def compute_type(type_name)
1060
+ type_name.constantize
1061
+ end
1062
+
1063
+ public
1064
+ # Guesses the table name, but does not decorate it with prefix and suffix information.
1065
+ def undecorated_table_name(class_name = base_class.name)
1066
+ table_name = Inflector.underscore(Inflector.demodulize(class_name))
1067
+ table_name = Inflector.pluralize(table_name) if pluralize_table_names
1068
+ table_name
1069
+ end
1070
+ end
1071
+
1072
+ protected
1073
+ # Handle *? for method_missing.
1074
+ def attribute?(attribute_name)
1075
+ query_attribute(attribute_name)
1076
+ end
1077
+
1078
+ # Handle *= for method_missing.
1079
+ def attribute=(attribute_name, value)
1080
+ write_attribute(attribute_name, value)
1081
+ end
1082
+
1083
+ # Handle *_before_type_cast for method_missing.
1084
+ def attribute_before_type_cast(attribute_name)
1085
+ read_attribute_before_type_cast(attribute_name)
1086
+ end
1087
+ end
1088
+ end