duck_record 0.0.1 → 0.0.3
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/README.md +6 -2
- data/lib/duck_record/attribute.rb +31 -39
- data/lib/duck_record/attribute/user_provided_default.rb +2 -2
- data/lib/duck_record/attribute_assignment.rb +2 -2
- data/lib/duck_record/attribute_methods.rb +1 -8
- data/lib/duck_record/attribute_methods/before_type_cast.rb +7 -7
- data/lib/duck_record/attribute_methods/dirty.rb +0 -17
- data/lib/duck_record/attribute_methods/read.rb +1 -1
- data/lib/duck_record/attribute_mutation_tracker.rb +3 -8
- data/lib/duck_record/attribute_set.rb +4 -5
- data/lib/duck_record/attributes.rb +1 -2
- data/lib/duck_record/core.rb +9 -14
- data/lib/duck_record/inheritance.rb +2 -2
- data/lib/duck_record/model_schema.rb +0 -4
- data/lib/duck_record/translation.rb +1 -1
- data/lib/duck_record/type.rb +1 -0
- data/lib/duck_record/type/serialized.rb +8 -8
- data/lib/duck_record/version.rb +1 -1
- metadata +6 -5
- data/lib/duck_record/attribute_set/builder.rb +0 -124
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: edf615681e997320dd43475ac1b65efbf4a6355b
|
|
4
|
+
data.tar.gz: 2790953d7ec98a37866298eb4c445ed62e71f22c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 5bb6d8c090cc556f4edf072de23146850001ebdea769ab6656eb129aec0e07500b2c66aafdca655dc6df3a6efcada0af47f071a48bc3a9d8ee3c1a42545025b5
|
|
7
|
+
data.tar.gz: a7ef53be7401e9612c332d2a2f812f92d1a8ed8145d63855ac2da2ce4de415b3e5ff0afbf49219dd9ec1a787dbf81746219932d373edcaede219990f92a0b0ad
|
data/README.md
CHANGED
|
@@ -9,10 +9,12 @@ Actually it's extract from Active Record.
|
|
|
9
9
|
```ruby
|
|
10
10
|
class Book < DuckRecord::Base
|
|
11
11
|
attribute :title, :string
|
|
12
|
-
attribute :tags, :string, array: true
|
|
13
12
|
attribute :price, :decimal, default: 0
|
|
14
|
-
attribute :meta, :json, default: {}
|
|
15
13
|
attribute :bought_at, :datetime, default: -> { Time.new }
|
|
14
|
+
|
|
15
|
+
# some types that cheated from PG
|
|
16
|
+
attribute :tags, :string, array: true
|
|
17
|
+
attribute :meta, :json, default: {}
|
|
16
18
|
|
|
17
19
|
validates :title, presence: true
|
|
18
20
|
end
|
|
@@ -44,7 +46,9 @@ $ gem install duck_record
|
|
|
44
46
|
- `has_one`, `has_many`
|
|
45
47
|
- refactor that original design for database
|
|
46
48
|
- update docs
|
|
49
|
+
- add useful methods
|
|
47
50
|
- add tests
|
|
51
|
+
- let me known..
|
|
48
52
|
|
|
49
53
|
## Contributing
|
|
50
54
|
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
module DuckRecord
|
|
2
2
|
class Attribute # :nodoc:
|
|
3
3
|
class << self
|
|
4
|
+
def from_database(name, value, type)
|
|
5
|
+
FromDatabase.new(name, value, type)
|
|
6
|
+
end
|
|
7
|
+
|
|
4
8
|
def from_user(name, value, type, original_attribute = nil)
|
|
5
9
|
FromUser.new(name, value, type, original_attribute)
|
|
6
10
|
end
|
|
@@ -64,16 +68,16 @@ module DuckRecord
|
|
|
64
68
|
self.class.from_user(name, value, type, original_attribute || self)
|
|
65
69
|
end
|
|
66
70
|
|
|
71
|
+
def with_value_from_database(value)
|
|
72
|
+
self.class.from_database(name, value, type)
|
|
73
|
+
end
|
|
74
|
+
|
|
67
75
|
def with_cast_value(value)
|
|
68
76
|
self.class.with_cast_value(name, value, type)
|
|
69
77
|
end
|
|
70
78
|
|
|
71
79
|
def with_type(type)
|
|
72
|
-
|
|
73
|
-
with_value_from_user(value).with_type(type)
|
|
74
|
-
else
|
|
75
|
-
self.class.new(name, value_before_type_cast, type, original_attribute)
|
|
76
|
-
end
|
|
80
|
+
self.class.new(name, value_before_type_cast, type, original_attribute)
|
|
77
81
|
end
|
|
78
82
|
|
|
79
83
|
def type_cast(*)
|
|
@@ -104,38 +108,11 @@ module DuckRecord
|
|
|
104
108
|
[self.class, name, value_before_type_cast, type].hash
|
|
105
109
|
end
|
|
106
110
|
|
|
107
|
-
def init_with(coder)
|
|
108
|
-
@name = coder["name"]
|
|
109
|
-
@value_before_type_cast = coder["value_before_type_cast"]
|
|
110
|
-
@type = coder["type"]
|
|
111
|
-
@original_attribute = coder["original_attribute"]
|
|
112
|
-
@value = coder["value"] if coder.map.key?("value")
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
def encode_with(coder)
|
|
116
|
-
coder["name"] = name
|
|
117
|
-
coder["value_before_type_cast"] = value_before_type_cast if value_before_type_cast
|
|
118
|
-
coder["type"] = type if type
|
|
119
|
-
coder["original_attribute"] = original_attribute if original_attribute
|
|
120
|
-
coder["value"] = value if defined?(@value)
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
|
124
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
|
125
111
|
protected
|
|
126
112
|
|
|
127
113
|
attr_reader :original_attribute
|
|
128
114
|
alias_method :assigned?, :original_attribute
|
|
129
115
|
|
|
130
|
-
def original_value_for_database
|
|
131
|
-
if assigned?
|
|
132
|
-
original_attribute.original_value_for_database
|
|
133
|
-
else
|
|
134
|
-
_original_value_for_database
|
|
135
|
-
end
|
|
136
|
-
end
|
|
137
|
-
|
|
138
|
-
private
|
|
139
116
|
def initialize_dup(other)
|
|
140
117
|
if defined?(@value) && @value.duplicable?
|
|
141
118
|
@value = @value.dup
|
|
@@ -146,10 +123,28 @@ module DuckRecord
|
|
|
146
123
|
assigned? && type.changed?(original_value, value, value_before_type_cast)
|
|
147
124
|
end
|
|
148
125
|
|
|
126
|
+
def original_value_for_database
|
|
127
|
+
if assigned?
|
|
128
|
+
original_attribute.original_value_for_database
|
|
129
|
+
else
|
|
130
|
+
_original_value_for_database
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
149
134
|
def _original_value_for_database
|
|
150
135
|
type.serialize(original_value)
|
|
151
136
|
end
|
|
152
137
|
|
|
138
|
+
class FromDatabase < Attribute # :nodoc:
|
|
139
|
+
def type_cast(value)
|
|
140
|
+
type.deserialize(value)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def _original_value_for_database
|
|
144
|
+
value_before_type_cast
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
153
148
|
class FromUser < Attribute # :nodoc:
|
|
154
149
|
def type_cast(value)
|
|
155
150
|
type.cast(value)
|
|
@@ -172,7 +167,7 @@ module DuckRecord
|
|
|
172
167
|
|
|
173
168
|
class Null < Attribute # :nodoc:
|
|
174
169
|
def initialize(name)
|
|
175
|
-
super(name, nil,
|
|
170
|
+
super(name, nil, Type::Value.new)
|
|
176
171
|
end
|
|
177
172
|
|
|
178
173
|
def type_cast(*)
|
|
@@ -183,9 +178,10 @@ module DuckRecord
|
|
|
183
178
|
self.class.with_cast_value(name, nil, type)
|
|
184
179
|
end
|
|
185
180
|
|
|
186
|
-
def
|
|
181
|
+
def with_value_from_database(value)
|
|
187
182
|
raise ActiveModel::MissingAttributeError, "can't write unknown attribute `#{name}`"
|
|
188
183
|
end
|
|
184
|
+
alias_method :with_value_from_user, :with_value_from_database
|
|
189
185
|
end
|
|
190
186
|
|
|
191
187
|
class Uninitialized < Attribute # :nodoc:
|
|
@@ -211,11 +207,7 @@ module DuckRecord
|
|
|
211
207
|
def initialized?
|
|
212
208
|
false
|
|
213
209
|
end
|
|
214
|
-
|
|
215
|
-
def with_type(type)
|
|
216
|
-
self.class.new(name, DuckRecord::Type::Value.new)
|
|
217
|
-
end
|
|
218
210
|
end
|
|
219
|
-
private_constant :FromUser, :Null, :Uninitialized, :WithCastValue
|
|
211
|
+
private_constant :FromDatabase, :FromUser, :Null, :Uninitialized, :WithCastValue
|
|
220
212
|
end
|
|
221
213
|
end
|
|
@@ -3,9 +3,9 @@ require 'duck_record/attribute'
|
|
|
3
3
|
module DuckRecord
|
|
4
4
|
class Attribute # :nodoc:
|
|
5
5
|
class UserProvidedDefault < FromUser # :nodoc:
|
|
6
|
-
def initialize(name, value, type,
|
|
6
|
+
def initialize(name, value, type, default)
|
|
7
7
|
@user_provided_value = value
|
|
8
|
-
super(name, value, type,
|
|
8
|
+
super(name, value, type, default)
|
|
9
9
|
end
|
|
10
10
|
|
|
11
11
|
def value_before_type_cast
|
|
@@ -61,7 +61,7 @@ module DuckRecord
|
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
unless errors.empty?
|
|
64
|
-
error_descriptions = errors.map(&:message).join(
|
|
64
|
+
error_descriptions = errors.map(&:message).join(',')
|
|
65
65
|
raise MultiparameterAssignmentErrors.new(errors), "#{errors.size} error(s) on assignment of multiparameter attributes [#{error_descriptions}]"
|
|
66
66
|
end
|
|
67
67
|
end
|
|
@@ -70,7 +70,7 @@ module DuckRecord
|
|
|
70
70
|
attributes = {}
|
|
71
71
|
|
|
72
72
|
pairs.each do |(multiparameter_name, value)|
|
|
73
|
-
attribute_name = multiparameter_name.split(
|
|
73
|
+
attribute_name = multiparameter_name.split('(').first
|
|
74
74
|
attributes[attribute_name] ||= {}
|
|
75
75
|
|
|
76
76
|
parameter_value = value.empty? ? nil : type_cast_attribute_value(multiparameter_name, value)
|
|
@@ -182,14 +182,7 @@ module DuckRecord
|
|
|
182
182
|
def respond_to?(name, include_private = false)
|
|
183
183
|
return false unless super
|
|
184
184
|
|
|
185
|
-
|
|
186
|
-
when :to_partial_path
|
|
187
|
-
name = 'to_partial_path'.freeze
|
|
188
|
-
when :to_model
|
|
189
|
-
name = 'to_model'.freeze
|
|
190
|
-
else
|
|
191
|
-
name = name.to_s
|
|
192
|
-
end
|
|
185
|
+
name = name.to_s
|
|
193
186
|
|
|
194
187
|
# If the result is true then check for the select case.
|
|
195
188
|
# For queries selecting a subset of columns, return false for unselected columns.
|
|
@@ -63,14 +63,14 @@ module DuckRecord
|
|
|
63
63
|
|
|
64
64
|
private
|
|
65
65
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
# Handle *_before_type_cast for method_missing.
|
|
67
|
+
def attribute_before_type_cast(attribute_name)
|
|
68
|
+
read_attribute_before_type_cast(attribute_name)
|
|
69
|
+
end
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
def attribute_came_from_user?(attribute_name)
|
|
72
|
+
@attributes[attribute_name].came_from_user?
|
|
73
|
+
end
|
|
74
74
|
end
|
|
75
75
|
end
|
|
76
76
|
end
|
|
@@ -9,11 +9,6 @@ module DuckRecord
|
|
|
9
9
|
|
|
10
10
|
include ActiveModel::Dirty
|
|
11
11
|
|
|
12
|
-
included do
|
|
13
|
-
class_attribute :partial_writes, instance_writer: false
|
|
14
|
-
self.partial_writes = true
|
|
15
|
-
end
|
|
16
|
-
|
|
17
12
|
def initialize_dup(other) # :nodoc:
|
|
18
13
|
super
|
|
19
14
|
@attributes = self.class._default_attributes.map do |attr|
|
|
@@ -88,18 +83,6 @@ module DuckRecord
|
|
|
88
83
|
mutation_tracker.forget_change(attr_name)
|
|
89
84
|
end
|
|
90
85
|
|
|
91
|
-
def _update_record(*)
|
|
92
|
-
partial_writes? ? super(keys_for_partial_write) : super
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def _create_record(*)
|
|
96
|
-
partial_writes? ? super(keys_for_partial_write) : super
|
|
97
|
-
end
|
|
98
|
-
|
|
99
|
-
def keys_for_partial_write
|
|
100
|
-
changed & self.class.column_names
|
|
101
|
-
end
|
|
102
|
-
|
|
103
86
|
def store_original_attributes
|
|
104
87
|
@attributes = @attributes.map(&:forgetting_assignment)
|
|
105
88
|
@mutation_tracker = nil
|
|
@@ -25,7 +25,7 @@ module DuckRecord
|
|
|
25
25
|
# Making it frozen means that it doesn't get duped when used to
|
|
26
26
|
# key the @attributes in read_attribute.
|
|
27
27
|
def define_method_attribute(name)
|
|
28
|
-
safe_name = name.unpack(
|
|
28
|
+
safe_name = name.unpack('h*'.freeze).first
|
|
29
29
|
temp_method = "__temp__#{safe_name}"
|
|
30
30
|
|
|
31
31
|
DuckRecord::AttributeMethods::AttrNames.set_name_cache safe_name, name
|
|
@@ -5,7 +5,6 @@ module DuckRecord
|
|
|
5
5
|
def initialize(attributes)
|
|
6
6
|
@attributes = attributes
|
|
7
7
|
@forced_changes = Set.new
|
|
8
|
-
@deprecated_forced_changes = Set.new
|
|
9
8
|
end
|
|
10
9
|
|
|
11
10
|
def changed_values
|
|
@@ -32,7 +31,7 @@ module DuckRecord
|
|
|
32
31
|
end
|
|
33
32
|
|
|
34
33
|
def any_changes?
|
|
35
|
-
attr_names.any? { |attr| changed?(attr) }
|
|
34
|
+
attr_names.any? { |attr| changed?(attr) }
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
def changed?(attr_name, from: OPTION_NOT_GIVEN, to: OPTION_NOT_GIVEN)
|
|
@@ -61,15 +60,11 @@ module DuckRecord
|
|
|
61
60
|
forced_changes << attr_name.to_s
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
def deprecated_force_change(attr_name)
|
|
65
|
-
deprecated_forced_changes << attr_name.to_s
|
|
66
|
-
end
|
|
67
|
-
|
|
68
63
|
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
|
69
64
|
# Workaround for Ruby 2.2 "private attribute?" warning.
|
|
70
65
|
protected
|
|
71
66
|
|
|
72
|
-
attr_reader :attributes, :forced_changes
|
|
67
|
+
attr_reader :attributes, :forced_changes
|
|
73
68
|
|
|
74
69
|
private
|
|
75
70
|
|
|
@@ -89,7 +84,7 @@ module DuckRecord
|
|
|
89
84
|
{}
|
|
90
85
|
end
|
|
91
86
|
|
|
92
|
-
def change_to_attribute(
|
|
87
|
+
def change_to_attribute(_)
|
|
93
88
|
end
|
|
94
89
|
|
|
95
90
|
def any_changes?(*)
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
require 'duck_record/attribute_set/builder'
|
|
2
1
|
require 'duck_record/attribute_set/yaml_encoder'
|
|
3
2
|
|
|
4
3
|
module DuckRecord
|
|
@@ -88,12 +87,12 @@ module DuckRecord
|
|
|
88
87
|
# Workaround for Ruby 2.2 "private attribute?" warning.
|
|
89
88
|
protected
|
|
90
89
|
|
|
91
|
-
|
|
90
|
+
attr_reader :attributes
|
|
92
91
|
|
|
93
92
|
private
|
|
94
93
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
def initialized_attributes
|
|
95
|
+
attributes.select { |_, attr| attr.initialized? }
|
|
96
|
+
end
|
|
98
97
|
end
|
|
99
98
|
end
|
|
@@ -221,8 +221,7 @@ module DuckRecord
|
|
|
221
221
|
def define_attribute(
|
|
222
222
|
name,
|
|
223
223
|
cast_type,
|
|
224
|
-
default: NO_DEFAULT_PROVIDED
|
|
225
|
-
user_provided_default: true
|
|
224
|
+
default: NO_DEFAULT_PROVIDED
|
|
226
225
|
)
|
|
227
226
|
attribute_types[name] = cast_type
|
|
228
227
|
define_default_attribute(name, default, cast_type)
|
data/lib/duck_record/core.rb
CHANGED
|
@@ -13,13 +13,7 @@ module DuckRecord
|
|
|
13
13
|
super
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def initialize_find_by_cache # :nodoc:
|
|
17
|
-
@find_by_statement_cache = { true => {}.extend(Mutex_m), false => {}.extend(Mutex_m) }
|
|
18
|
-
end
|
|
19
|
-
|
|
20
16
|
def inherited(child_class) # :nodoc:
|
|
21
|
-
# initialize cache at class definition for thread safety
|
|
22
|
-
child_class.initialize_find_by_cache
|
|
23
17
|
super
|
|
24
18
|
end
|
|
25
19
|
|
|
@@ -62,7 +56,10 @@ module DuckRecord
|
|
|
62
56
|
init_internals
|
|
63
57
|
initialize_internals_callback
|
|
64
58
|
|
|
65
|
-
|
|
59
|
+
if attributes
|
|
60
|
+
assign_attributes(attributes)
|
|
61
|
+
clear_changes_information
|
|
62
|
+
end
|
|
66
63
|
|
|
67
64
|
yield self if block_given?
|
|
68
65
|
_run_initialize_callbacks
|
|
@@ -83,7 +80,6 @@ module DuckRecord
|
|
|
83
80
|
# post.init_with(coder)
|
|
84
81
|
# post.title # => 'hello world'
|
|
85
82
|
def init_with(coder)
|
|
86
|
-
coder = LegacyYamlAdapter.convert(self.class, coder)
|
|
87
83
|
@attributes = self.class.yaml_encoder.decode(coder)
|
|
88
84
|
|
|
89
85
|
init_internals
|
|
@@ -92,7 +88,6 @@ module DuckRecord
|
|
|
92
88
|
|
|
93
89
|
yield self if block_given?
|
|
94
90
|
|
|
95
|
-
_run_find_callbacks
|
|
96
91
|
_run_initialize_callbacks
|
|
97
92
|
|
|
98
93
|
self
|
|
@@ -199,17 +194,17 @@ module DuckRecord
|
|
|
199
194
|
if defined?(@attributes) && @attributes
|
|
200
195
|
pp.seplist(self.class.attribute_names, proc { pp.text ',' }) do |attribute_name|
|
|
201
196
|
attribute_value = read_attribute(attribute_name)
|
|
202
|
-
pp.breakable
|
|
197
|
+
pp.breakable ' '
|
|
203
198
|
pp.group(1) do
|
|
204
199
|
pp.text attribute_name
|
|
205
|
-
pp.text
|
|
200
|
+
pp.text ':'
|
|
206
201
|
pp.breakable
|
|
207
202
|
pp.pp attribute_value
|
|
208
203
|
end
|
|
209
204
|
end
|
|
210
205
|
else
|
|
211
|
-
pp.breakable
|
|
212
|
-
pp.text
|
|
206
|
+
pp.breakable ' '
|
|
207
|
+
pp.text 'not initialized'
|
|
213
208
|
end
|
|
214
209
|
end
|
|
215
210
|
end
|
|
@@ -234,7 +229,7 @@ module DuckRecord
|
|
|
234
229
|
end
|
|
235
230
|
|
|
236
231
|
def init_internals
|
|
237
|
-
@readonly
|
|
232
|
+
@readonly = false
|
|
238
233
|
end
|
|
239
234
|
|
|
240
235
|
def initialize_internals_callback
|
|
@@ -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
|
|
@@ -99,7 +99,7 @@ module DuckRecord
|
|
|
99
99
|
# Returns the class type of the record using the current module as a prefix. So descendants of
|
|
100
100
|
# MyApp::Business::Account would appear as MyApp::Business::AccountSubclass.
|
|
101
101
|
def compute_type(type_name)
|
|
102
|
-
if type_name.start_with?(
|
|
102
|
+
if type_name.start_with?('::'.freeze)
|
|
103
103
|
# If the type is prefixed with a scope operator then we assume that
|
|
104
104
|
# the type_name is an absolute reference.
|
|
105
105
|
ActiveSupport::Dependencies.constantize(type_name)
|
data/lib/duck_record/type.rb
CHANGED
|
@@ -47,6 +47,7 @@ module DuckRecord
|
|
|
47
47
|
DateTime = ActiveModel::Type::DateTime
|
|
48
48
|
Time = ActiveModel::Type::Time
|
|
49
49
|
Date = ActiveModel::Type::Date
|
|
50
|
+
Value = ActiveModel::Type::Value
|
|
50
51
|
|
|
51
52
|
register(:big_integer, Type::BigInteger, override: false)
|
|
52
53
|
register(:binary, Type::Binary, override: false)
|
|
@@ -43,21 +43,21 @@ module DuckRecord
|
|
|
43
43
|
|
|
44
44
|
def assert_valid_value(value)
|
|
45
45
|
if coder.respond_to?(:assert_valid_value)
|
|
46
|
-
coder.assert_valid_value(value, action:
|
|
46
|
+
coder.assert_valid_value(value, action: 'serialize')
|
|
47
47
|
end
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
private
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
52
|
+
def default_value?(value)
|
|
53
|
+
value == coder.load(nil)
|
|
54
|
+
end
|
|
55
55
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
end
|
|
56
|
+
def encoded(value)
|
|
57
|
+
unless default_value?(value)
|
|
58
|
+
coder.dump(value)
|
|
60
59
|
end
|
|
60
|
+
end
|
|
61
61
|
end
|
|
62
62
|
end
|
|
63
63
|
end
|
data/lib/duck_record/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: duck_record
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.0.
|
|
4
|
+
version: 0.0.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- jasl
|
|
@@ -52,8 +52,10 @@ dependencies:
|
|
|
52
52
|
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
54
|
version: '5.0'
|
|
55
|
-
description: "It looks like Active Record and quacks like Active Record, it'
|
|
56
|
-
Record! \n Actually it's extract from
|
|
55
|
+
description: "It looks like Active Record and quacks like Active Record, but it can't
|
|
56
|
+
do persistence or querying,\n it's Duck Record! \n Actually it's extract from
|
|
57
|
+
Active Record.\n Used for creating virtual models like ActiveType or ModelAttribute
|
|
58
|
+
does."
|
|
57
59
|
email:
|
|
58
60
|
- jasl9187@hotmail.com
|
|
59
61
|
executables: []
|
|
@@ -74,7 +76,6 @@ files:
|
|
|
74
76
|
- lib/duck_record/attribute_methods/write.rb
|
|
75
77
|
- lib/duck_record/attribute_mutation_tracker.rb
|
|
76
78
|
- lib/duck_record/attribute_set.rb
|
|
77
|
-
- lib/duck_record/attribute_set/builder.rb
|
|
78
79
|
- lib/duck_record/attribute_set/yaml_encoder.rb
|
|
79
80
|
- lib/duck_record/attributes.rb
|
|
80
81
|
- lib/duck_record/base.rb
|
|
@@ -122,5 +123,5 @@ rubyforge_project:
|
|
|
122
123
|
rubygems_version: 2.6.10
|
|
123
124
|
signing_key:
|
|
124
125
|
specification_version: 4
|
|
125
|
-
summary:
|
|
126
|
+
summary: Used for creating virtual models like ActiveType or ModelAttribute does
|
|
126
127
|
test_files: []
|
|
@@ -1,124 +0,0 @@
|
|
|
1
|
-
require 'duck_record/attribute'
|
|
2
|
-
|
|
3
|
-
module DuckRecord
|
|
4
|
-
class AttributeSet # :nodoc:
|
|
5
|
-
class Builder # :nodoc:
|
|
6
|
-
attr_reader :types, :always_initialized, :default
|
|
7
|
-
|
|
8
|
-
def initialize(types, always_initialized = nil, &default)
|
|
9
|
-
@types = types
|
|
10
|
-
@always_initialized = always_initialized
|
|
11
|
-
@default = default
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def build_from_user(values = {}, additional_types = {})
|
|
15
|
-
if always_initialized && !values.key?(always_initialized)
|
|
16
|
-
values[always_initialized] = nil
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
attributes = LazyAttributeHash.new(types, values, additional_types, &default)
|
|
20
|
-
AttributeSet.new(attributes)
|
|
21
|
-
end
|
|
22
|
-
end
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
class LazyAttributeHash # :nodoc:
|
|
26
|
-
delegate :transform_values, :each_key, :each_value, :fetch, to: :materialize
|
|
27
|
-
|
|
28
|
-
def initialize(types, values, additional_types, &default)
|
|
29
|
-
@types = types
|
|
30
|
-
@values = values
|
|
31
|
-
@additional_types = additional_types
|
|
32
|
-
@materialized = false
|
|
33
|
-
@delegate_hash = {}
|
|
34
|
-
@default = default || proc {}
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def key?(key)
|
|
38
|
-
delegate_hash.key?(key) || values.key?(key) || types.key?(key)
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def [](key)
|
|
42
|
-
delegate_hash[key] || assign_default_value(key)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
def []=(key, value)
|
|
46
|
-
if frozen?
|
|
47
|
-
raise RuntimeError, "Can't modify frozen hash"
|
|
48
|
-
end
|
|
49
|
-
delegate_hash[key] = value
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
def deep_dup
|
|
53
|
-
dup.tap do |copy|
|
|
54
|
-
copy.instance_variable_set(:@delegate_hash, delegate_hash.transform_values(&:dup))
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
def initialize_dup(_)
|
|
59
|
-
@delegate_hash = Hash[delegate_hash]
|
|
60
|
-
super
|
|
61
|
-
end
|
|
62
|
-
|
|
63
|
-
def select
|
|
64
|
-
keys = types.keys | values.keys | delegate_hash.keys
|
|
65
|
-
keys.each_with_object({}) do |key, hash|
|
|
66
|
-
attribute = self[key]
|
|
67
|
-
if yield(key, attribute)
|
|
68
|
-
hash[key] = attribute
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def ==(other)
|
|
74
|
-
if other.is_a?(LazyAttributeHash)
|
|
75
|
-
materialize == other.materialize
|
|
76
|
-
else
|
|
77
|
-
materialize == other
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def marshal_dump
|
|
82
|
-
materialize
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def marshal_load(delegate_hash)
|
|
86
|
-
@delegate_hash = delegate_hash
|
|
87
|
-
@types = {}
|
|
88
|
-
@values = {}
|
|
89
|
-
@additional_types = {}
|
|
90
|
-
@materialized = true
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
|
94
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
|
95
|
-
protected
|
|
96
|
-
|
|
97
|
-
attr_reader :types, :values, :additional_types, :delegate_hash, :default
|
|
98
|
-
|
|
99
|
-
def materialize
|
|
100
|
-
unless @materialized
|
|
101
|
-
values.each_key { |key| self[key] }
|
|
102
|
-
types.each_key { |key| self[key] }
|
|
103
|
-
unless frozen?
|
|
104
|
-
@materialized = true
|
|
105
|
-
end
|
|
106
|
-
end
|
|
107
|
-
delegate_hash
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
private
|
|
111
|
-
|
|
112
|
-
def assign_default_value(name)
|
|
113
|
-
type = additional_types.fetch(name, types[name])
|
|
114
|
-
value_present = true
|
|
115
|
-
value = values.fetch(name) { value_present = false }
|
|
116
|
-
|
|
117
|
-
if value_present
|
|
118
|
-
delegate_hash[name] = Attribute.from_user(name, value, type)
|
|
119
|
-
elsif types.key?(name)
|
|
120
|
-
delegate_hash[name] = default.call(name) || Attribute.uninitialized(name, type)
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|