ar_doc_store 1.0.5 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/ar_doc_store.gemspec +3 -3
  3. data/lib/ar_doc_store.rb +30 -28
  4. data/lib/ar_doc_store/attributes/array.rb +25 -0
  5. data/lib/ar_doc_store/attributes/base.rb +72 -0
  6. data/lib/ar_doc_store/attributes/boolean.rb +17 -0
  7. data/lib/ar_doc_store/attributes/callback_support.rb +21 -0
  8. data/lib/ar_doc_store/attributes/date.rb +21 -0
  9. data/lib/ar_doc_store/attributes/datetime.rb +21 -0
  10. data/lib/ar_doc_store/attributes/decimal.rb +17 -0
  11. data/lib/ar_doc_store/attributes/embeds_base.rb +35 -0
  12. data/lib/ar_doc_store/attributes/embeds_many.rb +143 -0
  13. data/lib/ar_doc_store/{attribute_types/embeds_one_attribute.rb → attributes/embeds_one.rb} +25 -30
  14. data/lib/ar_doc_store/{attribute_types/enumeration_attribute.rb → attributes/enumeration.rb} +2 -6
  15. data/lib/ar_doc_store/{attribute_types/float_attribute.rb → attributes/float.rb} +5 -8
  16. data/lib/ar_doc_store/{attribute_types/integer_attribute.rb → attributes/integer.rb} +5 -8
  17. data/lib/ar_doc_store/attributes/string.rb +13 -0
  18. data/lib/ar_doc_store/embeddable_model.rb +51 -75
  19. data/lib/ar_doc_store/embedded_collection.rb +14 -0
  20. data/lib/ar_doc_store/embedding.rb +0 -2
  21. data/lib/ar_doc_store/model.rb +18 -8
  22. data/lib/ar_doc_store/storage.rb +4 -127
  23. data/lib/ar_doc_store/types/embeds_many.rb +52 -0
  24. data/lib/ar_doc_store/types/embeds_one.rb +42 -0
  25. data/lib/ar_doc_store/version.rb +1 -1
  26. data/test/{attribute_types → attributes}/array_attribute_test.rb +5 -1
  27. data/test/{attribute_types → attributes}/boolean_attribute_test.rb +7 -1
  28. data/test/attributes/date_attribute_test.rb +52 -0
  29. data/test/attributes/datetime_attribute_test.rb +54 -0
  30. data/test/attributes/decimal_attribute_test.rb +38 -0
  31. data/test/attributes/embeds_many_attribute_test.rb +78 -0
  32. data/test/attributes/embeds_one_attribute_test.rb +80 -0
  33. data/test/{attribute_types → attributes}/enumeration_attribute_test.rb +5 -0
  34. data/test/{attribute_types → attributes}/float_attribute_test.rb +6 -1
  35. data/test/attributes/integer_attribute_test.rb +63 -0
  36. data/test/{attribute_types → attributes}/string_attribute_test.rb +6 -0
  37. data/test/test_helper.rb +22 -94
  38. metadata +57 -35
  39. data/lib/ar_doc_store/attribute_types/array_attribute.rb +0 -30
  40. data/lib/ar_doc_store/attribute_types/base_attribute.rb +0 -61
  41. data/lib/ar_doc_store/attribute_types/boolean_attribute.rb +0 -25
  42. data/lib/ar_doc_store/attribute_types/datetime_attribute.rb +0 -17
  43. data/lib/ar_doc_store/attribute_types/embeds_many_attribute.rb +0 -165
  44. data/lib/ar_doc_store/attribute_types/string_attribute.rb +0 -17
  45. data/lib/ar_doc_store/attribute_types/uuid_attribute.rb +0 -30
  46. data/test/attribute_types/datetime_attribute_test.rb +0 -35
  47. data/test/attribute_types/integer_attribute_test.rb +0 -33
  48. data/test/originals/dirty_attributes_test.rb +0 -35
  49. data/test/originals/embedded_model_attribute_test.rb +0 -23
  50. data/test/originals/embedding_test.rb +0 -82
@@ -1,51 +1,45 @@
1
1
  module ArDocStore
2
- module AttributeTypes
2
+ module Attributes
3
+ class EmbedsOne < EmbedsBase
4
+ private
3
5
 
4
- class EmbedsOneAttribute < BaseAttribute
5
- attr_reader :class_name
6
- def build
7
- @class_name = options[:class_name] || attribute.to_s.classify
8
- create_accessors
9
- create_embed_one_attributes_method
10
- create_embeds_one_accessors
11
- create_embeds_one_validation
12
- end
13
-
14
- def create_accessors
6
+ def store_attribute
15
7
  model.class_eval <<-CODE, __FILE__, __LINE__ + 1
8
+ attribute :#{attribute}, ArDocStore::Types::EmbedsOne.new("#{@class_name}")
16
9
  def #{attribute}
17
- @#{attribute} || begin
18
- item = read_store_attribute json_column, :#{attribute}
19
- item = #{class_name}.build(item) unless item.is_a?(#{class_name})
20
- @#{attribute} = item
21
- end
10
+ value = send :attribute, :#{attribute}
11
+ value && value.parent = self
12
+ value && value.embedded_as = :#{attribute}
13
+ value
22
14
  end
23
-
24
15
  def #{attribute}=(value)
25
- if value == '' || !value
26
- value = nil
27
- elsif value.is_a?(#{class_name})
28
- value = value.attributes
29
- end
30
- value = #{class_name}.build value
31
- @#{attribute} = value
32
- write_store_attribute json_column, :#{attribute}, value
16
+ value = nil if value == '' || value == ['']
17
+ write_attribute :#{attribute}, value
18
+ #{attribute}.parent = self
19
+ #{attribute}.embedded_as = :#{attribute}
20
+ write_store_attribute json_column, :#{attribute}, #{attribute}
21
+ #{attribute}
33
22
  end
34
23
  CODE
35
24
  end
36
25
 
37
- def create_embeds_one_accessors
26
+ def create_build_method
38
27
  model.class_eval <<-CODE, __FILE__, __LINE__ + 1
39
28
  def build_#{attribute}(attributes=nil)
40
- self.#{attribute} = #{class_name}.build(attributes)
29
+ self.#{attribute} = #{class_name}.build(attributes, self)
41
30
  end
31
+ CODE
32
+ end
33
+
34
+ def create_ensure_method
35
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
42
36
  def ensure_#{attribute}
43
37
  #{attribute} || build_#{attribute}
44
38
  end
45
39
  CODE
46
40
  end
47
41
 
48
- def create_embed_one_attributes_method
42
+ def create_attributes_method
49
43
  model.class_eval <<-CODE, __FILE__, __LINE__ + 1
50
44
  def #{attribute}_attributes=(values={})
51
45
  values.symbolize_keys! if values.respond_to?(:symbolize_keys!)
@@ -54,12 +48,13 @@ module ArDocStore
54
48
  else
55
49
  item = ensure_#{attribute}
56
50
  item.attributes = values
51
+ item
57
52
  end
58
53
  end
59
54
  CODE
60
55
  end
61
56
 
62
- def create_embeds_one_validation
57
+ def create_validation
63
58
  model.class_eval <<-CODE, __FILE__, __LINE__ + 1
64
59
  def validate_embedded_record_for_#{attribute}
65
60
  validate_embeds_one :#{attribute}
@@ -1,8 +1,6 @@
1
1
  module ArDocStore
2
- module AttributeTypes
3
-
4
- class EnumerationAttribute < BaseAttribute
5
-
2
+ module Attributes
3
+ class Enumeration < Base
6
4
  def build
7
5
  key = attribute.to_sym
8
6
  dictionary = options[:values]
@@ -10,7 +8,6 @@ module ArDocStore
10
8
  strict = options[:strict]
11
9
  default_value = default
12
10
  model.class_eval do
13
-
14
11
  if multiple
15
12
  json_attribute key, as: :array, default: default_value
16
13
  if strict
@@ -35,7 +32,6 @@ module ArDocStore
35
32
  def type
36
33
  :string
37
34
  end
38
-
39
35
  end
40
36
  end
41
37
  end
@@ -1,11 +1,6 @@
1
1
  module ArDocStore
2
- module AttributeTypes
3
-
4
- class FloatAttribute < BaseAttribute
5
- def conversion
6
- :to_f
7
- end
8
-
2
+ module Attributes
3
+ class Float < Base
9
4
  def predicate
10
5
  'float'
11
6
  end
@@ -14,7 +9,9 @@ module ArDocStore
14
9
  :number
15
10
  end
16
11
 
12
+ def attribute_type
13
+ ActiveModel::Type::Float
14
+ end
17
15
  end
18
-
19
16
  end
20
17
  end
@@ -1,11 +1,6 @@
1
1
  module ArDocStore
2
- module AttributeTypes
3
-
4
- class IntegerAttribute < BaseAttribute
5
- def conversion
6
- :to_i
7
- end
8
-
2
+ module Attributes
3
+ class Integer < Base
9
4
  def predicate
10
5
  'int'
11
6
  end
@@ -14,7 +9,9 @@ module ArDocStore
14
9
  :number
15
10
  end
16
11
 
12
+ def attribute_type
13
+ ActiveModel::Type::Integer
14
+ end
17
15
  end
18
-
19
16
  end
20
17
  end
@@ -0,0 +1,13 @@
1
+ module ArDocStore
2
+ module Attributes
3
+ class String < Base
4
+ def predicate
5
+ 'text'
6
+ end
7
+
8
+ def attribute_type
9
+ ActiveModel::Type::String
10
+ end
11
+ end
12
+ end
13
+ end
@@ -3,71 +3,67 @@ require 'securerandom'
3
3
  module ArDocStore
4
4
  module EmbeddableModel
5
5
  def self.included(mod)
6
+ mod.send :include, ActiveModel::Model
7
+ mod.send :include, ActiveModel::Attributes
6
8
  mod.send :include, ActiveModel::AttributeMethods
7
- mod.send :include, ActiveModel::Validations
8
- mod.send :include, ActiveModel::Conversion
9
- mod.send :extend, ActiveModel::Naming
10
9
  mod.send :include, ActiveModel::Dirty
11
10
  mod.send :include, ActiveModel::Serialization
12
11
  mod.send :include, ArDocStore::Storage
13
12
  mod.send :include, ArDocStore::Embedding
13
+ mod.send :include, ActiveSupport::Callbacks
14
14
  mod.send :include, InstanceMethods
15
15
  mod.send :extend, ClassMethods
16
16
 
17
17
  mod.class_eval do
18
+ define_callbacks :persist
18
19
  attr_accessor :_destroy
19
20
  attr_accessor :parent
20
- attr_reader :attributes
21
+ attr_accessor :embedded_as
21
22
 
22
- class_attribute :virtual_attributes
23
- self.virtual_attributes ||= HashWithIndifferentAccess.new
23
+ class_attribute :json_attributes
24
+ self.json_attributes = HashWithIndifferentAccess.new
24
25
 
25
- delegate :as_json, to: :attributes
26
-
27
- json_attribute :id, :uuid
28
-
29
- def self.attribute(name, *args)
30
- json_attribute name, *args
31
- end
26
+ json_attribute :id, :string #, default: -> { SecureRandom.uuid }
32
27
 
28
+ set_callback :persist, :before, :ensure_id
29
+ set_callback :persist, :after, :mark_embeds_as_persisted
33
30
  end
34
-
35
31
  end
36
32
 
37
33
  module InstanceMethods
34
+ def initialize(values={})
35
+ super(values)
36
+ values && persisted? && values.keys.each do |key|
37
+ mutations_from_database.forget_change(key)
38
+ end
39
+ end
38
40
 
39
- def initialize(attrs=HashWithIndifferentAccess.new)
40
- @_initialized = true
41
- initialize_attributes attrs
41
+ def embedded?
42
+ true
42
43
  end
43
44
 
44
- def instantiate(attrs=HashWithIndifferentAccess.new)
45
- initialize_attributes attrs
46
- @_initialized = true
47
- self
45
+ def persist
46
+ run_callbacks :persist
48
47
  end
49
48
 
50
- def initialize_attributes(attrs)
51
- @attributes ||= HashWithIndifferentAccess.new
52
- self.parent = attributes.delete(:parent) if attributes
53
- self.attributes = attrs
49
+ def ensure_id
50
+ self.id ||= SecureRandom.uuid
54
51
  end
55
52
 
56
- def attributes=(attrs=HashWithIndifferentAccess.new)
57
- virtual_attributes.keys.each do |attr|
58
- @attributes[attr] ||= nil
59
- end
60
- if attrs
61
- attrs.each { |key, value|
62
- key = "#{key}=".to_sym
63
- self.public_send(key, value) if methods.include?(key)
64
- }
53
+ def mark_embeds_as_persisted
54
+ json_attributes.values.each do |value|
55
+ if value.respond_to?(:embedded?) && value.embedded? && respond_to?(value.attribute) && !public_send(value.attribute).nil?
56
+ public_send(value.attribute).persist
57
+ end
65
58
  end
66
- self
59
+ end
60
+
61
+ def new_record?
62
+ id.blank?
67
63
  end
68
64
 
69
65
  def persisted?
70
- !!read_store_attribute(nil, :id)
66
+ id.present?
71
67
  end
72
68
 
73
69
  def inspect
@@ -79,58 +75,38 @@ module ArDocStore
79
75
  end
80
76
 
81
77
  def write_store_attribute(store, attribute, value)
82
- if @_initialized
83
- old_value = attributes[attribute]
84
- if attribute.to_s != 'id' && value != old_value
85
- if Rails.version >= '5.2.0'
86
- set_attribute_was(attribute, old_value)
87
- mutations_from_database.force_change(attribute)
88
- else
89
- public_send :"#{attribute}_will_change!"
90
- end
91
- if parent
92
- parent.public_send("#{parent.json_column}_will_change!")
93
- end
78
+ if parent
79
+ if parent.is_a?(EmbeddedCollection)
80
+ parent.save
81
+ else
82
+ parent.send :write_store_attribute, store, embedded_as, as_json
94
83
  end
95
84
  end
96
- attributes[attribute] = value
97
- end
98
-
99
- def write_default_store_attribute(attr, value)
100
- attributes[attr] = value
85
+ value
101
86
  end
102
87
 
103
88
  def to_param
104
89
  id
105
90
  end
106
91
 
107
- def id_will_change!
92
+ def as_json(_=nil)
93
+ attributes.inject({}) do |attrs, attr|
94
+ attrs[attr[0]] = attr[1] unless attr[1].nil?
95
+ attrs
96
+ end
108
97
  end
109
-
110
98
  end
111
99
 
112
100
  module ClassMethods
113
-
114
- #:nodoc:
115
- def store_accessor(store, key)
116
- self.virtual_attributes ||= HashWithIndifferentAccess.new
117
- self.virtual_attributes = virtual_attributes.merge key => true
118
- key = key.to_sym
119
- define_method key, -> { read_store_attribute(json_column, key) }
120
- define_method "#{key}=".to_sym, -> (value) { write_store_attribute json_column, key, value }
101
+ def build(attrs=HashWithIndifferentAccess.new, parent = nil)
102
+ model = if attrs.is_a?(self.class)
103
+ attrs
104
+ else
105
+ new(attrs)
106
+ end
107
+ model.parent = parent
108
+ model
121
109
  end
122
-
123
- def build(attrs=HashWithIndifferentAccess.new)
124
- if attrs.is_a?(self.class)
125
- attrs
126
- else
127
- instance = allocate
128
- instance.instantiate attrs
129
- end
130
- end
131
-
132
-
133
110
  end
134
-
135
111
  end
136
112
  end
@@ -0,0 +1,14 @@
1
+ module ArDocStore
2
+ class EmbeddedCollection < Array
3
+ attr_accessor :parent, :embedded_as
4
+ def save
5
+ parent.send :write_store_attribute, parent.json_column, embedded_as, as_json
6
+ end
7
+ def persist
8
+ each &:persist
9
+ end
10
+ def inspect
11
+ "ArDocStore::EmbeddedCollection - #{as_json.inspect}"
12
+ end
13
+ end
14
+ end
@@ -6,7 +6,6 @@ module ArDocStore
6
6
  end
7
7
 
8
8
  module ClassMethods
9
-
10
9
  def embeds_many(assn_name, *args)
11
10
  json_attribute assn_name, :embeds_many, *args
12
11
  end
@@ -14,7 +13,6 @@ module ArDocStore
14
13
  def embeds_one(assn_name, *args)
15
14
  json_attribute assn_name, :embeds_one, *args
16
15
  end
17
-
18
16
  end
19
17
 
20
18
 
@@ -4,19 +4,29 @@ module ArDocStore
4
4
  mod.send :include, ArDocStore::Storage
5
5
  mod.send :include, ArDocStore::Embedding
6
6
  mod.send :include, InstanceMethods
7
+ mod.after_initialize :assign_json_data
8
+ mod.before_save :mark_embeds_as_persisted
7
9
  end
8
10
 
9
11
  module InstanceMethods
10
-
11
- def write_store_attribute(store_attribute, key, value)
12
- public_send "#{key}_will_change!"
13
- super(store_attribute, key, value)
12
+ def assign_json_data
13
+ json_data = self[json_column]
14
+ return if json_data.blank?
15
+ json_attributes.keys.each do |key|
16
+ next unless json_data.key?(key)
17
+ send :attribute=, key, json_data[key]
18
+ self[key].parent = self if self[key].respond_to?(:parent)
19
+ self[key].embedded_as = key if self[key].respond_to?(:embedded_as)
20
+ mutations_from_database.forget_change(key) unless new_record?
21
+ end
14
22
  end
15
-
16
- def write_default_store_attribute(key, default_value)
17
- data[key] = default_value
23
+ def mark_embeds_as_persisted
24
+ json_attributes.values.each do |value|
25
+ if value.respond_to?(:embedded?) && value.embedded? && respond_to?(value.attribute) && !public_send(value.attribute).nil?
26
+ public_send(value.attribute).persist
27
+ end
28
+ end
18
29
  end
19
-
20
30
  end
21
31
  end
22
32
  end
@@ -5,77 +5,19 @@ module ArDocStore
5
5
 
6
6
  mod.class_attribute :json_column
7
7
  mod.json_column ||= :data
8
- mod.class_attribute :virtual_attributes
9
- mod.virtual_attributes ||= HashWithIndifferentAccess.new
10
-
11
- mod.send :include, InstanceMethods
8
+ mod.class_attribute :json_attributes
9
+ mod.json_attributes ||= HashWithIndifferentAccess.new
12
10
  mod.send :extend, ClassMethods
13
11
  end
14
12
 
15
- module InstanceMethods
16
-
17
- def json_column
18
- self.class.json_column
19
- end
20
-
21
- def write_attribute(name, value)
22
- if is_stored_attribute?(name)
23
- write_store_attribute json_column, attribute_name_from_foreign_key(name), value
24
- else
25
- super
26
- end
27
- end
28
-
29
- def read_attribute(name)
30
- if is_stored_attribute?(name)
31
- read_store_attribute json_column, attribute_name_from_foreign_key(name)
32
- else
33
- super
34
- end
35
- end
36
-
37
- private
38
-
39
- def is_stored_attribute?(name)
40
- name = name.to_sym
41
- is_store_accessor_method?(name) || name =~ /data\-\>\>/
42
- end
43
-
44
- def is_store_accessor_method?(name)
45
- name = name.to_sym
46
- self.class.stored_attributes[json_column] && self.class.stored_attributes[json_column].include?(name)
47
- end
48
-
49
- def attribute_name_from_foreign_key(name)
50
- is_store_accessor_method?(name) ? name : name.match(/\'(\w+)\'/)[0].gsub("'", '')
51
- end
52
-
53
- def clean_attribute_names_for_arel(attribute_names)
54
- attribute_names.reject{|attribute_name| is_stored_attribute?(attribute_name)}
55
- end
56
-
57
- # Overridden otherwise insert and update queries get fooled into thinking that stored attributes are real columns.
58
- def arel_attributes_with_values_for_create(attribute_names) #:nodoc:
59
- super clean_attribute_names_for_arel(attribute_names)
60
- end
61
-
62
- # Overridden otherwise insert and update queries get fooled into thinking that stored attributes are real columns.
63
- def arel_attributes_with_values_for_update(attribute_names) #:nodoc:
64
- super clean_attribute_names_for_arel(attribute_names)
65
- end
66
- end
67
-
68
13
  module ClassMethods
69
-
70
14
  def json_attribute(name, *args)
71
15
  type = args.shift if args.first.is_a?(Symbol)
72
16
  options = args.extract_options!
73
17
  type ||= options.delete(:as) || :string
74
- class_name = ArDocStore.mappings[type] || "ArDocStore::AttributeTypes::#{type.to_s.classify}Attribute"
18
+ class_name = ArDocStore.mappings[type] || "ArDocStore::Attributes::#{type.to_s.classify}"
75
19
  raise "Invalid attribute type: #{class_name}" unless const_defined?(class_name)
76
- class_name.constantize.build self, name, options
77
- define_virtual_attribute_method name
78
- define_method "#{name}?", -> { public_send(name).present? }
20
+ json_attributes[name] = class_name.constantize.build self, name, options
79
21
  end
80
22
 
81
23
  #:nodoc:
@@ -89,71 +31,6 @@ module ArDocStore
89
31
  Arel.sql(sql)
90
32
  end
91
33
  end
92
-
93
- # Pretty much the same as define_attribute_method but skipping the matches that create read and write methods
94
- def define_virtual_attribute_method(attr_name)
95
- attr_name = attr_name.to_s
96
- attribute_method_matchers.each do |matcher|
97
- method_name = matcher.method_name(attr_name)
98
- next if instance_method_already_implemented?(method_name)
99
- next if %w{attribute attribute= attribute_before_type_cast}.include? matcher.method_missing_target
100
- generate_method = "define_method_#{matcher.method_missing_target}"
101
- if respond_to?(generate_method, true)
102
- send(generate_method, attr_name)
103
- else
104
- define_proxy_call true, generated_attribute_methods, method_name, matcher.method_missing_target, attr_name.to_s
105
- end
106
- end
107
- attribute_method_matchers_cache.clear
108
- end
109
-
110
-
111
- # TODO: Remove the following deprecated methods once projects that use them have been refactored.
112
- #:nodoc:
113
- def store_attributes(typecast_method, predicate=nil, attributes=[], default_value=nil)
114
- attributes = [attributes] unless attributes.respond_to?(:each)
115
- attributes.each do |key|
116
- store_attribute key, typecast_method, predicate, default_value
117
- end
118
- end
119
-
120
- # Allows you to define several string attributes at once. Deprecated.
121
- def string_attributes(*args)
122
- args.each do |arg|
123
- json_attribute arg, as: :string
124
- end
125
- end
126
-
127
- # Allows you to define several float attributes at once. Deprecated.
128
- def float_attributes(*args)
129
- args.each do |arg|
130
- json_attribute arg, as: :float
131
- end
132
- end
133
-
134
- # Allows you to define several integer attributes at once. Deprecated.
135
- def integer_attributes(*args)
136
- args.each do |arg|
137
- json_attribute arg, as: :integer
138
- end
139
- end
140
-
141
- # Allows you to define several boolean attributes at once. Deprecated.
142
- def boolean_attributes(*args)
143
- args.each do |arg|
144
- json_attribute arg, as: :boolean
145
- end
146
- end
147
-
148
- # Shorthand for attribute :name, as: :enumeration, values: %w{a b c}
149
- # Deprecated.
150
- def enumerates(field, *args)
151
- options = args.extract_options!
152
- options[:as] = :enumeration
153
- json_attribute field, options
154
- end
155
-
156
-
157
34
  end
158
35
 
159
36
  end