options_model 0.0.1 → 0.0.2
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/lib/options_model/concerns/attribute_assignment.rb +58 -56
- data/lib/options_model/concerns/attributes.rb +117 -115
- data/lib/options_model/concerns/name_hacking.rb +14 -12
- data/lib/options_model/concerns/serialization.rb +37 -35
- data/lib/options_model/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA1:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 68ef05d17124810d0f70c7c6f08e996045d455dd
|
|
4
|
+
data.tar.gz: 4b0511272bf4303dff6a6ae0b87d149e3074cee4
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 17ce123c0378e081b1c49298b4666f357677b77d1bd8d7faf431bef6bcbe38516aabbdc31c35982c6f6e26265d1bb4e7288d5ffd30e5bddb98a419dea0bcc11a
|
|
7
|
+
data.tar.gz: c487d6352c9a9d749aa3d5165776398733af9e44f05cf406169765158edde4f272953271549db831cf559ca83a18d51fe170a72e54d9b87afa15f1708073257a
|
|
@@ -1,77 +1,79 @@
|
|
|
1
|
-
module OptionsModel
|
|
2
|
-
module
|
|
3
|
-
|
|
1
|
+
module OptionsModel
|
|
2
|
+
module Concerns
|
|
3
|
+
module AttributeAssignment
|
|
4
|
+
extend ActiveSupport::Concern
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
def initialize(attributes = {})
|
|
7
|
+
update_attributes(attributes)
|
|
8
|
+
end
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
def initialize_dup(other)
|
|
11
|
+
super
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
13
|
+
update_attributes(other)
|
|
14
|
+
end
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
16
|
+
def update_attributes(other)
|
|
17
|
+
return unless other
|
|
17
18
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
unless other.respond_to?(:to_h)
|
|
20
|
+
raise ArgumentError, "#{other} must be respond to `to_h`"
|
|
21
|
+
end
|
|
21
22
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
23
|
+
other.to_h.each do |k, v|
|
|
24
|
+
if respond_to?("#{k}=")
|
|
25
|
+
public_send("#{k}=", v)
|
|
26
|
+
else
|
|
27
|
+
unused_attributes[k] = v
|
|
28
|
+
end
|
|
27
29
|
end
|
|
28
30
|
end
|
|
29
|
-
end
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def []=(key, val)
|
|
36
|
-
setter = "#{key}="
|
|
37
|
-
if respond_to?(setter)
|
|
38
|
-
public_send(setter, val)
|
|
39
|
-
else
|
|
40
|
-
unused_attributes[key] = val
|
|
32
|
+
def [](key)
|
|
33
|
+
public_send(key) if respond_to?(key)
|
|
41
34
|
end
|
|
42
|
-
end
|
|
43
35
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
def []=(key, val)
|
|
37
|
+
setter = "#{key}="
|
|
38
|
+
if respond_to?(setter)
|
|
39
|
+
public_send(setter, val)
|
|
40
|
+
else
|
|
41
|
+
unused_attributes[key] = val
|
|
42
|
+
end
|
|
47
43
|
end
|
|
48
44
|
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
def fetch(key, default = nil)
|
|
46
|
+
if self.class.attribute_names.exclude?(key.to_sym) && default.nil? && !block_given?
|
|
47
|
+
raise KeyError, "attribute not found"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
value = respond_to?(key) ? public_send(key) : nil
|
|
51
|
+
return value if value
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
if default
|
|
54
|
+
default
|
|
55
|
+
elsif block_given?
|
|
56
|
+
yield
|
|
57
|
+
end
|
|
56
58
|
end
|
|
57
|
-
end
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
def _attributes
|
|
61
|
+
@attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
62
|
+
end
|
|
63
|
+
private :_attributes
|
|
64
|
+
alias_method :attributes, :_attributes
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
66
|
+
def _nested_attributes
|
|
67
|
+
@nested_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
68
|
+
end
|
|
69
|
+
private :_nested_attributes
|
|
70
|
+
alias_method :nested_attributes, :_nested_attributes
|
|
70
71
|
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
def _unused_attributes
|
|
73
|
+
@unused_attributes ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
74
|
+
end
|
|
75
|
+
private :_unused_attributes
|
|
76
|
+
alias_method :unused_attributes, :_unused_attributes
|
|
73
77
|
end
|
|
74
|
-
private :_unused_attributes
|
|
75
|
-
alias_method :unused_attributes, :_unused_attributes
|
|
76
78
|
end
|
|
77
79
|
end
|
|
@@ -1,30 +1,31 @@
|
|
|
1
|
-
module OptionsModel
|
|
2
|
-
module
|
|
3
|
-
|
|
1
|
+
module OptionsModel
|
|
2
|
+
module Concerns
|
|
3
|
+
module Attributes
|
|
4
|
+
extend ActiveSupport::Concern
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def attribute(name, cast_type, default: nil, array: false)
|
|
8
|
+
check_not_finalized!
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
10
|
+
name = name.to_sym
|
|
11
|
+
check_name_validity! name
|
|
11
12
|
|
|
12
|
-
|
|
13
|
+
ActiveModel::Type.lookup(cast_type)
|
|
13
14
|
|
|
14
|
-
|
|
15
|
+
attribute_defaults[name] = default
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
17
|
+
generated_attribute_methods.synchronize do
|
|
18
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
|
18
19
|
def #{name}
|
|
19
20
|
value = attributes[:#{name}]
|
|
20
21
|
return value unless value.nil?
|
|
21
22
|
attributes[:#{name}] = self.class.attribute_defaults[:#{name}].#{default.respond_to?(:call) ? "call" : "dup"}
|
|
22
23
|
attributes[:#{name}]
|
|
23
24
|
end
|
|
24
|
-
|
|
25
|
+
STR
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
|
|
27
|
+
if array
|
|
28
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
|
28
29
|
def #{name}=(value)
|
|
29
30
|
if value.respond_to?(:to_a)
|
|
30
31
|
attributes[:#{name}] = value.to_a.map { |i| ActiveModel::Type.lookup(:#{cast_type}).cast(i) }
|
|
@@ -35,70 +36,70 @@ module OptionsModel::Concerns
|
|
|
35
36
|
"`value` should respond to `to_a`, but got \#{value.class} -- \#{value.inspect}"
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
STR
|
|
40
|
+
else
|
|
41
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
|
41
42
|
def #{name}=(value)
|
|
42
43
|
attributes[:#{name}] = ActiveModel::Type.lookup(:#{cast_type}).cast(value)
|
|
43
44
|
end
|
|
44
|
-
|
|
45
|
+
STR
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
if cast_type == :boolean
|
|
48
|
+
generated_attribute_methods.send :alias_method, :"#{name}?", name
|
|
49
|
+
end
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
|
-
end
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
self.attribute_names_for_inlining << name
|
|
54
|
+
name
|
|
55
|
+
end
|
|
55
56
|
|
|
56
|
-
|
|
57
|
-
|
|
57
|
+
def enum_attribute(name, enum, default: nil, allow_nil: false)
|
|
58
|
+
check_not_finalized!
|
|
58
59
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
60
|
+
unless enum.is_a?(Array) && enum.any?
|
|
61
|
+
raise ArgumentError, "enum should be an Array and can't empty"
|
|
62
|
+
end
|
|
63
|
+
enum = enum.map(&:to_s)
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
attribute name, :string, default: default
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
pluralized_name = name.to_s.pluralize
|
|
68
|
+
generated_class_methods.synchronize do
|
|
69
|
+
generated_class_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
|
69
70
|
def #{pluralized_name}
|
|
70
71
|
%w(#{enum.join(" ")}).freeze
|
|
71
72
|
end
|
|
72
|
-
|
|
73
|
+
STR
|
|
73
74
|
|
|
74
|
-
|
|
75
|
+
validates name, inclusion: {in: enum}, allow_nil: allow_nil
|
|
76
|
+
end
|
|
75
77
|
end
|
|
76
|
-
end
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
def embeds_one(name, class_name: nil, anonymous_class: nil)
|
|
80
|
+
check_not_finalized!
|
|
80
81
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
if class_name.blank? && anonymous_class.nil?
|
|
83
|
+
raise ArgumentError, "must provide at least one of `class_name` or `anonymous_class`"
|
|
84
|
+
end
|
|
84
85
|
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
name = name.to_sym
|
|
87
|
+
check_name_validity! name
|
|
87
88
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
if class_name.present?
|
|
90
|
+
nested_classes[name] = class_name.constantize
|
|
91
|
+
else
|
|
92
|
+
nested_classes[name] = anonymous_class
|
|
93
|
+
end
|
|
93
94
|
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
generated_attribute_methods.synchronize do
|
|
96
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
|
96
97
|
def #{name}
|
|
97
98
|
nested_attributes[:#{name}] ||= self.class.nested_classes[:#{name}].new(attributes[:#{name}])
|
|
98
99
|
end
|
|
99
|
-
|
|
100
|
+
STR
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
|
102
103
|
def #{name}=(value)
|
|
103
104
|
klass = self.class.nested_classes[:#{name}]
|
|
104
105
|
if value.respond_to?(:to_h)
|
|
@@ -112,91 +113,92 @@ module OptionsModel::Concerns
|
|
|
112
113
|
"`value` should respond to `to_h` or \#{klass}, but got \#{value.class}"
|
|
113
114
|
end
|
|
114
115
|
end
|
|
115
|
-
|
|
116
|
+
STR
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
self.attribute_names_for_nesting << name
|
|
120
|
+
name
|
|
116
121
|
end
|
|
117
122
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
123
|
+
def attribute_defaults
|
|
124
|
+
@attribute_defaults ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
125
|
+
end
|
|
121
126
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
127
|
+
def nested_classes
|
|
128
|
+
@nested_classes ||= ActiveSupport::HashWithIndifferentAccess.new
|
|
129
|
+
end
|
|
125
130
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
def attribute_names_for_nesting
|
|
132
|
+
@attribute_names_for_nesting ||= Set.new
|
|
133
|
+
end
|
|
129
134
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
def attribute_names_for_inlining
|
|
136
|
+
@attribute_names_for_inlining ||= Set.new
|
|
137
|
+
end
|
|
133
138
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
139
|
+
def attribute_names
|
|
140
|
+
attribute_names_for_nesting + attribute_names_for_inlining
|
|
141
|
+
end
|
|
137
142
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
143
|
+
def finalized?
|
|
144
|
+
@finalized ||= false
|
|
145
|
+
end
|
|
141
146
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
147
|
+
def finalize!(nested = true)
|
|
148
|
+
if nested
|
|
149
|
+
nested_classes.values.each &:finalize!
|
|
150
|
+
end
|
|
145
151
|
|
|
146
|
-
|
|
147
|
-
if nested
|
|
148
|
-
nested_classes.values.each &:finalize!
|
|
152
|
+
@finalized = true
|
|
149
153
|
end
|
|
150
154
|
|
|
151
|
-
|
|
152
|
-
end
|
|
155
|
+
protected
|
|
153
156
|
|
|
154
|
-
|
|
157
|
+
def check_name_validity!(symbolized_name)
|
|
158
|
+
if dangerous_attribute_method?(symbolized_name)
|
|
159
|
+
raise ArgumentError, "#{symbolized_name} is defined by #{OptionsModel::Base}. Check to make sure that you don't have an attribute or method with the same name."
|
|
160
|
+
end
|
|
155
161
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
162
|
+
if attribute_names_for_inlining.include?(symbolized_name) || attribute_names_for_nesting.include?(symbolized_name)
|
|
163
|
+
raise ArgumentError, "duplicate define attribute `#{symbolized_name}`"
|
|
164
|
+
end
|
|
159
165
|
end
|
|
160
166
|
|
|
161
|
-
|
|
162
|
-
|
|
167
|
+
def check_not_finalized!
|
|
168
|
+
if finalized?
|
|
169
|
+
raise "can't modify finalized #{self}"
|
|
170
|
+
end
|
|
163
171
|
end
|
|
164
|
-
end
|
|
165
172
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
173
|
+
# A method name is 'dangerous' if it is already (re)defined by OptionsModel, but
|
|
174
|
+
# not by any ancestors. (So 'puts' is not dangerous but 'save' is.)
|
|
175
|
+
def dangerous_attribute_method?(name) # :nodoc:
|
|
176
|
+
method_defined_within?(name, OptionsModel::Base)
|
|
169
177
|
end
|
|
170
|
-
end
|
|
171
178
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
|
180
|
-
if superklass.method_defined?(name) || superklass.private_method_defined?(name)
|
|
181
|
-
klass.instance_method(name).owner != superklass.instance_method(name).owner
|
|
179
|
+
def method_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
|
|
180
|
+
if klass.method_defined?(name) || klass.private_method_defined?(name)
|
|
181
|
+
if superklass.method_defined?(name) || superklass.private_method_defined?(name)
|
|
182
|
+
klass.instance_method(name).owner != superklass.instance_method(name).owner
|
|
183
|
+
else
|
|
184
|
+
true
|
|
185
|
+
end
|
|
182
186
|
else
|
|
183
|
-
|
|
187
|
+
false
|
|
184
188
|
end
|
|
185
|
-
else
|
|
186
|
-
false
|
|
187
189
|
end
|
|
188
|
-
end
|
|
189
190
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
191
|
+
def generated_attribute_methods
|
|
192
|
+
@generated_attribute_methods ||= Module.new {
|
|
193
|
+
extend Mutex_m
|
|
194
|
+
}.tap { |mod| include mod }
|
|
195
|
+
end
|
|
195
196
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
197
|
+
def generated_class_methods
|
|
198
|
+
@generated_class_methods ||= Module.new {
|
|
199
|
+
extend Mutex_m
|
|
200
|
+
}.tap { |mod| extend mod }
|
|
201
|
+
end
|
|
200
202
|
end
|
|
201
203
|
end
|
|
202
204
|
end
|
|
@@ -1,18 +1,20 @@
|
|
|
1
|
-
module OptionsModel
|
|
2
|
-
module
|
|
3
|
-
|
|
1
|
+
module OptionsModel
|
|
2
|
+
module Concerns
|
|
3
|
+
module NameHacking
|
|
4
|
+
extend ActiveSupport::Concern
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def name=(value)
|
|
11
|
-
unless /^[A-Z][a-zA-Z_0-9]*$/ =~ value
|
|
12
|
-
raise ArgumentError, "`name` must a valid class name"
|
|
6
|
+
module ClassMethods
|
|
7
|
+
def name
|
|
8
|
+
@_name
|
|
13
9
|
end
|
|
14
10
|
|
|
15
|
-
|
|
11
|
+
def name=(value)
|
|
12
|
+
unless /^[A-Z][a-zA-Z_0-9]*$/ =~ value
|
|
13
|
+
raise ArgumentError, "`name` must a valid class name"
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
@_name = value
|
|
17
|
+
end
|
|
16
18
|
end
|
|
17
19
|
end
|
|
18
20
|
end
|
|
@@ -1,50 +1,52 @@
|
|
|
1
|
-
module OptionsModel
|
|
2
|
-
module
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
1
|
+
module OptionsModel
|
|
2
|
+
module Concerns
|
|
3
|
+
module Serialization
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def to_h
|
|
7
|
+
hash = {}
|
|
8
|
+
|
|
9
|
+
self.class.attribute_names.each do |attribute_name|
|
|
10
|
+
attribute = public_send(attribute_name)
|
|
11
|
+
if attribute.is_a?(OptionsModel::Base)
|
|
12
|
+
hash[attribute_name] = attribute.to_h
|
|
13
|
+
else
|
|
14
|
+
hash[attribute_name] = attribute
|
|
15
|
+
end
|
|
14
16
|
end
|
|
17
|
+
|
|
18
|
+
hash
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
|
|
21
|
+
def to_h_with_unused
|
|
22
|
+
to_h.merge unused_attributes
|
|
23
|
+
end
|
|
19
24
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
module ClassMethods
|
|
26
|
+
def dump(obj)
|
|
27
|
+
return YAML.dump({}) unless obj
|
|
23
28
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
unless obj.is_a? self
|
|
30
|
+
raise ArgumentError,
|
|
31
|
+
"can't dump: was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
|
|
32
|
+
end
|
|
27
33
|
|
|
28
|
-
|
|
29
|
-
raise ArgumentError,
|
|
30
|
-
"can't dump: was supposed to be a #{self}, but was a #{obj.class}. -- #{obj.inspect}"
|
|
34
|
+
YAML.dump obj.to_h
|
|
31
35
|
end
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
37
|
+
def load(yaml)
|
|
38
|
+
return new unless yaml
|
|
39
|
+
return new unless yaml.is_a?(String) && /^---/.match?(yaml)
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
return new unless yaml
|
|
38
|
-
return new unless yaml.is_a?(String) && /^---/.match?(yaml)
|
|
41
|
+
hash = YAML.load(yaml) || Hash.new
|
|
39
42
|
|
|
40
|
-
|
|
43
|
+
unless hash.is_a? Hash
|
|
44
|
+
raise ArgumentError,
|
|
45
|
+
"can't load: was supposed to be a #{Hash}, but was a #{hash.class}. -- #{hash.inspect}"
|
|
46
|
+
end
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
raise ArgumentError,
|
|
44
|
-
"can't load: was supposed to be a #{Hash}, but was a #{hash.class}. -- #{hash.inspect}"
|
|
48
|
+
new hash
|
|
45
49
|
end
|
|
46
|
-
|
|
47
|
-
new hash
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
end
|