ar_doc_store 0.0.6 → 0.0.7
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 +3 -4
- data/lib/ar_doc_store/attribute_types/array.rb +5 -0
- data/lib/ar_doc_store/attribute_types/base.rb +2 -1
- data/lib/ar_doc_store/attribute_types/embeds_many.rb +136 -71
- data/lib/ar_doc_store/attribute_types/enumeration.rb +3 -3
- data/lib/ar_doc_store/attribute_types/uuid.rb +1 -8
- data/lib/ar_doc_store/embeddable_model.rb +20 -10
- data/lib/ar_doc_store/storage.rb +9 -4
- data/lib/ar_doc_store/version.rb +1 -1
- data/test/embedding_test.rb +14 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b25355bb2fb3a6f42bb5881cc6835432c3130022
|
4
|
+
data.tar.gz: 6f3194e9c30f5ba0a18ab6d6a63b12339438890b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3f7b11c57e6fe7468e9c46ad852047eb1b8b5549e2db4d03a787d4c7332f1530045f5bbbb603747a2ce53032cd108d5bdaac29e9f76837b317d34ead19c97e2f
|
7
|
+
data.tar.gz: bf923904e616456addce4f123bc92184f094ea2af7832d454eb6d7000e5c7a9967d2d89406ee90d35d643ecd22f378fac36866aef0c3ce7ff8af8b9a9121350e
|
data/README.md
CHANGED
@@ -228,10 +228,9 @@ end
|
|
228
228
|
|
229
229
|
## Roadmap
|
230
230
|
1. Default values for attributes. (I haven't needed yet...)
|
231
|
-
2. Ransackers for embedded model attributes. (I haven't needed yet...)
|
232
|
-
3.
|
233
|
-
4.
|
234
|
-
5. It would be nice if you could use the AR fluent query API on stored attributes, where is knows to replace, say, "name" with "data->>name" but I don't see how to do that, and Ransack provides a nice enough wrapper around ARel to get the job done another way.
|
231
|
+
2. Ransackers for embedded model attributes. (I haven't needed yet, but may this summer when the inspiring project goes to phase 2...)
|
232
|
+
3. It would be nice if you could use the AR fluent query API on stored attributes, where is knows to replace, say, "name" with "data->>name" but I don't see how to do that, and Ransack provides a nice enough wrapper around ARel to get the job done another way.
|
233
|
+
4. Migration support to rename attributes, remove attributes. (I haven't needed yet...)
|
235
234
|
|
236
235
|
## Contributing
|
237
236
|
|
@@ -5,8 +5,13 @@ module ArDocStore
|
|
5
5
|
|
6
6
|
def build
|
7
7
|
key = attribute.to_sym
|
8
|
+
default_value = default
|
8
9
|
model.class_eval do
|
9
10
|
store_accessor :data, key
|
11
|
+
define_method key, -> {
|
12
|
+
value = read_store_attribute(:data, key)
|
13
|
+
value or default_value
|
14
|
+
}
|
10
15
|
define_method "#{key}=".to_sym, -> (value) {
|
11
16
|
value = nil if value == ['']
|
12
17
|
write_store_attribute(:data, key, value)
|
@@ -10,10 +10,11 @@ module ArDocStore
|
|
10
10
|
def initialize(model, attribute, options)
|
11
11
|
@model, @attribute, @options = model, attribute, options
|
12
12
|
@model.virtual_attributes[attribute] = self
|
13
|
+
@default = options.delete(:default)
|
13
14
|
end
|
14
15
|
|
15
16
|
def build
|
16
|
-
model.store_attributes conversion, predicate, attribute
|
17
|
+
model.store_attributes conversion, predicate, attribute, default
|
17
18
|
end
|
18
19
|
|
19
20
|
end
|
@@ -1,90 +1,101 @@
|
|
1
1
|
module ArDocStore
|
2
|
+
|
3
|
+
class EmbeddedCollection < Array
|
4
|
+
attr_accessor :parent
|
5
|
+
end
|
6
|
+
|
2
7
|
module AttributeTypes
|
3
8
|
|
4
9
|
class EmbedsManyAttribute < Base
|
10
|
+
|
5
11
|
def build
|
6
12
|
assn_name = attribute.to_sym
|
7
13
|
class_name = options[:class_name] || attribute.to_s.classify
|
8
14
|
model.store_accessor :data, assn_name
|
9
|
-
|
10
|
-
|
15
|
+
create_reader_for assn_name, class_name
|
16
|
+
create_writer_for assn_name, class_name
|
17
|
+
create_build_method_for assn_name, class_name
|
18
|
+
create_ensure_method_for assn_name
|
19
|
+
create_embeds_many_attributes_method(class_name, assn_name)
|
11
20
|
create_embeds_many_validation(assn_name)
|
12
21
|
end
|
13
22
|
|
14
23
|
private
|
15
|
-
|
16
|
-
def
|
24
|
+
|
25
|
+
def add_method(method, block)
|
17
26
|
model.class_eval do
|
18
|
-
define_method
|
19
|
-
ivar = "@#{assn_name}"
|
20
|
-
existing = instance_variable_get(ivar)
|
21
|
-
return existing if existing
|
22
|
-
my_class_name = class_name.constantize
|
23
|
-
items = read_store_attribute(:data, assn_name)
|
24
|
-
if items.present? && items.first.respond_to?(:keys)
|
25
|
-
items = items.map { |item| my_class_name.new(item) }
|
26
|
-
end
|
27
|
-
items ||= []
|
28
|
-
instance_variable_set ivar, (items)
|
29
|
-
items
|
30
|
-
}
|
31
|
-
define_method "#{assn_name}=".to_sym, -> (values) {
|
32
|
-
if values && values.respond_to?(:map)
|
33
|
-
items = values.map { |item|
|
34
|
-
my_class_name = class_name.constantize
|
35
|
-
item.is_a?(my_class_name) ? item : my_class_name.new(item)
|
36
|
-
}
|
37
|
-
else
|
38
|
-
items = []
|
39
|
-
end
|
40
|
-
instance_variable_set "@#{assn_name}", write_store_attribute(:data, assn_name, items)
|
41
|
-
# data_will_change!
|
42
|
-
}
|
43
|
-
define_method "build_#{assn_name.to_s.singularize}", -> (attributes=nil) {
|
44
|
-
assns = self.public_send assn_name
|
45
|
-
item = class_name.constantize.new attributes
|
46
|
-
assns << item
|
47
|
-
public_send "#{assn_name}=", assns
|
48
|
-
item
|
49
|
-
}
|
50
|
-
|
51
|
-
define_method "ensure_#{assn_name.to_s.singularize}", -> {
|
52
|
-
public_send "build_#{assn_name.to_s.singularize}" if self.public_send(assn_name).blank?
|
53
|
-
}
|
54
|
-
# TODO: alias here instead of show the same code twice?
|
55
|
-
define_method "ensure_#{assn_name}", -> {
|
56
|
-
public_send "build_#{assn_name.to_s.singularize}" if self.public_send(assn_name).blank?
|
57
|
-
}
|
27
|
+
define_method method, block
|
58
28
|
end
|
59
29
|
end
|
60
|
-
|
61
|
-
def
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
30
|
+
|
31
|
+
def create_reader_for(assn_name, class_name)
|
32
|
+
add_method assn_name.to_sym, -> {
|
33
|
+
ivar = "@#{assn_name}"
|
34
|
+
existing = instance_variable_get(ivar)
|
35
|
+
return existing if existing
|
36
|
+
my_class_name = class_name.constantize
|
37
|
+
items = read_store_attribute(:data, assn_name)
|
38
|
+
if items.present? && items.first.respond_to?(:keys)
|
39
|
+
items = ArDocStore::EmbeddedCollection.new items.map { |item| my_class_name.new(item) }
|
40
|
+
end
|
41
|
+
items ||= ArDocStore::EmbeddedCollection.new
|
42
|
+
instance_variable_set ivar, (items)
|
43
|
+
items.parent = self
|
44
|
+
items.map {|item| item.parent = self }
|
45
|
+
items
|
46
|
+
}
|
47
|
+
end
|
48
|
+
def create_writer_for(assn_name, class_name)
|
49
|
+
add_method "#{assn_name}=".to_sym, -> (values) {
|
50
|
+
if values && values.respond_to?(:map)
|
51
|
+
items = ArDocStore::EmbeddedCollection.new values.map { |item|
|
52
|
+
my_class_name = class_name.constantize
|
53
|
+
item = item.is_a?(my_class_name) ? item : my_class_name.new(item)
|
54
|
+
item.id
|
55
|
+
item.parent = self
|
56
|
+
item
|
82
57
|
}
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
58
|
+
else
|
59
|
+
items = []
|
60
|
+
end
|
61
|
+
items.parent = self
|
62
|
+
instance_variable_set "@#{assn_name}", write_store_attribute(:data, assn_name, items)
|
63
|
+
}
|
64
|
+
end
|
65
|
+
|
66
|
+
def create_build_method_for(assn_name, class_name)
|
67
|
+
add_method "build_#{assn_name.to_s.singularize}", -> (attributes=nil) {
|
68
|
+
assns = self.public_send assn_name
|
69
|
+
item = class_name.constantize.new attributes
|
70
|
+
item.id
|
71
|
+
item.parent = self
|
72
|
+
assns << item
|
73
|
+
public_send "#{assn_name}=", assns
|
74
|
+
item
|
75
|
+
}
|
76
|
+
end
|
77
|
+
|
78
|
+
def create_ensure_method_for(assn_name)
|
79
|
+
method = -> { public_send "build_#{assn_name.to_s.singularize}" if self.public_send(assn_name).blank? }
|
80
|
+
add_method "ensure_#{assn_name.to_s.singularize}", method
|
81
|
+
add_method "ensure_#{assn_name}", method
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_embeds_many_attributes_method(class_name, assn_name)
|
85
|
+
add_method "#{assn_name}_attributes=", -> (values) {
|
86
|
+
return if values.blank?
|
87
|
+
# if it's a single item then wrap it in an array but how to tell?
|
88
|
+
|
89
|
+
if values.respond_to?(:each)
|
90
|
+
if values.respond_to?(:values)
|
91
|
+
values = values.values
|
92
|
+
end
|
93
|
+
else
|
94
|
+
values = [values]
|
95
|
+
end
|
96
|
+
models = public_send assn_name
|
97
|
+
public_send "#{assn_name}=", AssignEmbedsManyAttributes.new(self, class_name, assn_name, models, values).models
|
98
|
+
}
|
88
99
|
end
|
89
100
|
|
90
101
|
def create_embeds_many_validation(assn_name)
|
@@ -98,5 +109,59 @@ module ArDocStore
|
|
98
109
|
|
99
110
|
end
|
100
111
|
|
112
|
+
class AssignEmbedsManyAttributes
|
113
|
+
attr_reader :models, :assn_name, :parent, :class_name
|
114
|
+
def initialize(parent, class_name, assn_name, models, values)
|
115
|
+
@parent, @class_name, @assn_name, @models, @values = parent, class_name, assn_name, models, values
|
116
|
+
values.each { |value|
|
117
|
+
value = value.symbolize_keys
|
118
|
+
Rails.logger.info value.inspect
|
119
|
+
if value.key?(:id)
|
120
|
+
Rails.logger.info 'process_existing_model'
|
121
|
+
process_existing_model(value)
|
122
|
+
else
|
123
|
+
Rails.logger.info 'adding new model'
|
124
|
+
add(value)
|
125
|
+
end
|
126
|
+
}
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
|
131
|
+
attr_writer :models, :values
|
132
|
+
attr_reader :values, :assn_name
|
133
|
+
|
134
|
+
def process_existing_model(value)
|
135
|
+
return false unless value.key?(:id)
|
136
|
+
model = find_model_by_value(value)
|
137
|
+
model && destroy_or_update(model, value) or add(value)
|
138
|
+
end
|
139
|
+
|
140
|
+
def destroy_or_update(model, value)
|
141
|
+
destroy(model, value) or update_attributes(model, value)
|
142
|
+
end
|
143
|
+
|
144
|
+
def add(value)
|
145
|
+
models << class_name.constantize.new(value)
|
146
|
+
end
|
147
|
+
|
148
|
+
def destroy(model, value)
|
149
|
+
wants_to_die?(value) && models.delete(model)
|
150
|
+
end
|
151
|
+
|
152
|
+
def update_attributes(model, value)
|
153
|
+
model.apply_attributes(value)
|
154
|
+
end
|
155
|
+
|
156
|
+
def wants_to_die?(value)
|
157
|
+
value.key?(:_destroy) && ArDocStore.convert_boolean(value[:_destroy])
|
158
|
+
end
|
159
|
+
|
160
|
+
def find_model_by_value(value)
|
161
|
+
models.detect { |item| item.id == value[:id] }
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
|
101
166
|
end
|
102
167
|
end
|
@@ -8,10 +8,11 @@ module ArDocStore
|
|
8
8
|
dictionary = options[:values]
|
9
9
|
multiple = options[:multiple]
|
10
10
|
strict = options[:strict]
|
11
|
+
default_value = default
|
11
12
|
model.class_eval do
|
12
13
|
|
13
14
|
if multiple
|
14
|
-
attribute key, as: :array
|
15
|
+
attribute key, as: :array, default: default_value
|
15
16
|
if strict
|
16
17
|
define_method "validate_#{key}" do
|
17
18
|
value = public_send(key)
|
@@ -19,9 +20,8 @@ module ArDocStore
|
|
19
20
|
end
|
20
21
|
validate "validate_#{key}".to_sym
|
21
22
|
end
|
22
|
-
# TODO should we do anything for strict option?
|
23
23
|
else
|
24
|
-
attribute key, as: :string
|
24
|
+
attribute key, as: :string, default: default_value
|
25
25
|
if strict
|
26
26
|
validates_inclusion_of key, in: dictionary, allow_blank: true
|
27
27
|
end
|
@@ -8,7 +8,6 @@ module ArDocStore
|
|
8
8
|
key = attribute.to_sym
|
9
9
|
model.class_eval do
|
10
10
|
store_accessor :data, key
|
11
|
-
define_method "#{key}?".to_sym, -> { !!key }
|
12
11
|
define_method key, -> {
|
13
12
|
value = read_store_attribute(:data, key)
|
14
13
|
unless value
|
@@ -18,18 +17,12 @@ module ArDocStore
|
|
18
17
|
value
|
19
18
|
}
|
20
19
|
define_method "#{key}=".to_sym, -> (value) {
|
21
|
-
|
22
|
-
res = true if value == 'true' || value == true || value == '1' || value == 1
|
23
|
-
res = false if value == 'false' || value == false || value == '0' || value == 0
|
24
|
-
write_store_attribute(:data, key, res)
|
20
|
+
write_store_attribute(:data, key, value)
|
25
21
|
}
|
26
22
|
add_ransacker(key, 'text')
|
27
23
|
end
|
28
24
|
end
|
29
25
|
|
30
|
-
def type
|
31
|
-
:boolean
|
32
|
-
end
|
33
26
|
|
34
27
|
end
|
35
28
|
|
@@ -4,20 +4,20 @@ module ArDocStore
|
|
4
4
|
|
5
5
|
def self.included(mod)
|
6
6
|
|
7
|
-
mod.send :include, ArDocStore::Storage
|
8
|
-
mod.send :include, ArDocStore::Embedding
|
9
|
-
mod.send :include, InstanceMethods
|
10
|
-
mod.send :extend, ClassMethods
|
11
7
|
mod.send :include, ActiveModel::AttributeMethods
|
12
8
|
mod.send :include, ActiveModel::Validations
|
13
9
|
mod.send :include, ActiveModel::Conversion
|
14
10
|
mod.send :extend, ActiveModel::Naming
|
15
11
|
mod.send :include, ActiveModel::Dirty
|
16
12
|
mod.send :include, ActiveModel::Serialization
|
13
|
+
mod.send :include, ArDocStore::Storage
|
14
|
+
mod.send :include, ArDocStore::Embedding
|
15
|
+
mod.send :include, InstanceMethods
|
16
|
+
mod.send :extend, ClassMethods
|
17
17
|
|
18
18
|
mod.class_eval do
|
19
19
|
attr_accessor :_destroy
|
20
|
-
attr_accessor :attributes
|
20
|
+
attr_accessor :attributes, :parent
|
21
21
|
|
22
22
|
class_attribute :virtual_attributes
|
23
23
|
self.virtual_attributes ||= HashWithIndifferentAccess.new
|
@@ -33,20 +33,26 @@ module ArDocStore
|
|
33
33
|
|
34
34
|
def initialize(attrs=HashWithIndifferentAccess.new)
|
35
35
|
@attributes = HashWithIndifferentAccess.new
|
36
|
+
self.parent = attrs.delete(:parent)
|
36
37
|
apply_attributes attrs
|
37
38
|
end
|
38
39
|
|
39
40
|
def apply_attributes(attrs=HashWithIndifferentAccess.new)
|
40
|
-
return self unless attrs
|
41
|
-
attrs.each { |key, value|
|
42
|
-
key = "#{key}=".to_sym
|
43
|
-
self.public_send(key, value) if methods.include?(key)
|
44
|
-
}
|
45
41
|
virtual_attributes.keys.each do |attr|
|
46
42
|
@attributes[attr] ||= nil
|
47
43
|
end
|
44
|
+
if attrs
|
45
|
+
attrs.each { |key, value|
|
46
|
+
key = "#{key}=".to_sym
|
47
|
+
self.public_send(key, value) if methods.include?(key)
|
48
|
+
}
|
49
|
+
end
|
48
50
|
self
|
49
51
|
end
|
52
|
+
|
53
|
+
def save
|
54
|
+
parent && parent.save
|
55
|
+
end
|
50
56
|
|
51
57
|
def persisted?
|
52
58
|
false
|
@@ -69,6 +75,10 @@ module ArDocStore
|
|
69
75
|
true
|
70
76
|
end
|
71
77
|
|
78
|
+
def to_param
|
79
|
+
id
|
80
|
+
end
|
81
|
+
|
72
82
|
end
|
73
83
|
|
74
84
|
module ClassMethods
|
data/lib/ar_doc_store/storage.rb
CHANGED
@@ -69,23 +69,28 @@ module ArDocStore
|
|
69
69
|
end
|
70
70
|
end
|
71
71
|
|
72
|
-
def store_attributes(typecast_method, predicate=nil, attributes=[])
|
72
|
+
def store_attributes(typecast_method, predicate=nil, attributes=[], default_value=nil)
|
73
73
|
attributes = [attributes] unless attributes.respond_to?(:each)
|
74
74
|
attributes.each do |key|
|
75
75
|
store_accessor :data, key
|
76
76
|
add_ransacker(key, predicate)
|
77
77
|
if typecast_method.is_a?(Symbol)
|
78
|
-
store_attribute_from_symbol typecast_method, key
|
78
|
+
store_attribute_from_symbol typecast_method, key, default_value
|
79
79
|
else
|
80
80
|
store_attribute_from_class typecast_method, key
|
81
81
|
end
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
-
def store_attribute_from_symbol(typecast_method, key)
|
85
|
+
def store_attribute_from_symbol(typecast_method, key, default_value)
|
86
86
|
define_method key.to_sym, -> {
|
87
87
|
value = read_store_attribute(:data, key)
|
88
|
-
|
88
|
+
if value
|
89
|
+
value.public_send(typecast_method)
|
90
|
+
elsif default_value
|
91
|
+
write_store_attribute(:data, key, default_value)
|
92
|
+
default_value
|
93
|
+
end
|
89
94
|
}
|
90
95
|
define_method "#{key}=".to_sym, -> (value) {
|
91
96
|
# data_will_change! if @initalized
|
data/lib/ar_doc_store/version.rb
CHANGED
data/test/embedding_test.rb
CHANGED
@@ -48,6 +48,20 @@ class EmbeddingTest < MiniTest::Test
|
|
48
48
|
assert_nil building.restrooms.detect {|restroom| restroom.id == restrooms_attributes[:a2][:id] }
|
49
49
|
end
|
50
50
|
|
51
|
+
def test_attributes_method_embeds_many_does_not_clobber_existing_embeds_that_are_not_in_array
|
52
|
+
building = Building.new
|
53
|
+
building.restrooms << Restroom.new(door_attributes: { clear_distance: 5, opening_force: 13, clear_space: 43 })
|
54
|
+
building.restrooms << Restroom.new(door_attributes: { clear_distance: 6, opening_force: 14, clear_space: 44 })
|
55
|
+
building.restrooms << Restroom.new(door_attributes: { clear_distance: 7, opening_force: 15, clear_space: 45 })
|
56
|
+
restrooms_attributes = {
|
57
|
+
a1: { id: building.restrooms[0].id, door_attributes: { clear_distance: 10 } },
|
58
|
+
}
|
59
|
+
building.restrooms_attributes = restrooms_attributes
|
60
|
+
assert_equal 3, building.restrooms.size
|
61
|
+
assert_equal 10, building.restrooms.first.door.clear_distance
|
62
|
+
assert_equal 15, building.restrooms.last.door.opening_force
|
63
|
+
end
|
64
|
+
|
51
65
|
def test_attribute_validity_of_embedded_model_from_model
|
52
66
|
b = Building.new
|
53
67
|
r = Restroom.new
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ar_doc_store
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- David Furber
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-03-
|
11
|
+
date: 2015-03-11 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|