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