duck_record 0.0.1

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