duck_record 0.0.1

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 (41) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +59 -0
  4. data/Rakefile +29 -0
  5. data/lib/duck_record/attribute/user_provided_default.rb +30 -0
  6. data/lib/duck_record/attribute.rb +221 -0
  7. data/lib/duck_record/attribute_assignment.rb +91 -0
  8. data/lib/duck_record/attribute_methods/before_type_cast.rb +76 -0
  9. data/lib/duck_record/attribute_methods/dirty.rb +124 -0
  10. data/lib/duck_record/attribute_methods/read.rb +78 -0
  11. data/lib/duck_record/attribute_methods/write.rb +65 -0
  12. data/lib/duck_record/attribute_methods.rb +332 -0
  13. data/lib/duck_record/attribute_mutation_tracker.rb +113 -0
  14. data/lib/duck_record/attribute_set/builder.rb +124 -0
  15. data/lib/duck_record/attribute_set/yaml_encoder.rb +41 -0
  16. data/lib/duck_record/attribute_set.rb +99 -0
  17. data/lib/duck_record/attributes.rb +262 -0
  18. data/lib/duck_record/base.rb +296 -0
  19. data/lib/duck_record/callbacks.rb +324 -0
  20. data/lib/duck_record/core.rb +253 -0
  21. data/lib/duck_record/define_callbacks.rb +23 -0
  22. data/lib/duck_record/errors.rb +44 -0
  23. data/lib/duck_record/inheritance.rb +130 -0
  24. data/lib/duck_record/locale/en.yml +48 -0
  25. data/lib/duck_record/model_schema.rb +64 -0
  26. data/lib/duck_record/serialization.rb +19 -0
  27. data/lib/duck_record/translation.rb +22 -0
  28. data/lib/duck_record/type/array.rb +36 -0
  29. data/lib/duck_record/type/decimal_without_scale.rb +13 -0
  30. data/lib/duck_record/type/internal/abstract_json.rb +33 -0
  31. data/lib/duck_record/type/json.rb +6 -0
  32. data/lib/duck_record/type/registry.rb +97 -0
  33. data/lib/duck_record/type/serialized.rb +63 -0
  34. data/lib/duck_record/type/text.rb +9 -0
  35. data/lib/duck_record/type/unsigned_integer.rb +15 -0
  36. data/lib/duck_record/type.rb +66 -0
  37. data/lib/duck_record/validations.rb +40 -0
  38. data/lib/duck_record/version.rb +3 -0
  39. data/lib/duck_record.rb +47 -0
  40. data/lib/tasks/acts_as_record_tasks.rake +4 -0
  41. metadata +126 -0
@@ -0,0 +1,296 @@
1
+ require 'yaml'
2
+ require 'active_support/benchmarkable'
3
+ require 'active_support/dependencies'
4
+ require 'active_support/descendants_tracker'
5
+ require 'active_support/time'
6
+ require 'active_support/core_ext/module/attribute_accessors'
7
+ require 'active_support/core_ext/array/extract_options'
8
+ require 'active_support/core_ext/hash/deep_merge'
9
+ require 'active_support/core_ext/hash/slice'
10
+ require 'active_support/core_ext/hash/transform_values'
11
+ require 'active_support/core_ext/string/behavior'
12
+ require 'active_support/core_ext/kernel/singleton_class'
13
+ require 'active_support/core_ext/module/introspection'
14
+ require 'active_support/core_ext/object/duplicable'
15
+ require 'active_support/core_ext/class/subclasses'
16
+ require 'duck_record/define_callbacks'
17
+ require 'duck_record/errors'
18
+ require 'duck_record/attributes'
19
+
20
+ module DuckRecord #:nodoc:
21
+ # = Active Record
22
+ #
23
+ # Active Record objects don't specify their attributes directly, but rather infer them from
24
+ # the table definition with which they're linked. Adding, removing, and changing attributes
25
+ # and their type is done directly in the database. Any change is instantly reflected in the
26
+ # Active Record objects. The mapping that binds a given Active Record class to a certain
27
+ # database table will happen automatically in most common cases, but can be overwritten for the uncommon ones.
28
+ #
29
+ # See the mapping rules in table_name and the full example in link:files/activerecord/README_rdoc.html for more insight.
30
+ #
31
+ # == Creation
32
+ #
33
+ # Active Records accept constructor parameters either in a hash or as a block. The hash
34
+ # method is especially useful when you're receiving the data from somewhere else, like an
35
+ # HTTP request. It works like this:
36
+ #
37
+ # user = User.new(name: 'David', occupation: 'Code Artist')
38
+ # user.name # => 'David'
39
+ #
40
+ # You can also use block initialization:
41
+ #
42
+ # user = User.new do |u|
43
+ # u.name = 'David'
44
+ # u.occupation = 'Code Artist'
45
+ # end
46
+ #
47
+ # And of course you can just create a bare object and specify the attributes after the fact:
48
+ #
49
+ # user = User.new
50
+ # user.name = 'David'
51
+ # user.occupation = 'Code Artist'
52
+ #
53
+ # == Conditions
54
+ #
55
+ # Conditions can either be specified as a string, array, or hash representing the WHERE-part of an SQL statement.
56
+ # The array form is to be used when the condition input is tainted and requires sanitization. The string form can
57
+ # be used for statements that don't involve tainted data. The hash form works much like the array form, except
58
+ # only equality and range is possible. Examples:
59
+ #
60
+ # class User < DuckRecord::Base
61
+ # def self.authenticate_unsafely(user_name, password)
62
+ # where('user_name = '#{user_name}' AND password = '#{password}'').first
63
+ # end
64
+ #
65
+ # def self.authenticate_safely(user_name, password)
66
+ # where('user_name = ? AND password = ?', user_name, password).first
67
+ # end
68
+ #
69
+ # def self.authenticate_safely_simply(user_name, password)
70
+ # where(user_name: user_name, password: password).first
71
+ # end
72
+ # end
73
+ #
74
+ # The <tt>authenticate_unsafely</tt> method inserts the parameters directly into the query
75
+ # and is thus susceptible to SQL-injection attacks if the <tt>user_name</tt> and +password+
76
+ # parameters come directly from an HTTP request. The <tt>authenticate_safely</tt> and
77
+ # <tt>authenticate_safely_simply</tt> both will sanitize the <tt>user_name</tt> and +password+
78
+ # before inserting them in the query, which will ensure that an attacker can't escape the
79
+ # query and fake the login (or worse).
80
+ #
81
+ # When using multiple parameters in the conditions, it can easily become hard to read exactly
82
+ # what the fourth or fifth question mark is supposed to represent. In those cases, you can
83
+ # resort to named bind variables instead. That's done by replacing the question marks with
84
+ # symbols and supplying a hash with values for the matching symbol keys:
85
+ #
86
+ # Company.where(
87
+ # 'id = :id AND name = :name AND division = :division AND created_at > :accounting_date',
88
+ # { id: 3, name: '37signals', division: 'First', accounting_date: '2005-01-01' }
89
+ # ).first
90
+ #
91
+ # Similarly, a simple hash without a statement will generate conditions based on equality with the SQL AND
92
+ # operator. For instance:
93
+ #
94
+ # Student.where(first_name: 'Harvey', status: 1)
95
+ # Student.where(params[:student])
96
+ #
97
+ # A range may be used in the hash to use the SQL BETWEEN operator:
98
+ #
99
+ # Student.where(grade: 9..12)
100
+ #
101
+ # An array may be used in the hash to use the SQL IN operator:
102
+ #
103
+ # Student.where(grade: [9,11,12])
104
+ #
105
+ # When joining tables, nested hashes or keys written in the form 'table_name.column_name'
106
+ # can be used to qualify the table name of a particular condition. For instance:
107
+ #
108
+ # Student.joins(:schools).where(schools: { category: 'public' })
109
+ # Student.joins(:schools).where('schools.category' => 'public' )
110
+ #
111
+ # == Overwriting default accessors
112
+ #
113
+ # All column values are automatically available through basic accessors on the Active Record
114
+ # object, but sometimes you want to specialize this behavior. This can be done by overwriting
115
+ # the default accessors (using the same name as the attribute) and calling
116
+ # +super+ to actually change things.
117
+ #
118
+ # class Song < DuckRecord::Base
119
+ # # Uses an integer of seconds to hold the length of the song
120
+ #
121
+ # def length=(minutes)
122
+ # super(minutes.to_i * 60)
123
+ # end
124
+ #
125
+ # def length
126
+ # super / 60
127
+ # end
128
+ # end
129
+ #
130
+ # == Attribute query methods
131
+ #
132
+ # In addition to the basic accessors, query methods are also automatically available on the Active Record object.
133
+ # Query methods allow you to test whether an attribute value is present.
134
+ # Additionally, when dealing with numeric values, a query method will return false if the value is zero.
135
+ #
136
+ # For example, an Active Record User with the <tt>name</tt> attribute has a <tt>name?</tt> method that you can call
137
+ # to determine whether the user has a name:
138
+ #
139
+ # user = User.new(name: 'David')
140
+ # user.name? # => true
141
+ #
142
+ # anonymous = User.new(name: '')
143
+ # anonymous.name? # => false
144
+ #
145
+ # == Accessing attributes before they have been typecasted
146
+ #
147
+ # Sometimes you want to be able to read the raw attribute data without having the column-determined
148
+ # typecast run its course first. That can be done by using the <tt><attribute>_before_type_cast</tt>
149
+ # accessors that all attributes have. For example, if your Account model has a <tt>balance</tt> attribute,
150
+ # you can call <tt>account.balance_before_type_cast</tt> or <tt>account.id_before_type_cast</tt>.
151
+ #
152
+ # This is especially useful in validation situations where the user might supply a string for an
153
+ # integer field and you want to display the original string back in an error message. Accessing the
154
+ # attribute normally would typecast the string to 0, which isn't what you want.
155
+ #
156
+ # == Dynamic attribute-based finders
157
+ #
158
+ # Dynamic attribute-based finders are a mildly deprecated way of getting (and/or creating) objects
159
+ # by simple queries without turning to SQL. They work by appending the name of an attribute
160
+ # to <tt>find_by_</tt> like <tt>Person.find_by_user_name</tt>.
161
+ # Instead of writing <tt>Person.find_by(user_name: user_name)</tt>, you can use
162
+ # <tt>Person.find_by_user_name(user_name)</tt>.
163
+ #
164
+ # It's possible to add an exclamation point (!) on the end of the dynamic finders to get them to raise an
165
+ # DuckRecord::RecordNotFound error if they do not return any records,
166
+ # like <tt>Person.find_by_last_name!</tt>.
167
+ #
168
+ # It's also possible to use multiple attributes in the same <tt>find_by_</tt> by separating them with
169
+ # '_and_'.
170
+ #
171
+ # Person.find_by(user_name: user_name, password: password)
172
+ # Person.find_by_user_name_and_password(user_name, password) # with dynamic finder
173
+ #
174
+ # It's even possible to call these dynamic finder methods on relations and named scopes.
175
+ #
176
+ # Payment.order('created_on').find_by_amount(50)
177
+ #
178
+ # == Saving arrays, hashes, and other non-mappable objects in text columns
179
+ #
180
+ # Active Record can serialize any object in text columns using YAML. To do so, you must
181
+ # specify this with a call to the class method
182
+ # {serialize}[rdoc-ref:AttributeMethods::Serialization::ClassMethods#serialize].
183
+ # This makes it possible to store arrays, hashes, and other non-mappable objects without doing
184
+ # any additional work.
185
+ #
186
+ # class User < DuckRecord::Base
187
+ # serialize :preferences
188
+ # end
189
+ #
190
+ # user = User.create(preferences: { 'background' => 'black', 'display' => large })
191
+ # User.find(user.id).preferences # => { 'background' => 'black', 'display' => large }
192
+ #
193
+ # You can also specify a class option as the second parameter that'll raise an exception
194
+ # if a serialized object is retrieved as a descendant of a class not in the hierarchy.
195
+ #
196
+ # class User < DuckRecord::Base
197
+ # serialize :preferences, Hash
198
+ # end
199
+ #
200
+ # user = User.create(preferences: %w( one two three ))
201
+ # User.find(user.id).preferences # raises SerializationTypeMismatch
202
+ #
203
+ # When you specify a class option, the default value for that attribute will be a new
204
+ # instance of that class.
205
+ #
206
+ # class User < DuckRecord::Base
207
+ # serialize :preferences, OpenStruct
208
+ # end
209
+ #
210
+ # user = User.new
211
+ # user.preferences.theme_color = 'red'
212
+ #
213
+ #
214
+ # == Single table inheritance
215
+ #
216
+ # Active Record allows inheritance by storing the name of the class in a
217
+ # column that is named 'type' by default. See DuckRecord::Inheritance for
218
+ # more details.
219
+ #
220
+ # == Connection to multiple databases in different models
221
+ #
222
+ # Connections are usually created through
223
+ # {DuckRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] and retrieved
224
+ # by DuckRecord::Base.connection. All classes inheriting from DuckRecord::Base will use this
225
+ # connection. But you can also set a class-specific connection. For example, if Course is an
226
+ # DuckRecord::Base, but resides in a different database, you can just say <tt>Course.establish_connection</tt>
227
+ # and Course and all of its subclasses will use this connection instead.
228
+ #
229
+ # This feature is implemented by keeping a connection pool in DuckRecord::Base that is
230
+ # a hash indexed by the class. If a connection is requested, the
231
+ # {DuckRecord::Base.retrieve_connection}[rdoc-ref:ConnectionHandling#retrieve_connection] method
232
+ # will go up the class-hierarchy until a connection is found in the connection pool.
233
+ #
234
+ # == Exceptions
235
+ #
236
+ # * DuckRecordError - Generic error class and superclass of all other errors raised by Active Record.
237
+ # * AdapterNotSpecified - The configuration hash used in
238
+ # {DuckRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
239
+ # didn't include an <tt>:adapter</tt> key.
240
+ # * AdapterNotFound - The <tt>:adapter</tt> key used in
241
+ # {DuckRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection]
242
+ # specified a non-existent adapter
243
+ # (or a bad spelling of an existing one).
244
+ # * AssociationTypeMismatch - The object assigned to the association wasn't of the type
245
+ # specified in the association definition.
246
+ # * AttributeAssignmentError - An error occurred while doing a mass assignment through the
247
+ # {DuckRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
248
+ # You can inspect the +attribute+ property of the exception object to determine which attribute
249
+ # triggered the error.
250
+ # * ConnectionNotEstablished - No connection has been established.
251
+ # Use {DuckRecord::Base.establish_connection}[rdoc-ref:ConnectionHandling#establish_connection] before querying.
252
+ # * MultiparameterAssignmentErrors - Collection of errors that occurred during a mass assignment using the
253
+ # {DuckRecord::Base#attributes=}[rdoc-ref:AttributeAssignment#attributes=] method.
254
+ # The +errors+ property of this exception contains an array of
255
+ # AttributeAssignmentError
256
+ # objects that should be inspected to determine which attributes triggered the errors.
257
+ # * RecordInvalid - raised by {DuckRecord::Base#save!}[rdoc-ref:Persistence#save!] and
258
+ # {DuckRecord::Base.create!}[rdoc-ref:Persistence::ClassMethods#create!]
259
+ # when the record is invalid.
260
+ # * RecordNotFound - No record responded to the {DuckRecord::Base.find}[rdoc-ref:FinderMethods#find] method.
261
+ # Either the row with the given ID doesn't exist or the row didn't meet the additional restrictions.
262
+ # Some {DuckRecord::Base.find}[rdoc-ref:FinderMethods#find] calls do not raise this exception to signal
263
+ # nothing was found, please check its documentation for further details.
264
+ # * SerializationTypeMismatch - The serialized object wasn't of the class specified as the second parameter.
265
+ # * StatementInvalid - The database server rejected the SQL statement. The precise error is added in the message.
266
+ #
267
+ # *Note*: The attributes listed are class-level attributes (accessible from both the class and instance level).
268
+ # So it's possible to assign a logger to the class through <tt>Base.logger=</tt> which will then be used by all
269
+ # instances in the current object space.
270
+ class Base
271
+ extend ActiveModel::Naming
272
+
273
+ extend ActiveSupport::Benchmarkable
274
+ extend ActiveSupport::DescendantsTracker
275
+
276
+ extend Translation
277
+
278
+ include Core
279
+ include ModelSchema
280
+ include Inheritance
281
+ include AttributeAssignment
282
+ include ActiveModel::Conversion
283
+ include Validations
284
+ include Attributes
285
+ include DefineCallbacks
286
+ include AttributeMethods
287
+ include Callbacks
288
+ include Serialization
289
+
290
+ def persisted?
291
+ false
292
+ end
293
+ end
294
+
295
+ ActiveSupport.run_load_hooks(:duck_record, Base)
296
+ end
@@ -0,0 +1,324 @@
1
+ module DuckRecord
2
+ # = Active Record \Callbacks
3
+ #
4
+ # \Callbacks are hooks into the life cycle of an Active Record object that allow 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 {DuckRecord::Base#destroy}[rdoc-ref:Persistence#destroy] is called (by overwriting +before_destroy+) or
7
+ # to massage attributes before they're validated (by overwriting +before_validation+).
8
+ # As an example of the callbacks initiated, consider the {DuckRecord::Base#save}[rdoc-ref:Persistence#save] call for a new record:
9
+ #
10
+ # * (-) <tt>save</tt>
11
+ # * (-) <tt>valid</tt>
12
+ # * (1) <tt>before_validation</tt>
13
+ # * (-) <tt>validate</tt>
14
+ # * (2) <tt>after_validation</tt>
15
+ # * (3) <tt>before_save</tt>
16
+ # * (4) <tt>before_create</tt>
17
+ # * (-) <tt>create</tt>
18
+ # * (5) <tt>after_create</tt>
19
+ # * (6) <tt>after_save</tt>
20
+ # * (7) <tt>after_commit</tt>
21
+ #
22
+ # Also, an <tt>after_rollback</tt> callback can be configured to be triggered whenever a rollback is issued.
23
+ # Check out DuckRecord::Transactions for more details about <tt>after_commit</tt> and
24
+ # <tt>after_rollback</tt>.
25
+ #
26
+ # Additionally, an <tt>after_touch</tt> callback is triggered whenever an
27
+ # object is touched.
28
+ #
29
+ # Lastly an <tt>after_find</tt> and <tt>after_initialize</tt> callback is triggered for each object that
30
+ # is found and instantiated by a finder, with <tt>after_initialize</tt> being triggered after new objects
31
+ # are instantiated as well.
32
+ #
33
+ # There are nineteen callbacks in total, which give you immense power to react and prepare for each state in the
34
+ # Active Record life cycle. The sequence for calling {DuckRecord::Base#save}[rdoc-ref:Persistence#save] for an existing record is similar,
35
+ # except that each <tt>_create</tt> callback is replaced by the corresponding <tt>_update</tt> callback.
36
+ #
37
+ # Examples:
38
+ # class CreditCard < DuckRecord::Base
39
+ # # Strip everything but digits, so the user can specify "555 234 34" or
40
+ # # "5552-3434" and both will mean "55523434"
41
+ # before_validation(on: :create) do
42
+ # self.number = number.gsub(/[^0-9]/, "") if attribute_present?("number")
43
+ # end
44
+ # end
45
+ #
46
+ # class Subscription < DuckRecord::Base
47
+ # before_create :record_signup
48
+ #
49
+ # private
50
+ # def record_signup
51
+ # self.signed_up_on = Date.today
52
+ # end
53
+ # end
54
+ #
55
+ # class Firm < DuckRecord::Base
56
+ # # Disables access to the system, for associated clients and people when the firm is destroyed
57
+ # before_destroy { |record| Person.where(firm_id: record.id).update_all(access: 'disabled') }
58
+ # before_destroy { |record| Client.where(client_of: record.id).update_all(access: 'disabled') }
59
+ # end
60
+ #
61
+ # == Inheritable callback queues
62
+ #
63
+ # Besides the overwritable callback methods, it's also possible to register callbacks through the
64
+ # use of the callback macros. Their main advantage is that the macros add behavior into a callback
65
+ # queue that is kept intact down through an inheritance hierarchy.
66
+ #
67
+ # class Topic < DuckRecord::Base
68
+ # before_destroy :destroy_author
69
+ # end
70
+ #
71
+ # class Reply < Topic
72
+ # before_destroy :destroy_readers
73
+ # end
74
+ #
75
+ # Now, when <tt>Topic#destroy</tt> is run only +destroy_author+ is called. When <tt>Reply#destroy</tt> is
76
+ # run, both +destroy_author+ and +destroy_readers+ are called. Contrast this to the following situation
77
+ # where the +before_destroy+ method is overridden:
78
+ #
79
+ # class Topic < DuckRecord::Base
80
+ # def before_destroy() destroy_author end
81
+ # end
82
+ #
83
+ # class Reply < Topic
84
+ # def before_destroy() destroy_readers end
85
+ # end
86
+ #
87
+ # In that case, <tt>Reply#destroy</tt> would only run +destroy_readers+ and _not_ +destroy_author+.
88
+ # So, use the callback macros when you want to ensure that a certain callback is called for the entire
89
+ # hierarchy, and use the regular overwritable methods when you want to leave it up to each descendant
90
+ # to decide whether they want to call +super+ and trigger the inherited callbacks.
91
+ #
92
+ # *IMPORTANT:* In order for inheritance to work for the callback queues, you must specify the
93
+ # callbacks before specifying the associations. Otherwise, you might trigger the loading of a
94
+ # child before the parent has registered the callbacks and they won't be inherited.
95
+ #
96
+ # == Types of callbacks
97
+ #
98
+ # There are four types of callbacks accepted by the callback macros: Method references (symbol), callback objects,
99
+ # inline methods (using a proc), and inline eval methods (using a string). Method references and callback objects
100
+ # are the recommended approaches, inline methods using a proc are sometimes appropriate (such as for
101
+ # creating mix-ins), and inline eval methods are deprecated.
102
+ #
103
+ # The method reference callbacks work by specifying a protected or private method available in the object, like this:
104
+ #
105
+ # class Topic < DuckRecord::Base
106
+ # before_destroy :delete_parents
107
+ #
108
+ # private
109
+ # def delete_parents
110
+ # self.class.delete_all "parent_id = #{id}"
111
+ # end
112
+ # end
113
+ #
114
+ # The callback objects have methods named after the callback called with the record as the only parameter, such as:
115
+ #
116
+ # class BankAccount < DuckRecord::Base
117
+ # before_save EncryptionWrapper.new
118
+ # after_save EncryptionWrapper.new
119
+ # after_initialize EncryptionWrapper.new
120
+ # end
121
+ #
122
+ # class EncryptionWrapper
123
+ # def before_save(record)
124
+ # record.credit_card_number = encrypt(record.credit_card_number)
125
+ # end
126
+ #
127
+ # def after_save(record)
128
+ # record.credit_card_number = decrypt(record.credit_card_number)
129
+ # end
130
+ #
131
+ # alias_method :after_initialize, :after_save
132
+ #
133
+ # private
134
+ # def encrypt(value)
135
+ # # Secrecy is committed
136
+ # end
137
+ #
138
+ # def decrypt(value)
139
+ # # Secrecy is unveiled
140
+ # end
141
+ # end
142
+ #
143
+ # So you specify the object you want messaged on a given callback. When that callback is triggered, the object has
144
+ # a method by the name of the callback messaged. You can make these callbacks more flexible by passing in other
145
+ # initialization data such as the name of the attribute to work with:
146
+ #
147
+ # class BankAccount < DuckRecord::Base
148
+ # before_save EncryptionWrapper.new("credit_card_number")
149
+ # after_save EncryptionWrapper.new("credit_card_number")
150
+ # after_initialize EncryptionWrapper.new("credit_card_number")
151
+ # end
152
+ #
153
+ # class EncryptionWrapper
154
+ # def initialize(attribute)
155
+ # @attribute = attribute
156
+ # end
157
+ #
158
+ # def before_save(record)
159
+ # record.send("#{@attribute}=", encrypt(record.send("#{@attribute}")))
160
+ # end
161
+ #
162
+ # def after_save(record)
163
+ # record.send("#{@attribute}=", decrypt(record.send("#{@attribute}")))
164
+ # end
165
+ #
166
+ # alias_method :after_initialize, :after_save
167
+ #
168
+ # private
169
+ # def encrypt(value)
170
+ # # Secrecy is committed
171
+ # end
172
+ #
173
+ # def decrypt(value)
174
+ # # Secrecy is unveiled
175
+ # end
176
+ # end
177
+ #
178
+ # == <tt>before_validation*</tt> returning statements
179
+ #
180
+ # If the +before_validation+ callback throws +:abort+, the process will be
181
+ # aborted and {DuckRecord::Base#save}[rdoc-ref:Persistence#save] will return +false+.
182
+ # If {DuckRecord::Base#save!}[rdoc-ref:Persistence#save!] is called it will raise an DuckRecord::RecordInvalid exception.
183
+ # Nothing will be appended to the errors object.
184
+ #
185
+ # == Canceling callbacks
186
+ #
187
+ # If a <tt>before_*</tt> callback throws +:abort+, all the later callbacks and
188
+ # the associated action are cancelled.
189
+ # Callbacks are generally run in the order they are defined, with the exception of callbacks defined as
190
+ # methods on the model, which are called last.
191
+ #
192
+ # == Ordering callbacks
193
+ #
194
+ # Sometimes the code needs that the callbacks execute in a specific order. For example, a +before_destroy+
195
+ # callback (+log_children+ in this case) should be executed before the children get destroyed by the
196
+ # <tt>dependent: :destroy</tt> option.
197
+ #
198
+ # Let's look at the code below:
199
+ #
200
+ # class Topic < DuckRecord::Base
201
+ # has_many :children, dependent: :destroy
202
+ #
203
+ # before_destroy :log_children
204
+ #
205
+ # private
206
+ # def log_children
207
+ # # Child processing
208
+ # end
209
+ # end
210
+ #
211
+ # In this case, the problem is that when the +before_destroy+ callback is executed, the children are not available
212
+ # because the {DuckRecord::Base#destroy}[rdoc-ref:Persistence#destroy] callback gets executed first.
213
+ # You can use the +prepend+ option on the +before_destroy+ callback to avoid this.
214
+ #
215
+ # class Topic < DuckRecord::Base
216
+ # has_many :children, dependent: :destroy
217
+ #
218
+ # before_destroy :log_children, prepend: true
219
+ #
220
+ # private
221
+ # def log_children
222
+ # # Child processing
223
+ # end
224
+ # end
225
+ #
226
+ # This way, the +before_destroy+ gets executed before the <tt>dependent: :destroy</tt> is called, and the data is still available.
227
+ #
228
+ # Also, there are cases when you want several callbacks of the same type to
229
+ # be executed in order.
230
+ #
231
+ # For example:
232
+ #
233
+ # class Topic
234
+ # has_many :children
235
+ #
236
+ # after_save :log_children
237
+ # after_save :do_something_else
238
+ #
239
+ # private
240
+ #
241
+ # def log_chidren
242
+ # # Child processing
243
+ # end
244
+ #
245
+ # def do_something_else
246
+ # # Something else
247
+ # end
248
+ # end
249
+ #
250
+ # In this case the +log_children+ gets executed before +do_something_else+.
251
+ # The same applies to all non-transactional callbacks.
252
+ #
253
+ # In case there are multiple transactional callbacks as seen below, the order
254
+ # is reversed.
255
+ #
256
+ # For example:
257
+ #
258
+ # class Topic
259
+ # has_many :children
260
+ #
261
+ # after_commit :log_children
262
+ # after_commit :do_something_else
263
+ #
264
+ # private
265
+ #
266
+ # def log_chidren
267
+ # # Child processing
268
+ # end
269
+ #
270
+ # def do_something_else
271
+ # # Something else
272
+ # end
273
+ # end
274
+ #
275
+ # In this case the +do_something_else+ gets executed before +log_children+.
276
+ #
277
+ # == \Transactions
278
+ #
279
+ # The entire callback chain of a {#save}[rdoc-ref:Persistence#save], {#save!}[rdoc-ref:Persistence#save!],
280
+ # or {#destroy}[rdoc-ref:Persistence#destroy] call runs within a transaction. That includes <tt>after_*</tt> hooks.
281
+ # If everything goes fine a COMMIT is executed once the chain has been completed.
282
+ #
283
+ # If a <tt>before_*</tt> callback cancels the action a ROLLBACK is issued. You
284
+ # can also trigger a ROLLBACK raising an exception in any of the callbacks,
285
+ # including <tt>after_*</tt> hooks. Note, however, that in that case the client
286
+ # needs to be aware of it because an ordinary {#save}[rdoc-ref:Persistence#save] will raise such exception
287
+ # instead of quietly returning +false+.
288
+ #
289
+ # == Debugging callbacks
290
+ #
291
+ # The callback chain is accessible via the <tt>_*_callbacks</tt> method on an object. Active Model \Callbacks support
292
+ # <tt>:before</tt>, <tt>:after</tt> and <tt>:around</tt> as values for the <tt>kind</tt> property. The <tt>kind</tt> property
293
+ # defines what part of the chain the callback runs in.
294
+ #
295
+ # To find all callbacks in the before_save callback chain:
296
+ #
297
+ # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }
298
+ #
299
+ # Returns an array of callback objects that form the before_save chain.
300
+ #
301
+ # To further check if the before_save chain contains a proc defined as <tt>rest_when_dead</tt> use the <tt>filter</tt> property of the callback object:
302
+ #
303
+ # Topic._save_callbacks.select { |cb| cb.kind.eql?(:before) }.collect(&:filter).include?(:rest_when_dead)
304
+ #
305
+ # Returns true or false depending on whether the proc is contained in the before_save callback chain on a Topic model.
306
+ #
307
+ module Callbacks
308
+ extend ActiveSupport::Concern
309
+
310
+ CALLBACKS = [
311
+ :after_initialize, :before_validation, :after_validation
312
+ ]
313
+
314
+ module ClassMethods # :nodoc:
315
+ include ActiveModel::Callbacks
316
+ end
317
+
318
+ included do
319
+ include ActiveModel::Validations::Callbacks
320
+
321
+ define_model_callbacks :initialize, :only => :after
322
+ end
323
+ end
324
+ end