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,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