tallty_duck_record 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +41 -0
  3. data/README.md +82 -0
  4. data/Rakefile +28 -0
  5. data/lib/core_ext/array_without_blank.rb +46 -0
  6. data/lib/duck_record.rb +65 -0
  7. data/lib/duck_record/associations.rb +130 -0
  8. data/lib/duck_record/associations/association.rb +271 -0
  9. data/lib/duck_record/associations/belongs_to_association.rb +71 -0
  10. data/lib/duck_record/associations/builder/association.rb +127 -0
  11. data/lib/duck_record/associations/builder/belongs_to.rb +44 -0
  12. data/lib/duck_record/associations/builder/collection_association.rb +45 -0
  13. data/lib/duck_record/associations/builder/embeds_many.rb +9 -0
  14. data/lib/duck_record/associations/builder/embeds_one.rb +9 -0
  15. data/lib/duck_record/associations/builder/has_many.rb +11 -0
  16. data/lib/duck_record/associations/builder/has_one.rb +20 -0
  17. data/lib/duck_record/associations/builder/singular_association.rb +33 -0
  18. data/lib/duck_record/associations/collection_association.rb +476 -0
  19. data/lib/duck_record/associations/collection_proxy.rb +1160 -0
  20. data/lib/duck_record/associations/embeds_association.rb +92 -0
  21. data/lib/duck_record/associations/embeds_many_association.rb +203 -0
  22. data/lib/duck_record/associations/embeds_many_proxy.rb +892 -0
  23. data/lib/duck_record/associations/embeds_one_association.rb +48 -0
  24. data/lib/duck_record/associations/foreign_association.rb +11 -0
  25. data/lib/duck_record/associations/has_many_association.rb +17 -0
  26. data/lib/duck_record/associations/has_one_association.rb +39 -0
  27. data/lib/duck_record/associations/singular_association.rb +73 -0
  28. data/lib/duck_record/attribute.rb +213 -0
  29. data/lib/duck_record/attribute/user_provided_default.rb +30 -0
  30. data/lib/duck_record/attribute_assignment.rb +118 -0
  31. data/lib/duck_record/attribute_decorators.rb +89 -0
  32. data/lib/duck_record/attribute_methods.rb +325 -0
  33. data/lib/duck_record/attribute_methods/before_type_cast.rb +76 -0
  34. data/lib/duck_record/attribute_methods/dirty.rb +107 -0
  35. data/lib/duck_record/attribute_methods/read.rb +78 -0
  36. data/lib/duck_record/attribute_methods/serialization.rb +66 -0
  37. data/lib/duck_record/attribute_methods/write.rb +70 -0
  38. data/lib/duck_record/attribute_mutation_tracker.rb +108 -0
  39. data/lib/duck_record/attribute_set.rb +98 -0
  40. data/lib/duck_record/attribute_set/yaml_encoder.rb +41 -0
  41. data/lib/duck_record/attributes.rb +262 -0
  42. data/lib/duck_record/base.rb +300 -0
  43. data/lib/duck_record/callbacks.rb +324 -0
  44. data/lib/duck_record/coders/json.rb +13 -0
  45. data/lib/duck_record/coders/yaml_column.rb +48 -0
  46. data/lib/duck_record/core.rb +262 -0
  47. data/lib/duck_record/define_callbacks.rb +23 -0
  48. data/lib/duck_record/enum.rb +139 -0
  49. data/lib/duck_record/errors.rb +71 -0
  50. data/lib/duck_record/inheritance.rb +130 -0
  51. data/lib/duck_record/locale/en.yml +46 -0
  52. data/lib/duck_record/model_schema.rb +71 -0
  53. data/lib/duck_record/nested_attributes.rb +555 -0
  54. data/lib/duck_record/nested_validate_association.rb +262 -0
  55. data/lib/duck_record/persistence.rb +39 -0
  56. data/lib/duck_record/readonly_attributes.rb +36 -0
  57. data/lib/duck_record/reflection.rb +650 -0
  58. data/lib/duck_record/serialization.rb +26 -0
  59. data/lib/duck_record/translation.rb +22 -0
  60. data/lib/duck_record/type.rb +77 -0
  61. data/lib/duck_record/type/array.rb +36 -0
  62. data/lib/duck_record/type/array_without_blank.rb +36 -0
  63. data/lib/duck_record/type/date.rb +7 -0
  64. data/lib/duck_record/type/date_time.rb +7 -0
  65. data/lib/duck_record/type/decimal_without_scale.rb +13 -0
  66. data/lib/duck_record/type/internal/abstract_json.rb +33 -0
  67. data/lib/duck_record/type/internal/timezone.rb +15 -0
  68. data/lib/duck_record/type/json.rb +6 -0
  69. data/lib/duck_record/type/registry.rb +97 -0
  70. data/lib/duck_record/type/serialized.rb +63 -0
  71. data/lib/duck_record/type/text.rb +9 -0
  72. data/lib/duck_record/type/time.rb +19 -0
  73. data/lib/duck_record/type/unsigned_integer.rb +15 -0
  74. data/lib/duck_record/validations.rb +67 -0
  75. data/lib/duck_record/validations/subset.rb +74 -0
  76. data/lib/duck_record/validations/uniqueness_on_real_record.rb +248 -0
  77. data/lib/duck_record/version.rb +3 -0
  78. data/lib/tasks/acts_as_record_tasks.rake +4 -0
  79. metadata +181 -0
@@ -0,0 +1,41 @@
1
+ module DuckRecord
2
+ class AttributeSet
3
+ # Attempts to do more intelligent YAML dumping of an
4
+ # DuckRecord::AttributeSet to reduce the size of the resulting string
5
+ class YAMLEncoder # :nodoc:
6
+ def initialize(default_types)
7
+ @default_types = default_types
8
+ end
9
+
10
+ def encode(attribute_set, coder)
11
+ coder["concise_attributes"] = attribute_set.each_value.map do |attr|
12
+ if attr.type.equal?(default_types[attr.name])
13
+ attr.with_type(nil)
14
+ else
15
+ attr
16
+ end
17
+ end
18
+ end
19
+
20
+ def decode(coder)
21
+ if coder["attributes"]
22
+ coder["attributes"]
23
+ else
24
+ attributes_hash = Hash[coder["concise_attributes"].map do |attr|
25
+ if attr.type.nil?
26
+ attr = attr.with_type(default_types[attr.name])
27
+ end
28
+ [attr.name, attr]
29
+ end]
30
+ AttributeSet.new(attributes_hash)
31
+ end
32
+ end
33
+
34
+ # TODO Change this to private once we've dropped Ruby 2.2 support.
35
+ # Workaround for Ruby 2.2 'private attribute?' warning.
36
+ protected
37
+
38
+ attr_reader :default_types
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,262 @@
1
+ require "duck_record/attribute/user_provided_default"
2
+
3
+ module DuckRecord
4
+ # See DuckRecord::Attributes::ClassMethods for documentation
5
+ module Attributes
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ class_attribute :attributes_to_define_after_schema_loads, instance_accessor: false # :internal:
10
+ self.attributes_to_define_after_schema_loads = {}
11
+ end
12
+
13
+ module ClassMethods
14
+ # Defines an attribute with a type on this model. It will override the
15
+ # type of existing attributes if needed. This allows control over how
16
+ # values are converted to and from SQL when assigned to a model. It also
17
+ # changes the behavior of values passed to
18
+ # {DuckRecord::Base.where}[rdoc-ref:QueryMethods#where]. This will let you use
19
+ # your domain objects across much of Active Record, without having to
20
+ # rely on implementation details or monkey patching.
21
+ #
22
+ # +name+ The name of the methods to define attribute methods for, and the
23
+ # column which this will persist to.
24
+ #
25
+ # +cast_type+ A symbol such as +:string+ or +:integer+, or a type object
26
+ # to be used for this attribute. See the examples below for more
27
+ # information about providing custom type objects.
28
+ #
29
+ # ==== Options
30
+ #
31
+ # The following options are accepted:
32
+ #
33
+ # +default+ The default value to use when no value is provided. If this option
34
+ # is not passed, the previous default value (if any) will be used.
35
+ # Otherwise, the default will be +nil+.
36
+ #
37
+ # +array+ (PostgreSQL only) specifies that the type should be an array (see the
38
+ # examples below).
39
+ #
40
+ # +range+ (PostgreSQL only) specifies that the type should be a range (see the
41
+ # examples below).
42
+ #
43
+ # ==== Examples
44
+ #
45
+ # The type detected by Active Record can be overridden.
46
+ #
47
+ # # db/schema.rb
48
+ # create_table :store_listings, force: true do |t|
49
+ # t.decimal :price_in_cents
50
+ # end
51
+ #
52
+ # # app/models/store_listing.rb
53
+ # class StoreListing < DuckRecord::Base
54
+ # end
55
+ #
56
+ # store_listing = StoreListing.new(price_in_cents: '10.1')
57
+ #
58
+ # # before
59
+ # store_listing.price_in_cents # => BigDecimal.new(10.1)
60
+ #
61
+ # class StoreListing < DuckRecord::Base
62
+ # attribute :price_in_cents, :integer
63
+ # end
64
+ #
65
+ # # after
66
+ # store_listing.price_in_cents # => 10
67
+ #
68
+ # A default can also be provided.
69
+ #
70
+ # # db/schema.rb
71
+ # create_table :store_listings, force: true do |t|
72
+ # t.string :my_string, default: "original default"
73
+ # end
74
+ #
75
+ # StoreListing.new.my_string # => "original default"
76
+ #
77
+ # # app/models/store_listing.rb
78
+ # class StoreListing < DuckRecord::Base
79
+ # attribute :my_string, :string, default: "new default"
80
+ # end
81
+ #
82
+ # StoreListing.new.my_string # => "new default"
83
+ #
84
+ # class Product < DuckRecord::Base
85
+ # attribute :my_default_proc, :datetime, default: -> { Time.now }
86
+ # end
87
+ #
88
+ # Product.new.my_default_proc # => 2015-05-30 11:04:48 -0600
89
+ # sleep 1
90
+ # Product.new.my_default_proc # => 2015-05-30 11:04:49 -0600
91
+ #
92
+ # \Attributes do not need to be backed by a database column.
93
+ #
94
+ # # app/models/my_model.rb
95
+ # class MyModel < DuckRecord::Base
96
+ # attribute :my_string, :string
97
+ # attribute :my_int_array, :integer, array: true
98
+ # attribute :my_float_range, :float, range: true
99
+ # end
100
+ #
101
+ # model = MyModel.new(
102
+ # my_string: "string",
103
+ # my_int_array: ["1", "2", "3"],
104
+ # my_float_range: "[1,3.5]",
105
+ # )
106
+ # model.attributes
107
+ # # =>
108
+ # {
109
+ # my_string: "string",
110
+ # my_int_array: [1, 2, 3],
111
+ # my_float_range: 1.0..3.5
112
+ # }
113
+ #
114
+ # ==== Creating Custom Types
115
+ #
116
+ # Users may also define their own custom types, as long as they respond
117
+ # to the methods defined on the value type. The method +deserialize+ or
118
+ # +cast+ will be called on your type object, with raw input from the
119
+ # database or from your controllers. See ActiveModel::Type::Value for the
120
+ # expected API. It is recommended that your type objects inherit from an
121
+ # existing type, or from DuckRecord::Type::Value
122
+ #
123
+ # class MoneyType < DuckRecord::Type::Integer
124
+ # def cast(value)
125
+ # if !value.kind_of?(Numeric) && value.include?('$')
126
+ # price_in_dollars = value.gsub(/\$/, '').to_f
127
+ # super(price_in_dollars * 100)
128
+ # else
129
+ # super
130
+ # end
131
+ # end
132
+ # end
133
+ #
134
+ # # config/initializers/types.rb
135
+ # DuckRecord::Type.register(:money, MoneyType)
136
+ #
137
+ # # app/models/store_listing.rb
138
+ # class StoreListing < DuckRecord::Base
139
+ # attribute :price_in_cents, :money
140
+ # end
141
+ #
142
+ # store_listing = StoreListing.new(price_in_cents: '$10.00')
143
+ # store_listing.price_in_cents # => 1000
144
+ #
145
+ # For more details on creating custom types, see the documentation for
146
+ # ActiveModel::Type::Value. For more details on registering your types
147
+ # to be referenced by a symbol, see DuckRecord::Type.register. You can
148
+ # also pass a type object directly, in place of a symbol.
149
+ #
150
+ # ==== \Querying
151
+ #
152
+ # When {DuckRecord::Base.where}[rdoc-ref:QueryMethods#where] is called, it will
153
+ # use the type defined by the model class to convert the value to SQL,
154
+ # calling +serialize+ on your type object. For example:
155
+ #
156
+ # class Money < Struct.new(:amount, :currency)
157
+ # end
158
+ #
159
+ # class MoneyType < Type::Value
160
+ # def initialize(currency_converter:)
161
+ # @currency_converter = currency_converter
162
+ # end
163
+ #
164
+ # # value will be the result of +deserialize+ or
165
+ # # +cast+. Assumed to be an instance of +Money+ in
166
+ # # this case.
167
+ # def serialize(value)
168
+ # value_in_bitcoins = @currency_converter.convert_to_bitcoins(value)
169
+ # value_in_bitcoins.amount
170
+ # end
171
+ # end
172
+ #
173
+ # # config/initializers/types.rb
174
+ # DuckRecord::Type.register(:money, MoneyType)
175
+ #
176
+ # # app/models/product.rb
177
+ # class Product < DuckRecord::Base
178
+ # currency_converter = ConversionRatesFromTheInternet.new
179
+ # attribute :price_in_bitcoins, :money, currency_converter: currency_converter
180
+ # end
181
+ #
182
+ # Product.where(price_in_bitcoins: Money.new(5, "USD"))
183
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.02230
184
+ #
185
+ # Product.where(price_in_bitcoins: Money.new(5, "GBP"))
186
+ # # => SELECT * FROM products WHERE price_in_bitcoins = 0.03412
187
+ #
188
+ # ==== Dirty Tracking
189
+ #
190
+ # The type of an attribute is given the opportunity to change how dirty
191
+ # tracking is performed. The methods +changed?+ and +changed_in_place?+
192
+ # will be called from ActiveModel::Dirty. See the documentation for those
193
+ # methods in ActiveModel::Type::Value for more details.
194
+ def attribute(name, cast_type = Type::Value.new, **options)
195
+ name = name.to_s
196
+ reload_schema_from_cache
197
+
198
+ self.attributes_to_define_after_schema_loads =
199
+ attributes_to_define_after_schema_loads.merge(
200
+ name => [cast_type, options]
201
+ )
202
+ end
203
+
204
+ # This is the low level API which sits beneath +attribute+. It only
205
+ # accepts type objects, and will do its work immediately instead of
206
+ # waiting for the schema to load. Automatic schema detection and
207
+ # ClassMethods#attribute both call this under the hood. While this method
208
+ # is provided so it can be used by plugin authors, application code
209
+ # should probably use ClassMethods#attribute.
210
+ #
211
+ # +name+ The name of the attribute being defined. Expected to be a +String+.
212
+ #
213
+ # +cast_type+ The type object to use for this attribute.
214
+ #
215
+ # +default+ The default value to use when no value is provided. If this option
216
+ # is not passed, the previous default value (if any) will be used.
217
+ # Otherwise, the default will be +nil+. A proc can also be passed, and
218
+ # will be called once each time a new value is needed.
219
+ #
220
+ # +user_provided_default+ Whether the default value should be cast using
221
+ # +cast+ or +deserialize+.
222
+ def define_attribute(
223
+ name,
224
+ cast_type,
225
+ default: NO_DEFAULT_PROVIDED
226
+ )
227
+ attribute_types[name] = cast_type
228
+ define_default_attribute(name, default, cast_type)
229
+ end
230
+
231
+ def load_schema! # :nodoc:
232
+ super
233
+ attributes_to_define_after_schema_loads.each do |name, (type, options)|
234
+ if type.is_a?(Symbol)
235
+ type = DuckRecord::Type.lookup(type, **options.except(:default))
236
+ end
237
+
238
+ define_attribute(name, type, **options.slice(:default))
239
+ end
240
+ end
241
+
242
+ private
243
+
244
+ NO_DEFAULT_PROVIDED = Object.new # :nodoc:
245
+ private_constant :NO_DEFAULT_PROVIDED
246
+
247
+ def define_default_attribute(name, value, type)
248
+ if value == NO_DEFAULT_PROVIDED
249
+ default_attribute = _default_attributes[name].with_type(type)
250
+ else
251
+ default_attribute = Attribute::UserProvidedDefault.new(
252
+ name,
253
+ value,
254
+ type,
255
+ _default_attributes.fetch(name.to_s) { nil },
256
+ )
257
+ end
258
+ _default_attributes[name] = default_attribute
259
+ end
260
+ end
261
+ end
262
+ end
@@ -0,0 +1,300 @@
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
+ extend Enum
278
+
279
+ include Core
280
+ include Persistence
281
+ include ReadonlyAttributes
282
+ include ModelSchema
283
+ include Inheritance
284
+ include AttributeAssignment
285
+ include ActiveModel::Conversion
286
+ include Validations
287
+ include Attributes
288
+ include AttributeDecorators
289
+ include DefineCallbacks
290
+ include AttributeMethods
291
+ include Callbacks
292
+ include Associations
293
+ include NestedValidateAssociation
294
+ include NestedAttributes
295
+ include Reflection
296
+ include Serialization
297
+ end
298
+
299
+ ActiveSupport.run_load_hooks(:duck_record, Base)
300
+ end