ar_doc_store 1.0.5 → 2.0.0

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.
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