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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 768bd78def2275242d66654ea62177ded6df21eb
4
- data.tar.gz: 62c9f288637be16cfbf353b57acbfed050dbbcb8
3
+ metadata.gz: b25355bb2fb3a6f42bb5881cc6835432c3130022
4
+ data.tar.gz: 6f3194e9c30f5ba0a18ab6d6a63b12339438890b
5
5
  SHA512:
6
- metadata.gz: 4c41aea7206dfc929c14c6d6a09e3c7132007f51245b5a754a77c2c352622fd9609b235d1048addaaa41f3ee5b2b2131e1776cb845518032cf0320c6fcca59b5
7
- data.tar.gz: f1e58cbec99ea28a87fdcea51242fa5a69d84b01754b5fce69c46ec0244463eb3dc708802b0c533d04fbb4c0c0e00206d47732e1f4996abfbf7d3ce4642335d6
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. Refactor the EmbedsOne and EmbedsMany modules to use a Builder class instead of procedural metaprogramming. (Hello Code Climate...)
233
- 4. Currently when you mass-assign values to an embedded model, you need to assign all the values. It basically replaces what is there with what you send in, removing what has :_destroy set. I would be nice if it could do a smarter find or create behavior.
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
- create_embeds_many_accessors(assn_name, class_name)
10
- create_embeds_many_attributes_method(assn_name)
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 create_embeds_many_accessors(assn_name, class_name)
24
+
25
+ def add_method(method, block)
17
26
  model.class_eval do
18
- define_method assn_name.to_sym, -> {
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 create_embeds_many_attributes_method(assn_name)
62
- model.class_eval do
63
- define_method "#{assn_name}_attributes=", -> (values) {
64
- values = values && values.values || []
65
- models = public_send assn_name
66
- new_models = []
67
- values.each { |value|
68
- value.symbolize_keys!
69
- if value.key?(:id)
70
- next if value.key?(:_destroy) && ArDocStore.convert_boolean(value[:_destroy])
71
- existing = models.detect { |item| item.id == value[:id] }
72
- if existing
73
- new_models << existing.apply_attributes(value)
74
- else
75
- # If there was an ID but we can't find it now, do we add it back or ignore it?
76
- # Right now, add it back.
77
- new_models << public_send("build_#{assn_name}", value)
78
- end
79
- else
80
- new_models << public_send("build_#{assn_name}", value)
81
- end
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
- public_send "#{assn_name}=", new_models
84
- # values = values.reject { |item| item[:_destroy] && item[:_destroy].to_bool }
85
- # public_send "#{assn_name}=", values
86
- }
87
- end
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
- res = nil
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
@@ -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
- value.public_send(typecast_method) if value
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
@@ -1,3 +1,3 @@
1
1
  module ArDocStore
2
- VERSION = "0.0.6"
2
+ VERSION = "0.0.7"
3
3
  end
@@ -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.6
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-03 00:00:00.000000000 Z
11
+ date: 2015-03-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord