duck_record 0.0.9.1 → 0.0.10
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.
- checksums.yaml +4 -4
- data/Rakefile +13 -13
- data/lib/duck_record/associations/association.rb +15 -15
- data/lib/duck_record/associations/collection_association.rb +8 -8
- data/lib/duck_record/associations/collection_proxy.rb +1 -1
- data/lib/duck_record/associations/has_many_association.rb +0 -1
- data/lib/duck_record/associations/singular_association.rb +6 -6
- data/lib/duck_record/attribute/user_provided_default.rb +2 -2
- data/lib/duck_record/attribute.rb +70 -70
- data/lib/duck_record/attribute_assignment.rb +74 -74
- data/lib/duck_record/attribute_methods/before_type_cast.rb +9 -9
- data/lib/duck_record/attribute_methods/dirty.rb +29 -29
- data/lib/duck_record/attribute_methods/read.rb +27 -27
- data/lib/duck_record/attribute_methods/write.rb +20 -20
- data/lib/duck_record/attribute_methods.rb +8 -8
- data/lib/duck_record/attribute_mutation_tracker.rb +4 -4
- data/lib/duck_record/attribute_set/yaml_encoder.rb +5 -5
- data/lib/duck_record/attribute_set.rb +5 -5
- data/lib/duck_record/attributes.rb +14 -14
- data/lib/duck_record/base.rb +18 -18
- data/lib/duck_record/callbacks.rb +1 -1
- data/lib/duck_record/core.rb +35 -35
- data/lib/duck_record/inheritance.rb +25 -25
- data/lib/duck_record/model_schema.rb +10 -11
- data/lib/duck_record/nested_attributes.rb +149 -149
- data/lib/duck_record/reflection.rb +14 -14
- data/lib/duck_record/type/array.rb +6 -6
- data/lib/duck_record/type/registry.rb +21 -21
- data/lib/duck_record/type/serialized.rb +8 -8
- data/lib/duck_record/type/time.rb +0 -1
- data/lib/duck_record/type/unsigned_integer.rb +6 -6
- data/lib/duck_record/type.rb +16 -14
- data/lib/duck_record/validations/uniqueness_on_real_record.rb +45 -45
- data/lib/duck_record/validations.rb +6 -6
- data/lib/duck_record/version.rb +1 -1
- data/lib/duck_record.rb +7 -7
- metadata +7 -6
@@ -6,42 +6,42 @@ module DuckRecord
|
|
6
6
|
module ClassMethods
|
7
7
|
private
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
9
|
+
# We want to generate the methods via module_eval rather than
|
10
|
+
# define_method, because define_method is slower on dispatch.
|
11
|
+
# Evaluating many similar methods may use more memory as the instruction
|
12
|
+
# sequences are duplicated and cached (in MRI). define_method may
|
13
|
+
# be slower on dispatch, but if you're careful about the closure
|
14
|
+
# created, then define_method will consume much less memory.
|
15
|
+
#
|
16
|
+
# But sometimes the database might return columns with
|
17
|
+
# characters that are not allowed in normal method names (like
|
18
|
+
# 'my_column(omg)'. So to work around this we first define with
|
19
|
+
# the __temp__ identifier, and then use alias method to rename
|
20
|
+
# it to what we want.
|
21
|
+
#
|
22
|
+
# We are also defining a constant to hold the frozen string of
|
23
|
+
# the attribute name. Using a constant means that we do not have
|
24
|
+
# to allocate an object on each call to the attribute method.
|
25
|
+
# Making it frozen means that it doesn't get duped when used to
|
26
|
+
# key the @attributes in read_attribute.
|
27
|
+
def define_method_attribute(name)
|
28
|
+
safe_name = name.unpack("h*".freeze).first
|
29
|
+
temp_method = "__temp__#{safe_name}"
|
30
30
|
|
31
|
-
|
31
|
+
DuckRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
32
32
|
|
33
|
-
|
33
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
34
34
|
def #{temp_method}
|
35
35
|
name = ::DuckRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
36
36
|
_read_attribute(name) { |n| missing_attribute(n, caller) }
|
37
37
|
end
|
38
38
|
STR
|
39
39
|
|
40
|
-
|
41
|
-
|
42
|
-
|
40
|
+
generated_attribute_methods.module_eval do
|
41
|
+
alias_method name, temp_method
|
42
|
+
undef_method temp_method
|
43
|
+
end
|
43
44
|
end
|
44
|
-
end
|
45
45
|
end
|
46
46
|
|
47
47
|
# Returns the value of the attribute identified by <tt>attr_name</tt> after
|
@@ -4,17 +4,17 @@ module DuckRecord
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
included do
|
7
|
-
attribute_method_suffix
|
7
|
+
attribute_method_suffix "="
|
8
8
|
end
|
9
9
|
|
10
10
|
module ClassMethods
|
11
11
|
private
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
def define_method_attribute=(name)
|
14
|
+
safe_name = name.unpack("h*".freeze).first
|
15
|
+
DuckRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
16
16
|
|
17
|
-
|
17
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
18
18
|
def __temp__#{safe_name}=(value, force_write_readonly: false)
|
19
19
|
name = ::DuckRecord::AttributeMethods::AttrNames::ATTR_#{safe_name}
|
20
20
|
write_attribute(name, value, force_write_readonly: force_write_readonly)
|
@@ -22,7 +22,7 @@ module DuckRecord
|
|
22
22
|
alias_method #{(name + '=').inspect}, :__temp__#{safe_name}=
|
23
23
|
undef_method :__temp__#{safe_name}=
|
24
24
|
STR
|
25
|
-
|
25
|
+
end
|
26
26
|
end
|
27
27
|
|
28
28
|
# Updates the attribute identified by <tt>attr_name</tt> with the
|
@@ -44,24 +44,24 @@ module DuckRecord
|
|
44
44
|
|
45
45
|
private
|
46
46
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
47
|
+
# Handle *= for method_missing.
|
48
|
+
def attribute=(attribute_name, value, force_write_readonly: false)
|
49
|
+
write_attribute(attribute_name, value, force_write_readonly: force_write_readonly)
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
52
|
+
def write_attribute_with_type_cast(attr_name, value, should_type_cast, force_write_readonly: false)
|
53
|
+
attr_name = attr_name.to_s
|
54
54
|
|
55
|
-
|
55
|
+
return if !force_write_readonly && self.class.readonly_attributes.include?(attr_name)
|
56
56
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
57
|
+
if should_type_cast
|
58
|
+
@attributes.write_from_user(attr_name, value)
|
59
|
+
else
|
60
|
+
@attributes.write_cast_value(attr_name, value)
|
61
|
+
end
|
62
62
|
|
63
|
-
|
64
|
-
|
63
|
+
value
|
64
|
+
end
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "active_support/core_ext/enumerable"
|
2
|
+
require "active_support/core_ext/string/filters"
|
3
|
+
require "mutex_m"
|
4
|
+
require "concurrent/map"
|
5
5
|
|
6
6
|
module DuckRecord
|
7
7
|
# = Active Record Attribute Methods
|
@@ -317,9 +317,9 @@ module DuckRecord
|
|
317
317
|
|
318
318
|
protected
|
319
319
|
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
320
|
+
def attribute_method?(attr_name) # :nodoc:
|
321
|
+
# We check defined? because Syck calls respond_to? before actually calling initialize.
|
322
|
+
defined?(@attributes) && @attributes.key?(attr_name)
|
323
|
+
end
|
324
324
|
end
|
325
325
|
end
|
@@ -64,13 +64,13 @@ module DuckRecord
|
|
64
64
|
# Workaround for Ruby 2.2 "private attribute?" warning.
|
65
65
|
protected
|
66
66
|
|
67
|
-
|
67
|
+
attr_reader :attributes, :forced_changes
|
68
68
|
|
69
69
|
private
|
70
70
|
|
71
|
-
|
72
|
-
|
73
|
-
|
71
|
+
def attr_names
|
72
|
+
attributes.keys
|
73
|
+
end
|
74
74
|
end
|
75
75
|
|
76
76
|
class NullMutationTracker # :nodoc:
|
@@ -8,7 +8,7 @@ module DuckRecord
|
|
8
8
|
end
|
9
9
|
|
10
10
|
def encode(attribute_set, coder)
|
11
|
-
coder[
|
11
|
+
coder["concise_attributes"] = attribute_set.each_value.map do |attr|
|
12
12
|
if attr.type.equal?(default_types[attr.name])
|
13
13
|
attr.with_type(nil)
|
14
14
|
else
|
@@ -18,10 +18,10 @@ module DuckRecord
|
|
18
18
|
end
|
19
19
|
|
20
20
|
def decode(coder)
|
21
|
-
if coder[
|
22
|
-
coder[
|
21
|
+
if coder["attributes"]
|
22
|
+
coder["attributes"]
|
23
23
|
else
|
24
|
-
attributes_hash = Hash[coder[
|
24
|
+
attributes_hash = Hash[coder["concise_attributes"].map do |attr|
|
25
25
|
if attr.type.nil?
|
26
26
|
attr = attr.with_type(default_types[attr.name])
|
27
27
|
end
|
@@ -35,7 +35,7 @@ module DuckRecord
|
|
35
35
|
# Workaround for Ruby 2.2 'private attribute?' warning.
|
36
36
|
protected
|
37
37
|
|
38
|
-
|
38
|
+
attr_reader :default_types
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "duck_record/attribute_set/yaml_encoder"
|
2
2
|
|
3
3
|
module DuckRecord
|
4
4
|
class AttributeSet # :nodoc:
|
@@ -87,12 +87,12 @@ module DuckRecord
|
|
87
87
|
# Workaround for Ruby 2.2 "private attribute?" warning.
|
88
88
|
protected
|
89
89
|
|
90
|
-
|
90
|
+
attr_reader :attributes
|
91
91
|
|
92
92
|
private
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
def initialized_attributes
|
95
|
+
attributes.select { |_, attr| attr.initialized? }
|
96
|
+
end
|
97
97
|
end
|
98
98
|
end
|
@@ -240,22 +240,22 @@ module DuckRecord
|
|
240
240
|
|
241
241
|
private
|
242
242
|
|
243
|
-
|
244
|
-
|
243
|
+
NO_DEFAULT_PROVIDED = Object.new # :nodoc:
|
244
|
+
private_constant :NO_DEFAULT_PROVIDED
|
245
245
|
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
246
|
+
def define_default_attribute(name, value, type)
|
247
|
+
if value == NO_DEFAULT_PROVIDED
|
248
|
+
default_attribute = _default_attributes[name].with_type(type)
|
249
|
+
else
|
250
|
+
default_attribute = Attribute::UserProvidedDefault.new(
|
251
|
+
name,
|
252
|
+
value,
|
253
|
+
type,
|
254
|
+
_default_attributes.fetch(name.to_s) { nil },
|
255
|
+
)
|
256
|
+
end
|
257
|
+
_default_attributes[name] = default_attribute
|
256
258
|
end
|
257
|
-
_default_attributes[name] = default_attribute
|
258
|
-
end
|
259
259
|
end
|
260
260
|
end
|
261
261
|
end
|
data/lib/duck_record/base.rb
CHANGED
@@ -1,21 +1,21 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
7
|
-
require
|
8
|
-
require
|
9
|
-
require
|
10
|
-
require
|
11
|
-
require
|
12
|
-
require
|
13
|
-
require
|
14
|
-
require
|
15
|
-
require
|
16
|
-
require
|
17
|
-
require
|
18
|
-
require
|
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
19
|
|
20
20
|
module DuckRecord #:nodoc:
|
21
21
|
# = Active Record
|
data/lib/duck_record/core.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require
|
1
|
+
require "thread"
|
2
|
+
require "active_support/core_ext/hash/indifferent_access"
|
3
|
+
require "active_support/core_ext/object/duplicable"
|
4
|
+
require "active_support/core_ext/string/filters"
|
5
5
|
|
6
6
|
module DuckRecord
|
7
7
|
module Core
|
@@ -151,7 +151,7 @@ module DuckRecord
|
|
151
151
|
# coder # => {"attributes" => {"id" => nil, ... }}
|
152
152
|
def encode_with(coder)
|
153
153
|
self.class.yaml_encoder.encode(@attributes, coder)
|
154
|
-
coder[
|
154
|
+
coder["duck_record_yaml_version"] = 2
|
155
155
|
end
|
156
156
|
|
157
157
|
# Clone and freeze the attributes hash such that associations are still
|
@@ -187,9 +187,9 @@ module DuckRecord
|
|
187
187
|
if has_attribute?(name)
|
188
188
|
"#{name}: #{attribute_for_inspect(name)}"
|
189
189
|
end
|
190
|
-
end.compact.join(
|
190
|
+
end.compact.join(", ")
|
191
191
|
else
|
192
|
-
|
192
|
+
"not initialized"
|
193
193
|
end
|
194
194
|
|
195
195
|
"#<#{self.class} #{inspection}>"
|
@@ -201,19 +201,19 @@ module DuckRecord
|
|
201
201
|
return super if custom_inspect_method_defined?
|
202
202
|
pp.object_address_group(self) do
|
203
203
|
if defined?(@attributes) && @attributes
|
204
|
-
pp.seplist(self.class.attribute_names, proc { pp.text
|
204
|
+
pp.seplist(self.class.attribute_names, proc { pp.text "," }) do |attribute_name|
|
205
205
|
attribute_value = read_attribute(attribute_name)
|
206
|
-
pp.breakable
|
206
|
+
pp.breakable " "
|
207
207
|
pp.group(1) do
|
208
208
|
pp.text attribute_name
|
209
|
-
pp.text
|
209
|
+
pp.text ":"
|
210
210
|
pp.breakable
|
211
211
|
pp.pp attribute_value
|
212
212
|
end
|
213
213
|
end
|
214
214
|
else
|
215
|
-
pp.breakable
|
216
|
-
pp.text
|
215
|
+
pp.breakable " "
|
216
|
+
pp.text "not initialized"
|
217
217
|
end
|
218
218
|
end
|
219
219
|
end
|
@@ -225,33 +225,33 @@ module DuckRecord
|
|
225
225
|
|
226
226
|
private
|
227
227
|
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
228
|
+
# +Array#flatten+ will call +#to_ary+ (recursively) on each of the elements of
|
229
|
+
# the array, and then rescues from the possible +NoMethodError+. If those elements are
|
230
|
+
# +DuckRecord::Base+'s, then this triggers the various +method_missing+'s that we have,
|
231
|
+
# which significantly impacts upon performance.
|
232
|
+
#
|
233
|
+
# So we can avoid the +method_missing+ hit by explicitly defining +#to_ary+ as +nil+ here.
|
234
|
+
#
|
235
|
+
# See also http://tenderlovemaking.com/2011/06/28/til-its-ok-to-return-nil-from-to_ary.html
|
236
|
+
def to_ary
|
237
|
+
nil
|
238
|
+
end
|
239
239
|
|
240
|
-
|
241
|
-
|
242
|
-
|
240
|
+
def init_internals
|
241
|
+
@readonly = false
|
242
|
+
end
|
243
243
|
|
244
|
-
|
245
|
-
|
244
|
+
def initialize_internals_callback
|
245
|
+
end
|
246
246
|
|
247
|
-
|
248
|
-
|
249
|
-
|
247
|
+
def thaw
|
248
|
+
if frozen?
|
249
|
+
@attributes = @attributes.dup
|
250
|
+
end
|
250
251
|
end
|
251
|
-
end
|
252
252
|
|
253
|
-
|
254
|
-
|
255
|
-
|
253
|
+
def custom_inspect_method_defined?
|
254
|
+
self.class.instance_method(:inspect).owner != DuckRecord::Base.instance_method(:inspect).owner
|
255
|
+
end
|
256
256
|
end
|
257
257
|
end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require "active_support/core_ext/hash/indifferent_access"
|
2
2
|
|
3
3
|
module DuckRecord
|
4
4
|
# == Single table inheritance
|
@@ -96,35 +96,35 @@ module DuckRecord
|
|
96
96
|
|
97
97
|
protected
|
98
98
|
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
99
|
+
# Returns the class type of the record using the current module as a prefix. So descendants of
|
100
|
+
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
101
|
+
def compute_type(type_name)
|
102
|
+
if type_name.start_with?("::".freeze)
|
103
|
+
# If the type is prefixed with a scope operator then we assume that
|
104
|
+
# the type_name is an absolute reference.
|
105
|
+
ActiveSupport::Dependencies.constantize(type_name)
|
106
|
+
else
|
107
|
+
type_candidate = @_type_candidates_cache[type_name]
|
108
|
+
if type_candidate && type_constant = ActiveSupport::Dependencies.safe_constantize(type_candidate)
|
109
|
+
return type_constant
|
110
|
+
end
|
111
111
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
112
|
+
# Build a list of candidates to search for
|
113
|
+
candidates = []
|
114
|
+
name.scan(/::|$/) { candidates.unshift "#{$`}::#{type_name}" }
|
115
|
+
candidates << type_name
|
116
116
|
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
117
|
+
candidates.each do |candidate|
|
118
|
+
constant = ActiveSupport::Dependencies.safe_constantize(candidate)
|
119
|
+
if candidate == constant.to_s
|
120
|
+
@_type_candidates_cache[type_name] = candidate
|
121
|
+
return constant
|
122
|
+
end
|
122
123
|
end
|
123
|
-
end
|
124
124
|
|
125
|
-
|
125
|
+
raise NameError.new("uninitialized constant #{candidates.first}", candidates.first)
|
126
|
+
end
|
126
127
|
end
|
127
|
-
end
|
128
128
|
end
|
129
129
|
end
|
130
130
|
end
|
@@ -7,7 +7,6 @@ module DuckRecord
|
|
7
7
|
end
|
8
8
|
|
9
9
|
module ClassMethods
|
10
|
-
|
11
10
|
def attribute_types # :nodoc:
|
12
11
|
load_schema
|
13
12
|
@attribute_types ||= Hash.new
|
@@ -42,19 +41,19 @@ module DuckRecord
|
|
42
41
|
|
43
42
|
private
|
44
43
|
|
45
|
-
|
46
|
-
|
47
|
-
|
44
|
+
def schema_loaded?
|
45
|
+
defined?(@loaded) && @loaded
|
46
|
+
end
|
48
47
|
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
def load_schema
|
49
|
+
unless schema_loaded?
|
50
|
+
load_schema!
|
51
|
+
end
|
52
52
|
end
|
53
|
-
end
|
54
53
|
|
55
|
-
|
56
|
-
|
57
|
-
|
54
|
+
def load_schema!
|
55
|
+
@loaded = true
|
56
|
+
end
|
58
57
|
end
|
59
58
|
end
|
60
59
|
end
|