bigrecord 0.0.5

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.
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,151 @@
1
+ module BigRecord
2
+ module BrReflection # :nodoc:
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+
6
+ end
7
+
8
+ # Reflection allows you to interrogate Big Record classes and objects about their associations and aggregations.
9
+ # This information can, for example, be used in a form builder that took an Big Record object and created input
10
+ # fields for all of the attributes depending on their type and displayed the associations to other objects.
11
+ #
12
+ # You can find the interface for the AggregateReflection and AssociationReflection classes in the abstract MacroReflection class.
13
+ module ClassMethods
14
+ def create_reflection_big_record(macro, name, options, big_record)
15
+ case macro
16
+ when :has_many_big_records, :belongs_to_big_record, :belongs_to_many, :has_one_big_record, :has_and_belongs_to_many_big_records
17
+ reflection = BrAssociationReflection.new(macro, name, options, big_record)
18
+ when :composed_of_big_record
19
+ reflection = BrAggregateReflection.new(macro, name, options, big_record)
20
+ end
21
+ write_inheritable_hash :reflections, name => reflection
22
+ reflection
23
+ end
24
+ end
25
+
26
+ # TODO: this sucks... aren't there a better way to do it?
27
+ if self.class < ActiveRecord::Base
28
+ class MacroReflectionAbstract < ActiveRecord::Reflection::MacroReflection
29
+
30
+ end
31
+ else
32
+ class MacroReflectionAbstract < BigRecord::ArReflection::MacroReflection
33
+
34
+ end
35
+ end
36
+
37
+ # Holds all the meta-data about an aggregation as it was specified in the Big Record class.
38
+ class BrAggregateReflection < MacroReflectionAbstract #:nodoc:
39
+ def klass
40
+ @klass ||= Object.const_get(options[:class_name] || class_name)
41
+ end
42
+
43
+ private
44
+ def name_to_class_name(name)
45
+ name.capitalize.gsub(/_(.)/) { |s| $1.capitalize }
46
+ end
47
+ end
48
+
49
+ # Holds all the meta-data about an association as it was specified in the Big Record class.
50
+ class BrAssociationReflection < MacroReflectionAbstract #:nodoc:
51
+ def klass
52
+ @klass ||= big_record.send(:compute_type, class_name)
53
+ end
54
+
55
+ def table_name
56
+ @table_name ||= klass.table_name
57
+ end
58
+
59
+ def primary_key_name
60
+ return @primary_key_name if @primary_key_name
61
+ case
62
+ when macro == :belongs_to_big_record
63
+ @primary_key_name = options[:foreign_key] || class_name.foreign_key
64
+ when macro == :belongs_to_many
65
+ @primary_key_name = options[:foreign_key] || "#{big_record.default_family}:#{class_name.tableize}_ids"
66
+ when options[:as]
67
+ @primary_key_name = options[:foreign_key] || "#{big_record.default_family}:#{options[:as]}_id"
68
+ else
69
+ @primary_key_name = options[:foreign_key] || big_record.name.foreign_key
70
+ end
71
+ end
72
+
73
+ def association_foreign_key
74
+ @association_foreign_key ||= @options[:association_foreign_key] || class_name.foreign_key
75
+ end
76
+
77
+ def counter_cache_column
78
+ if options[:counter_cache] == true
79
+ "#{big_record.name.underscore.pluralize}_count"
80
+ elsif options[:counter_cache]
81
+ options[:counter_cache]
82
+ end
83
+ end
84
+
85
+ def through_reflection
86
+ @through_reflection ||= options[:through] ? big_record.reflect_on_association(options[:through]) : false
87
+ end
88
+
89
+ # Gets an array of possible :through source reflection names
90
+ #
91
+ # [singularized, pluralized]
92
+ #
93
+ def source_reflection_names
94
+ @source_reflection_names ||= (options[:source] ? [options[:source]] : [name.to_s.singularize, name]).collect { |n| n.to_sym }
95
+ end
96
+
97
+ # Gets the source of the through reflection. It checks both a singularized and pluralized form for :belongs_to or :has_many.
98
+ # (The :tags association on Tagging below)
99
+ #
100
+ # class Post
101
+ # has_many :tags, :through => :taggings
102
+ # end
103
+ #
104
+ def source_reflection
105
+ return nil unless through_reflection
106
+ @source_reflection ||= source_reflection_names.collect { |name| through_reflection.klass.reflect_on_association(name) }.compact.first
107
+ end
108
+
109
+ def check_validity!
110
+ if options[:through]
111
+ if through_reflection.nil?
112
+ raise HasManyThroughAssociationNotFoundError.new(big_record.name, self)
113
+ end
114
+
115
+ if source_reflection.nil?
116
+ raise HasManyThroughSourceAssociationNotFoundError.new(self)
117
+ end
118
+
119
+ if options[:source_type] && source_reflection.options[:polymorphic].nil?
120
+ raise HasManyThroughAssociationPointlessSourceTypeError.new(big_record.name, self, source_reflection)
121
+ end
122
+
123
+ if source_reflection.options[:polymorphic] && options[:source_type].nil?
124
+ raise HasManyThroughAssociationPolymorphicError.new(big_record.name, self, source_reflection)
125
+ end
126
+
127
+ unless [:belongs_to_big_record, :has_many_big_records].include?(source_reflection.macro) && source_reflection.options[:through].nil?
128
+ raise HasManyThroughSourceAssociationMacroError.new(self)
129
+ end
130
+ end
131
+ end
132
+
133
+ private
134
+ def name_to_class_name(name)
135
+ if name =~ /::/
136
+ name
137
+ else
138
+ if options[:class_name]
139
+ options[:class_name]
140
+ elsif through_reflection # get the class_name of the belongs_to association of the through reflection
141
+ options[:source_type] || source_reflection.class_name
142
+ else
143
+ class_name = name.to_s.camelize
144
+ class_name = class_name.singularize if [ :has_many_big_records, :has_and_belongs_to_many_big_records, :belongs_to_many ].include?(macro)
145
+ class_name
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,367 @@
1
+ require 'observer'
2
+
3
+ module BigRecord
4
+ # Callbacks are hooks into the lifecycle of an Active Record object that allows you to trigger logic
5
+ # before or after an alteration of the object state. This can be used to make sure that associated and
6
+ # dependent objects are deleted when destroy is called (by overwriting before_destroy) or to massage attributes
7
+ # before they're validated (by overwriting before_validation). As an example of the callbacks initiated, consider
8
+ # the Base#save call:
9
+ #
10
+ # * (-) save
11
+ # * (-) valid?
12
+ # * (1) before_validation
13
+ # * (2) before_validation_on_create
14
+ # * (-) validate
15
+ # * (-) validate_on_create
16
+ # * (3) after_validation
17
+ # * (4) after_validation_on_create
18
+ # * (5) before_save
19
+ # * (6) before_create
20
+ # * (-) create
21
+ # * (7) after_create
22
+ # * (8) after_save
23
+ #
24
+ # That's a total of eight callbacks, which gives you immense power to react and prepare for each state in the
25
+ # Active Record lifecycle.
26
+ #
27
+ # Examples:
28
+ # class CreditCard < ActiveRecord::Base
29
+ # # Strip everything but digits, so the user can specify "555 234 34" or
30
+ # # "5552-3434" or both will mean "55523434"
31
+ # def before_validation_on_create
32
+ # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
33
+ # end
34
+ # end
35
+ #
36
+ # class Subscription < ActiveRecord::Base
37
+ # before_create :record_signup
38
+ #
39
+ # private
40
+ # def record_signup
41
+ # self.signed_up_on = Date.today
42
+ # end
43
+ # end
44
+ #
45
+ # class Firm < ActiveRecord::Base
46
+ # # Destroys the associated clients and people when the firm is destroyed
47
+ # before_destroy { |record| Person.destroy_all "firm_id = #{record.id}" }
48
+ # before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
49
+ # end
50
+ #
51
+ # == Inheritable callback queues
52
+ #
53
+ # Besides the overwriteable callback methods, it's also possible to register callbacks through the use of the callback macros.
54
+ # Their main advantage is that the macros add behavior into a callback queue that is kept intact down through an inheritance
55
+ # hierarchy. Example:
56
+ #
57
+ # class Topic < ActiveRecord::Base
58
+ # before_destroy :destroy_author
59
+ # end
60
+ #
61
+ # class Reply < Topic
62
+ # before_destroy :destroy_readers
63
+ # end
64
+ #
65
+ # Now, when Topic#destroy is run only +destroy_author+ is called. When Reply#destroy is run both +destroy_author+ and
66
+ # +destroy_readers+ is called. Contrast this to the situation where we've implemented the save behavior through overwriteable
67
+ # methods:
68
+ #
69
+ # class Topic < ActiveRecord::Base
70
+ # def before_destroy() destroy_author end
71
+ # end
72
+ #
73
+ # class Reply < Topic
74
+ # def before_destroy() destroy_readers end
75
+ # end
76
+ #
77
+ # In that case, Reply#destroy would only run +destroy_readers+ and _not_ +destroy_author+. So use the callback macros when
78
+ # you want to ensure that a certain callback is called for the entire hierarchy and the regular overwriteable methods when you
79
+ # want to leave it up to each descendent to decide whether they want to call +super+ and trigger the inherited callbacks.
80
+ #
81
+ # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the callbacks before specifying the
82
+ # associations. Otherwise, you might trigger the loading of a child before the parent has registered the callbacks and they won't
83
+ # be inherited.
84
+ #
85
+ # == Types of callbacks
86
+ #
87
+ # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
88
+ # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects are the
89
+ # recommended approaches, inline methods using a proc are sometimes appropriate (such as for creating mix-ins), and inline
90
+ # eval methods are deprecated.
91
+ #
92
+ # The method reference callbacks work by specifying a protected or private method available in the object, like this:
93
+ #
94
+ # class Topic < ActiveRecord::Base
95
+ # before_destroy :delete_parents
96
+ #
97
+ # private
98
+ # def delete_parents
99
+ # self.class.delete_all "parent_id = #{id}"
100
+ # end
101
+ # end
102
+ #
103
+ # The callback objects have methods named after the callback called with the record as the only parameter, such as:
104
+ #
105
+ # class BankAccount < ActiveRecord::Base
106
+ # before_save EncryptionWrapper.new("credit_card_number")
107
+ # after_save EncryptionWrapper.new("credit_card_number")
108
+ # after_initialize EncryptionWrapper.new("credit_card_number")
109
+ # end
110
+ #
111
+ # class EncryptionWrapper
112
+ # def initialize(attribute)
113
+ # @attribute = attribute
114
+ # end
115
+ #
116
+ # def before_save(record)
117
+ # record.credit_card_number = encrypt(record.credit_card_number)
118
+ # end
119
+ #
120
+ # def after_save(record)
121
+ # record.credit_card_number = decrypt(record.credit_card_number)
122
+ # end
123
+ #
124
+ # alias_method :after_find, :after_save
125
+ #
126
+ # private
127
+ # def encrypt(value)
128
+ # # Secrecy is committed
129
+ # end
130
+ #
131
+ # def decrypt(value)
132
+ # # Secrecy is unveiled
133
+ # end
134
+ # end
135
+ #
136
+ # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
137
+ # a method by the name of the callback messaged.
138
+ #
139
+ # The callback macros usually accept a symbol for the method they're supposed to run, but you can also pass a "method string",
140
+ # which will then be evaluated within the binding of the callback. Example:
141
+ #
142
+ # class Topic < ActiveRecord::Base
143
+ # before_destroy 'self.class.delete_all "parent_id = #{id}"'
144
+ # end
145
+ #
146
+ # Notice that single plings (') are used so the #{id} part isn't evaluated until the callback is triggered. Also note that these
147
+ # inline callbacks can be stacked just like the regular ones:
148
+ #
149
+ # class Topic < ActiveRecord::Base
150
+ # before_destroy 'self.class.delete_all "parent_id = #{id}"',
151
+ # 'puts "Evaluated after parents are destroyed"'
152
+ # end
153
+ #
154
+ # == The after_find and after_initialize exceptions
155
+ #
156
+ # Because after_find and after_initialize are called for each object found and instantiated by a finder, such as Base.find(:all), we've had
157
+ # to implement a simple performance constraint (50% more speed on a simple test case). Unlike all the other callbacks, after_find and
158
+ # after_initialize will only be run if an explicit implementation is defined (<tt>def after_find</tt>). In that case, all of the
159
+ # callback types will be called.
160
+ #
161
+ # == before_validation* returning statements
162
+ #
163
+ # If the returning value of a before_validation callback can be evaluated to false, the process will be aborted and Base#save will return false.
164
+ # If Base#save! is called it will raise a RecordNotSave error.
165
+ # Nothing will be appended to the errors object.
166
+ #
167
+ # == Cancelling callbacks
168
+ #
169
+ # If a before_* callback returns false, all the later callbacks and the associated action are cancelled. If an after_* callback returns
170
+ # false, all the later callbacks are cancelled. Callbacks are generally run in the order they are defined, with the exception of callbacks
171
+ # defined as methods on the model, which are called last.
172
+ module Callbacks
173
+ CALLBACKS = %w(
174
+ after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
175
+ after_validation before_validation_on_create after_validation_on_create before_validation_on_update
176
+ after_validation_on_update before_destroy after_destroy
177
+ )
178
+
179
+ def self.included(base) #:nodoc:
180
+ base.extend(ClassMethods)
181
+ base.class_eval do
182
+ class << self
183
+ include Observable
184
+ alias_method_chain :instantiate, :callbacks
185
+ end
186
+
187
+ [:initialize, :create_or_update, :valid?, :create, :update, :destroy].each do |method|
188
+ alias_method_chain method, :callbacks
189
+ end
190
+ end
191
+
192
+ CALLBACKS.each do |method|
193
+ base.class_eval <<-"end_eval"
194
+ def self.#{method}(*callbacks, &block)
195
+ callbacks << block if block_given?
196
+ write_inheritable_array(#{method.to_sym.inspect}, callbacks)
197
+ end
198
+ end_eval
199
+ end
200
+ end
201
+
202
+ module ClassMethods #:nodoc:
203
+ def instantiate_with_callbacks(record)
204
+ object = instantiate_without_callbacks(record)
205
+
206
+ if object.respond_to_without_attributes?(:after_find)
207
+ object.send(:callback, :after_find)
208
+ end
209
+
210
+ if object.respond_to_without_attributes?(:after_initialize)
211
+ object.send(:callback, :after_initialize)
212
+ end
213
+
214
+ object
215
+ end
216
+ end
217
+
218
+ # Is called when the object was instantiated by one of the finders, like Base.find.
219
+ #def after_find() end
220
+
221
+ # Is called after the object has been instantiated by a call to Base.new.
222
+ #def after_initialize() end
223
+
224
+ def initialize_with_callbacks(attributes = nil) #:nodoc:
225
+ initialize_without_callbacks(attributes)
226
+ result = yield self if block_given?
227
+ callback(:after_initialize) if respond_to_without_attributes?(:after_initialize)
228
+ result
229
+ end
230
+
231
+ # Is called _before_ Base.save (regardless of whether it's a create or update save).
232
+ def before_save() end
233
+
234
+ # Is called _after_ Base.save (regardless of whether it's a create or update save).
235
+ #
236
+ # class Contact < ActiveRecord::Base
237
+ # after_save { logger.info( 'New contact saved!' ) }
238
+ # end
239
+ def after_save() end
240
+ def create_or_update_with_callbacks #:nodoc:
241
+ return false if callback(:before_save) == false
242
+ result = create_or_update_without_callbacks
243
+ callback(:after_save)
244
+ result
245
+ end
246
+
247
+ # Is called _before_ Base.save on new objects that haven't been saved yet (no record exists).
248
+ def before_create() end
249
+
250
+ # Is called _after_ Base.save on new objects that haven't been saved yet (no record exists).
251
+ def after_create() end
252
+ def create_with_callbacks #:nodoc:
253
+ return false if callback(:before_create) == false
254
+ result = create_without_callbacks
255
+ callback(:after_create)
256
+ result
257
+ end
258
+
259
+ # Is called _before_ Base.save on existing objects that have a record.
260
+ def before_update() end
261
+
262
+ # Is called _after_ Base.save on existing objects that have a record.
263
+ def after_update() end
264
+
265
+ def update_with_callbacks #:nodoc:
266
+ return false if callback(:before_update) == false
267
+ result = update_without_callbacks
268
+ callback(:after_update)
269
+ result
270
+ end
271
+
272
+ # Is called _before_ Validations.validate (which is part of the Base.save call).
273
+ def before_validation() end
274
+
275
+ # Is called _after_ Validations.validate (which is part of the Base.save call).
276
+ def after_validation() end
277
+
278
+ # Is called _before_ Validations.validate (which is part of the Base.save call) on new objects
279
+ # that haven't been saved yet (no record exists).
280
+ def before_validation_on_create() end
281
+
282
+ # Is called _after_ Validations.validate (which is part of the Base.save call) on new objects
283
+ # that haven't been saved yet (no record exists).
284
+ def after_validation_on_create() end
285
+
286
+ # Is called _before_ Validations.validate (which is part of the Base.save call) on
287
+ # existing objects that have a record.
288
+ def before_validation_on_update() end
289
+
290
+ # Is called _after_ Validations.validate (which is part of the Base.save call) on
291
+ # existing objects that have a record.
292
+ def after_validation_on_update() end
293
+
294
+ def valid_with_callbacks? #:nodoc:
295
+ return false if callback(:before_validation) == false
296
+ if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
297
+ return false if result == false
298
+
299
+ result = valid_without_callbacks?
300
+
301
+ callback(:after_validation)
302
+ if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end
303
+
304
+ return result
305
+ end
306
+
307
+ # Is called _before_ Base.destroy.
308
+ #
309
+ # Note: If you need to _destroy_ or _nullify_ associated records first,
310
+ # use the _:dependent_ option on your associations.
311
+ def before_destroy() end
312
+
313
+ # Is called _after_ Base.destroy (and all the attributes have been frozen).
314
+ #
315
+ # class Contact < ActiveRecord::Base
316
+ # after_destroy { |record| logger.info( "Contact #{record.id} was destroyed." ) }
317
+ # end
318
+ def after_destroy() end
319
+ def destroy_with_callbacks #:nodoc:
320
+ return false if callback(:before_destroy) == false
321
+ result = destroy_without_callbacks
322
+ callback(:after_destroy)
323
+ result
324
+ end
325
+
326
+ private
327
+ def callback(method)
328
+ notify(method)
329
+
330
+ callbacks_for(method).each do |callback|
331
+ result = case callback
332
+ when Symbol
333
+ self.send(callback)
334
+ when String
335
+ eval(callback, binding)
336
+ when Proc, Method
337
+ callback.call(self)
338
+ else
339
+ if callback.respond_to?(method)
340
+ callback.send(method, self)
341
+ else
342
+ raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
343
+ end
344
+ end
345
+ return false if (result.is_a?FalseClass)
346
+ end
347
+
348
+ result = send(method) if respond_to_without_attributes?(method)
349
+
350
+ return result
351
+ end
352
+
353
+ def callbacks_for(method)
354
+ self.class.read_inheritable_attribute(method.to_sym) or []
355
+ end
356
+
357
+ def invoke_and_notify(method)
358
+ notify(method)
359
+ send(method) if respond_to_without_attributes?(method)
360
+ end
361
+
362
+ def notify(method) #:nodoc:
363
+ self.class.changed
364
+ self.class.notify_observers(method, self)
365
+ end
366
+ end
367
+ end