ar_doc_store 0.0.6 → 0.0.7

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