ar_doc_store 0.1.3 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -0
  3. data/README.md +2 -0
  4. data/Rakefile +15 -1
  5. data/ar_doc_store.gemspec +3 -2
  6. data/lib/ar_doc_store.rb +14 -11
  7. data/lib/ar_doc_store/attribute_types/{array.rb → array_attribute.rb} +1 -1
  8. data/lib/ar_doc_store/attribute_types/base_attribute.rb +52 -0
  9. data/lib/ar_doc_store/attribute_types/{boolean.rb → boolean_attribute.rb} +1 -1
  10. data/lib/ar_doc_store/attribute_types/{embeds_many.rb → embeds_many_attribute.rb} +4 -5
  11. data/lib/ar_doc_store/attribute_types/embeds_one_attribute.rb +74 -0
  12. data/lib/ar_doc_store/attribute_types/{enumeration.rb → enumeration_attribute.rb} +1 -1
  13. data/lib/ar_doc_store/attribute_types/{float.rb → float_attribute.rb} +1 -1
  14. data/lib/ar_doc_store/attribute_types/{integer.rb → integer_attribute.rb} +1 -1
  15. data/lib/ar_doc_store/attribute_types/{string.rb → string_attribute.rb} +1 -1
  16. data/lib/ar_doc_store/attribute_types/{uuid.rb → uuid_attribute.rb} +1 -1
  17. data/lib/ar_doc_store/embeddable_model.rb +48 -20
  18. data/lib/ar_doc_store/storage.rb +3 -61
  19. data/lib/ar_doc_store/version.rb +1 -1
  20. data/test/attribute_types/array_attribute_test.rb +25 -0
  21. data/test/attribute_types/boolean_attribute_test.rb +36 -0
  22. data/test/{model_attribute_access_test.rb → attribute_types/enumeration_attribute_test.rb} +8 -46
  23. data/test/attribute_types/float_attribute_test.rb +33 -0
  24. data/test/attribute_types/integer_attribute_test.rb +33 -0
  25. data/test/attribute_types/string_attribute_test.rb +27 -0
  26. data/test/originals/dirty_attributes_test.rb +35 -0
  27. data/test/{embedded_model_attribute_test.rb → originals/embedded_model_attribute_test.rb} +1 -1
  28. data/test/{embedding_test.rb → originals/embedding_test.rb} +1 -1
  29. data/test/test_helper.rb +4 -2
  30. metadata +37 -28
  31. data/lib/ar_doc_store/attribute_types/base.rb +0 -23
  32. data/lib/ar_doc_store/attribute_types/embeds_one.rb +0 -54
  33. data/lib/ar_doc_store/attribute_types/json.rb +0 -13
  34. data/test/dirty_attributes_test.rb +0 -24
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 26475c142ad4190f755c67790ee452549a04625c
4
- data.tar.gz: 50df0fd6be02fc56a5ade3af39ad8faf0946b212
3
+ metadata.gz: a3700fe9780b88ba20e28eb918e3b49c307750cc
4
+ data.tar.gz: 0ccdb51dd3e3b5b2c27176282b6f4d0fab67953d
5
5
  SHA512:
6
- metadata.gz: 57894eff1ed254c21e932e4b18df91f2d1cf2d0e7c5f7496b2bf0866909456f97c2679aa00657ddc5d85b9768de9944e7bfe256b82e470d133c9267635207ee6
7
- data.tar.gz: 9523be413ae72a83b3f58826509018acc3bd43f2269eba15b78b2b247e374021f32b698c3173cb07fd7965c0321c22e2e0fc044f43e8484d60fb649fc790bafc
6
+ metadata.gz: e1794e597e28722089e23f160a3caf8f3376427ce57dfd9a5e942ccb24b3820cf37d5c886cc87cfc867451a70d29ed2ee8dbdab43f7380025e3f0284d12c271a
7
+ data.tar.gz: 8f70729d52793af0a9dc970957ab986970d617792518f80b872c22b5026ead45afc7d641602e63fc6917d77d245f9f93e78c04446a4ac5f9bd72698813aa0fb7
data/Gemfile CHANGED
@@ -1,3 +1,4 @@
1
+ ruby '2.2.2'
1
2
  source 'https://rubygems.org'
2
3
 
3
4
  # Specify your gem's dependencies in ar_doc_store.gemspec
data/README.md CHANGED
@@ -9,6 +9,7 @@ The use case is primarily when you have a rapidly evolving schema with scores of
9
9
  Learn more about the JSON column in Postgres and using it as a document store:
10
10
  * The Rails Guide on Postgres: http://edgeguides.rubyonrails.org/active_record_postgresql.html
11
11
  * Document Store Gymnastics: http://rob.conery.io/2015/03/01/document-storage-gymnastics-in-postgres/
12
+ * Query JSON with Rails 4.2 and Postgres 9.4: http://robertbeene.com/rails-4-2-and-postgresql-9-4/
12
13
  * PG as NoSQL: http://thebuild.com%5Cpresentations%5Cpg-as-nosql-pgday-fosdem-2013.pdf
13
14
  * Why JSON in PostgreSQL is Awesome: https://functionwhatwhat.com/json-in-postgresql/
14
15
  * Indexing JSONB: http://michael.otacoo.com/postgresql-2/postgres-9-4-feature-highlight-indexing-jsonb/
@@ -156,6 +157,7 @@ end
156
157
  = form.input :height, as: :float
157
158
  = form.object.ensure_door
158
159
  = form.fields_for :door do |door_form|
160
+ = door_form.input :id # <-- because fields_for won't output the hidden id field for us - not a bad thing to put it where you want it instead of where they put it
159
161
  = door_form.input :door_type, as: :check_boxes, collection: Door.door_type_choices
160
162
  ```
161
163
 
data/Rakefile CHANGED
@@ -3,5 +3,19 @@ require "bundler/gem_tasks"
3
3
  require 'rake/testtask'
4
4
 
5
5
  Rake::TestTask.new do |t|
6
- t.pattern = "test/*_test.rb"
6
+ t.pattern = "test/**/*_test.rb"
7
+ end
8
+
9
+ namespace :test do
10
+ task :prepare_ar_doc_store do
11
+ require 'active_record'
12
+ ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: 'ar_doc_store_test', username: 'postgres', password: 'postgres')
13
+
14
+ ActiveRecord::Schema.define do
15
+ self.verbose = false
16
+ create_table :buildings, force: true do |t|
17
+ t.jsonb :data
18
+ end
19
+ end
20
+ end
7
21
  end
@@ -18,8 +18,9 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "activerecord", ">= 4.0"
22
- spec.add_dependency "hashie", ">=3.4.0"
21
+ spec.add_dependency "activerecord", "~> 4.0"
22
+ spec.add_dependency "pg", "~> 0.17"
23
+ # spec.add_dependency "hashie", ">=3.4.0"
23
24
  spec.add_development_dependency "bundler", "~> 1.7"
24
25
  spec.add_development_dependency "rake", "~> 10.0"
25
26
  end
@@ -3,18 +3,22 @@ require "ar_doc_store/storage"
3
3
  require "ar_doc_store/embedding"
4
4
  require "ar_doc_store/model"
5
5
  require "ar_doc_store/embeddable_model"
6
- require "ar_doc_store/attribute_types/base"
7
- require "ar_doc_store/attribute_types/array"
8
- require "ar_doc_store/attribute_types/boolean"
9
- require "ar_doc_store/attribute_types/enumeration"
10
- require "ar_doc_store/attribute_types/float"
11
- require "ar_doc_store/attribute_types/integer"
12
- require "ar_doc_store/attribute_types/string"
13
- require "ar_doc_store/attribute_types/uuid"
14
- require "ar_doc_store/attribute_types/embeds_one"
15
- require "ar_doc_store/attribute_types/embeds_many"
16
6
 
17
7
  module ArDocStore
8
+
9
+ module AttributeTypes
10
+ autoload :BaseAttribute, "ar_doc_store/attribute_types/base_attribute"
11
+ autoload :ArrayAttribute, "ar_doc_store/attribute_types/array_attribute"
12
+ autoload :BooleanAttribute, "ar_doc_store/attribute_types/boolean_attribute"
13
+ autoload :EnumerationAttribute, "ar_doc_store/attribute_types/enumeration_attribute"
14
+ autoload :FloatAttribute, "ar_doc_store/attribute_types/float_attribute"
15
+ autoload :IntegerAttribute, "ar_doc_store/attribute_types/integer_attribute"
16
+ autoload :StringAttribute, "ar_doc_store/attribute_types/string_attribute"
17
+ autoload :UuidAttribute, "ar_doc_store/attribute_types/uuid_attribute"
18
+ autoload :EmbedsOneAttribute, "ar_doc_store/attribute_types/embeds_one_attribute"
19
+ autoload :EmbedsManyAttribute, "ar_doc_store/attribute_types/embeds_many_attribute"
20
+ end
21
+
18
22
  @mappings = Hash.new
19
23
  @mappings[:array] = 'ArDocStore::AttributeTypes::ArrayAttribute'
20
24
  @mappings[:boolean] = 'ArDocStore::AttributeTypes::BooleanAttribute'
@@ -22,7 +26,6 @@ module ArDocStore
22
26
  @mappings[:float] = 'ArDocStore::AttributeTypes::FloatAttribute'
23
27
  @mappings[:integer] = 'ArDocStore::AttributeTypes::IntegerAttribute'
24
28
  @mappings[:string] = 'ArDocStore::AttributeTypes::StringAttribute'
25
- @mappings[:json] = 'ArDocStore::AttributeTypes::JsonAttribute'
26
29
  @mappings[:uuid] = 'ArDocStore::AttributeTypes::UuidAttribute'
27
30
 
28
31
  def self.mappings
@@ -1,7 +1,7 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
3
 
4
- class ArrayAttribute < Base
4
+ class ArrayAttribute < BaseAttribute
5
5
 
6
6
  def build
7
7
  key = attribute.to_sym
@@ -0,0 +1,52 @@
1
+ module ArDocStore
2
+ module AttributeTypes
3
+ class BaseAttribute
4
+ attr_accessor :conversion, :predicate, :options, :model, :attribute, :default
5
+
6
+ def self.build(model, attribute, options={})
7
+ new(model, attribute, options).build
8
+ end
9
+
10
+ def initialize(model, attribute, options)
11
+ @model, @attribute, @options = model, attribute, options
12
+ @model.virtual_attributes[attribute] = self
13
+ @default = options.delete(:default)
14
+ end
15
+
16
+ def build
17
+ store_attribute
18
+ end
19
+
20
+ #:nodoc:
21
+ def store_attribute
22
+ attribute = @attribute
23
+ typecast_method = conversion
24
+ predicate = @predicate
25
+ default_value = default
26
+ model.class_eval do
27
+ add_ransacker(attribute, predicate)
28
+ define_method attribute.to_sym, -> {
29
+ value = read_store_attribute(:data, attribute)
30
+ if value
31
+ value.public_send(typecast_method)
32
+ elsif default_value
33
+ write_default_store_attribute(attribute, default_value)
34
+ default_value
35
+ end
36
+ }
37
+ define_method "#{attribute}=".to_sym, -> (value) {
38
+ if value == '' || value.nil?
39
+ write_store_attribute :data, attribute, nil
40
+ else
41
+ write_store_attribute(:data, attribute, value.public_send(typecast_method))
42
+ end
43
+ }
44
+ end
45
+ end
46
+
47
+
48
+
49
+ end
50
+
51
+ end
52
+ end
@@ -1,7 +1,7 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
3
 
4
- class BooleanAttribute < Base
4
+ class BooleanAttribute < BaseAttribute
5
5
  def build
6
6
  key = attribute.to_sym
7
7
  model.class_eval do
@@ -6,7 +6,7 @@ module ArDocStore
6
6
 
7
7
  module AttributeTypes
8
8
 
9
- class EmbedsManyAttribute < Base
9
+ class EmbedsManyAttribute < BaseAttribute
10
10
 
11
11
  def build
12
12
  assn_name = attribute.to_sym
@@ -35,7 +35,7 @@ module ArDocStore
35
35
  my_class_name = class_name.constantize
36
36
  items = read_store_attribute(:data, assn_name)
37
37
  if items.is_a?(Array) || items.is_a?(ArDocStore::EmbeddedCollection)
38
- items = ArDocStore::EmbeddedCollection.new items.map { |item| my_class_name.new(item) }
38
+ items = ArDocStore::EmbeddedCollection.new items.map { |item| my_class_name.build(item) }
39
39
  else
40
40
  items ||= ArDocStore::EmbeddedCollection.new
41
41
  end
@@ -67,8 +67,7 @@ module ArDocStore
67
67
  def create_build_method_for(assn_name, class_name)
68
68
  add_method "build_#{assn_name.to_s.singularize}", -> (attributes=nil) {
69
69
  assns = self.public_send assn_name
70
- item = class_name.constantize.new attributes
71
- item.id
70
+ item = class_name.constantize.build attributes
72
71
  item.parent = self
73
72
  assns << item
74
73
  public_send "#{assn_name}=", assns
@@ -148,7 +147,7 @@ module ArDocStore
148
147
  end
149
148
 
150
149
  def update_attributes(model, value)
151
- model.apply_attributes(value)
150
+ model.attributes = value
152
151
  end
153
152
 
154
153
  def wants_to_die?(value)
@@ -0,0 +1,74 @@
1
+ module ArDocStore
2
+ module AttributeTypes
3
+
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
15
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
16
+ def #{attribute}
17
+ @#{attribute} || begin
18
+ item = read_store_attribute :data, :#{attribute}
19
+ item = #{class_name}.build(item) unless item.is_a?(#{class_name})
20
+ @#{attribute} = item
21
+ end
22
+ end
23
+
24
+ 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 :data, :#{attribute}, value
33
+ end
34
+ CODE
35
+ end
36
+
37
+ def create_embeds_one_accessors
38
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
39
+ def build_#{attribute}(attributes=nil)
40
+ self.#{attribute} = #{class_name}.build(attributes)
41
+ end
42
+ def ensure_#{attribute}
43
+ #{attribute} || build_#{attribute}
44
+ end
45
+ CODE
46
+ end
47
+
48
+ def create_embed_one_attributes_method
49
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
50
+ def #{attribute}_attributes=(values={})
51
+ values.symbolize_keys! if values.respond_to?(:symbolize_keys!)
52
+ if values[:_destroy] && (values[:_destroy] == '1')
53
+ self.#{attribute} = nil
54
+ else
55
+ item = ensure_#{attribute}
56
+ item.attributes = values
57
+ end
58
+ end
59
+ CODE
60
+ end
61
+
62
+ def create_embeds_one_validation
63
+ model.class_eval <<-CODE, __FILE__, __LINE__ + 1
64
+ def validate_embedded_record_for_#{attribute}
65
+ validate_embeds_one :#{attribute}
66
+ end
67
+ validate :validate_embedded_record_for_#{attribute}
68
+ CODE
69
+ end
70
+
71
+ end
72
+
73
+ end
74
+ end
@@ -1,7 +1,7 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
3
 
4
- class EnumerationAttribute < Base
4
+ class EnumerationAttribute < BaseAttribute
5
5
 
6
6
  def build
7
7
  key = attribute.to_sym
@@ -1,7 +1,7 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
3
 
4
- class FloatAttribute < Base
4
+ class FloatAttribute < BaseAttribute
5
5
  def conversion
6
6
  :to_f
7
7
  end
@@ -1,7 +1,7 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
3
 
4
- class IntegerAttribute < Base
4
+ class IntegerAttribute < BaseAttribute
5
5
  def conversion
6
6
  :to_i
7
7
  end
@@ -1,7 +1,7 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
3
 
4
- class StringAttribute < Base
4
+ class StringAttribute < BaseAttribute
5
5
  def conversion
6
6
  :to_s
7
7
  end
@@ -3,7 +3,7 @@ require 'securerandom'
3
3
  module ArDocStore
4
4
  module AttributeTypes
5
5
 
6
- class UuidAttribute < Base
6
+ class UuidAttribute < BaseAttribute
7
7
  def build
8
8
  key = attribute.to_sym
9
9
  model.class_eval do
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  module ArDocStore
2
4
  module EmbeddableModel
3
5
  def self.included(mod)
@@ -14,7 +16,8 @@ module ArDocStore
14
16
 
15
17
  mod.class_eval do
16
18
  attr_accessor :_destroy
17
- attr_accessor :attributes, :parent
19
+ attr_accessor :parent
20
+ attr_reader :attributes
18
21
 
19
22
  class_attribute :virtual_attributes
20
23
  self.virtual_attributes ||= HashWithIndifferentAccess.new
@@ -22,6 +25,7 @@ module ArDocStore
22
25
  delegate :as_json, to: :attributes
23
26
 
24
27
  attribute :id, :uuid
28
+
25
29
  end
26
30
 
27
31
  end
@@ -29,13 +33,23 @@ module ArDocStore
29
33
  module InstanceMethods
30
34
 
31
35
  def initialize(attrs=HashWithIndifferentAccess.new)
32
- @attributes = HashWithIndifferentAccess.new
33
- self.parent = attrs.delete(:parent) if attrs
34
- apply_attributes attrs
35
36
  @_initialized = true
37
+ initialize_attributes attrs
38
+ end
39
+
40
+ def instantiate(attrs=HashWithIndifferentAccess.new)
41
+ initialize_attributes attrs
42
+ @_initialized = true
43
+ self
36
44
  end
37
45
 
38
- def apply_attributes(attrs=HashWithIndifferentAccess.new)
46
+ def initialize_attributes(attrs)
47
+ @attributes ||= HashWithIndifferentAccess.new
48
+ self.parent = attributes.delete(:parent) if attributes
49
+ self.attributes = attrs
50
+ end
51
+
52
+ def attributes=(attrs=HashWithIndifferentAccess.new)
39
53
  virtual_attributes.keys.each do |attr|
40
54
  @attributes[attr] ||= nil
41
55
  end
@@ -48,12 +62,6 @@ module ArDocStore
48
62
  self
49
63
  end
50
64
 
51
- # TODO: This doesn't work very well for embeds_many because the parent needs to have its setter triggered
52
- # before the embedded model will actually get saved.
53
- def save
54
- parent && parent.save
55
- end
56
-
57
65
  def persisted?
58
66
  false
59
67
  end
@@ -62,23 +70,33 @@ module ArDocStore
62
70
  "#{self.class}: #{attributes.inspect}"
63
71
  end
64
72
 
65
- def read_store_attribute(store, key)
66
- @attributes[key]
73
+ def read_store_attribute(store, attr)
74
+ attributes[attr]
67
75
  end
68
76
 
69
- def write_store_attribute(store, key, value)
70
- changed_attributes[key] = read_store_attribute(:data, key) if @_initialized
71
- @attributes[key] = value
77
+ def write_store_attribute(store, attribute, value)
78
+ if @_initialized
79
+ old_value = attributes[attribute]
80
+ if attribute.to_s != 'id' && value != old_value
81
+ public_send :"#{attribute}_will_change!"
82
+ parent.data_will_change! if parent
83
+ end
84
+
85
+ end
86
+ attributes[attribute] = value
72
87
  end
73
88
 
74
- def write_default_store_attribute(key, value)
75
- @attributes[key] = value
89
+ def write_default_store_attribute(attr, value)
90
+ attributes[attr] = value
76
91
  end
77
92
 
78
93
  def to_param
79
94
  id
80
95
  end
81
-
96
+
97
+ def id_will_change!
98
+ end
99
+
82
100
  end
83
101
 
84
102
  module ClassMethods
@@ -91,7 +109,17 @@ module ArDocStore
91
109
  define_method key, -> { read_store_attribute(:data, key) }
92
110
  define_method "#{key}=".to_sym, -> (value) { write_store_attribute :data, key, value }
93
111
  end
94
-
112
+
113
+ def build(attrs=HashWithIndifferentAccess.new)
114
+ if attrs.is_a?(self.class)
115
+ attrs
116
+ else
117
+ instance = allocate
118
+ instance.instantiate attrs
119
+ end
120
+ end
121
+
122
+
95
123
  end
96
124
 
97
125
  end
@@ -66,10 +66,10 @@ module ArDocStore
66
66
  options = args.extract_options!
67
67
  type ||= options.delete(:as) || :string
68
68
  class_name = ArDocStore.mappings[type] || "ArDocStore::AttributeTypes::#{type.to_s.classify}Attribute"
69
- raise "Invalid attribute type: #{name}" unless const_defined?(class_name)
70
- class_name = class_name.constantize
71
- class_name.build self, name, options
69
+ raise "Invalid attribute type: #{class_name}" unless const_defined?(class_name)
70
+ class_name.constantize.build self, name, options
72
71
  define_virtual_attribute_method name
72
+ define_method "#{name}?", -> { public_send(name).present? }
73
73
  end
74
74
 
75
75
  #:nodoc:
@@ -84,64 +84,6 @@ module ArDocStore
84
84
  end
85
85
  end
86
86
 
87
- #:nodoc:
88
- def store_attribute(attribute, typecast_method, predicate=nil, default_value=nil)
89
- store_accessor :data, attribute
90
- add_ransacker(attribute, predicate)
91
- if typecast_method.is_a?(Symbol)
92
- store_attribute_from_symbol typecast_method, attribute, default_value
93
- else
94
- store_attribute_from_class typecast_method, attribute
95
- end
96
- end
97
-
98
- #:nodoc:
99
- def store_attribute_from_symbol(typecast_method, key, default_value)
100
- define_method key.to_sym, -> {
101
- value = read_store_attribute(:data, key)
102
- if value
103
- value.public_send(typecast_method)
104
- elsif default_value
105
- write_default_store_attribute(key, default_value)
106
- default_value
107
- end
108
- }
109
- define_method "#{key}=".to_sym, -> (value) {
110
- if value == '' || value.nil?
111
- write_store_attribute :data, key, nil
112
- else
113
- write_store_attribute(:data, key, value.public_send(typecast_method))
114
- end
115
- }
116
- end
117
-
118
- # TODO: add default value support.
119
- # Default ought to be a hash of attributes,
120
- # also accept a proc that receives the newly initialized object
121
- def store_attribute_from_class(class_name, key)
122
- define_method key.to_sym, -> {
123
- ivar = "@#{key}"
124
- instance_variable_get(ivar) || begin
125
- item = read_store_attribute(:data, key)
126
- class_name = class_name.constantize if class_name.respond_to?(:constantize)
127
- item = class_name.new(item) unless item.is_a?(class_name)
128
- instance_variable_set ivar, item
129
- end
130
- }
131
- define_method "#{key}=".to_sym, -> (value) {
132
- ivar = "@#{key}"
133
- existing = public_send(key)
134
- class_name = class_name.constantize if class_name.respond_to?(:constantize)
135
- if value == '' || !value
136
- value = nil
137
- elsif !value.is_a?(class_name)
138
- value = existing.apply_attributes(value)
139
- end
140
- instance_variable_set ivar, value
141
- write_store_attribute :data, key, value
142
- }
143
- end
144
-
145
87
  # Pretty much the same as define_attribute_method but skipping the matches that create read and write methods
146
88
  def define_virtual_attribute_method(attr_name)
147
89
  attr_name = attr_name.to_s
@@ -1,3 +1,3 @@
1
1
  module ArDocStore
2
- VERSION = "0.1.3"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -0,0 +1,25 @@
1
+ require_relative './../test_helper'
2
+
3
+ class ArrayAttributeTest < MiniTest::Test
4
+
5
+ def test_attribute_on_model_init
6
+ architects = %W{Bob John Billy Bob}
7
+ b = Building.new architects: architects
8
+ assert_equal architects, b.architects
9
+ end
10
+
11
+ def test_attribute_on_existing_model
12
+ architects = %W{Bob John Billy Bob}
13
+ b = Building.new
14
+ b.architects = architects
15
+ assert_equal architects, b.architects
16
+ assert b.architects_changed?
17
+ end
18
+
19
+ def test_question_mark_method
20
+ b = Building.new architects: %W{Bob John}
21
+ assert_equal true, b.architects?
22
+ end
23
+
24
+ # Type conversion doesn't make sense here...
25
+ end
@@ -0,0 +1,36 @@
1
+ require_relative './../test_helper'
2
+
3
+ class BooleanAttributeTest < MiniTest::Test
4
+
5
+ def test_attribute_on_model_init
6
+ b = Building.new finished: true
7
+ assert_equal true, b.finished
8
+ end
9
+
10
+ def test_attribute_on_existing_model
11
+ b = Building.new
12
+ b.finished = true
13
+ assert_equal true, b.finished
14
+ assert b.finished_changed?
15
+ end
16
+
17
+ def test_question_mark_method
18
+ b = Building.new finished: true
19
+ assert_equal true, b.finished?
20
+ end
21
+
22
+ # The setter function doesn't appear to get called in this context.
23
+ # But more likely traces to ARDuck.
24
+ # TODO: Does this still fail after replacing ARDuck with AR::Base?
25
+ def test_type_conversion_on_init
26
+ b = Building.new finished: '1'
27
+ assert_equal true, b.finished
28
+ end
29
+
30
+ def test_type_conversion_on_existing
31
+ b = Building.new
32
+ b.finished = '1'
33
+ assert_equal true, b.finished
34
+ end
35
+
36
+ end
@@ -1,49 +1,6 @@
1
- require_relative './test_helper'
1
+ require_relative './../test_helper'
2
2
 
3
- class ModelAttributeAccessTest < MiniTest::Test
4
- def test_string_attribute_on_model_init
5
- b = Building.new name: 'test'
6
- assert_equal 'test', b.name
7
- end
8
-
9
- def test_string_attribute_on_existing_model
10
- b = Building.new
11
- b.name = 'test'
12
- assert_equal 'test', b.name
13
- end
14
-
15
- def test_boolean_attribute_on_model_init
16
- b = Building.new finished: true
17
- assert b.finished?
18
- end
19
-
20
- def test_boolean_attribute_on_existing_model
21
- b = Building.new
22
- b.finished = true
23
- assert b.finished?
24
- end
25
-
26
- def test_float_attribute_on_init
27
- b = Building.new height: 54.45
28
- assert_equal 54.45, b.height
29
- end
30
-
31
- def test_float_attribute_on_existing_model
32
- b = Building.new
33
- b.height = 54.45
34
- assert_equal 54.45, b.height
35
- end
36
-
37
- def test_int_attribute_on_init
38
- b = Building.new stories: 5
39
- assert_equal 5, b.stories
40
- end
41
-
42
- def test_int_attribute_on_set
43
- b = Building.new
44
- b.stories = 5
45
- assert_equal 5, b.stories
46
- end
3
+ class EnumerationAttributeTest < MiniTest::Test
47
4
 
48
5
  def test_simple_enumeration_attribute
49
6
  b = Building.new construction: 'wood'
@@ -96,5 +53,10 @@ class ModelAttributeAccessTest < MiniTest::Test
96
53
  def test_enumeration_has_choices_to_use_for_select
97
54
  assert Building.construction_choices.present?
98
55
  end
99
-
56
+
57
+ def test_question_mark_method
58
+ b = Building.new strict_multi_enumeration: %w{glad bad}
59
+ assert_equal true, b.strict_multi_enumeration?
60
+ end
61
+
100
62
  end
@@ -0,0 +1,33 @@
1
+ require_relative './../test_helper'
2
+
3
+ class FloatAttributeTest < MiniTest::Test
4
+
5
+ def test_attribute_on_model_init
6
+ b = Building.new height: 5.42
7
+ assert_equal 5.42, b.height
8
+ end
9
+
10
+ def test_attribute_on_existing_model
11
+ b = Building.new
12
+ b.height = 5.42
13
+ assert_equal 5.42, b.height
14
+ assert b.height_changed?
15
+ end
16
+
17
+ def test_question_mark_method
18
+ b = Building.new height: 5.42
19
+ assert_equal true, b.height?
20
+ end
21
+
22
+ def test_type_conversion_on_init
23
+ b = Building.new height: '5.42'
24
+ assert_equal 5.42, b.height
25
+ end
26
+
27
+ def test_type_conversion_on_existing
28
+ b = Building.new
29
+ b.height = '5.42'
30
+ assert_equal 5.42, b.height
31
+ end
32
+
33
+ end
@@ -0,0 +1,33 @@
1
+ require_relative './../test_helper'
2
+
3
+ class IntegerAttributeTest < MiniTest::Test
4
+
5
+ def test_string_attribute_on_model_init
6
+ b = Building.new stories: 5
7
+ assert_equal 5, b.stories
8
+ end
9
+
10
+ def test_string_attribute_on_existing_model
11
+ b = Building.new
12
+ b.stories = 5
13
+ assert_equal 5, b.stories
14
+ assert b.stories_changed?
15
+ end
16
+
17
+ def test_question_mark_method
18
+ b = Building.new stories: 5
19
+ assert_equal true, b.stories?
20
+ end
21
+
22
+ def test_type_conversion_on_init
23
+ b = Building.new stories: '5'
24
+ assert_equal 5, b.stories
25
+ end
26
+
27
+ def test_type_conversion_on_existing
28
+ b = Building.new
29
+ b.stories = '5'
30
+ assert_equal 5, b.stories
31
+ end
32
+
33
+ end
@@ -0,0 +1,27 @@
1
+ require_relative './../test_helper'
2
+
3
+ class StringAttributeTest < MiniTest::Test
4
+
5
+ def test_attribute_on_model_init
6
+ b = Building.new name: 'test'
7
+ assert_equal 'test', b.name
8
+ end
9
+
10
+ def test_attribute_on_existing_model
11
+ b = Building.new
12
+ b.name = 'test'
13
+ assert_equal 'test', b.name
14
+ assert b.name_changed?
15
+ end
16
+
17
+ def test_question_mark_method
18
+ b = Building.new name: 'test'
19
+ assert_equal true, b.name?
20
+ end
21
+
22
+ def test_conversion
23
+ b = Building.new name: 51
24
+ assert_equal '51', b.name
25
+ end
26
+
27
+ end
@@ -0,0 +1,35 @@
1
+ require_relative './../test_helper'
2
+
3
+ class DirtyAttributeTest < MiniTest::Test
4
+
5
+ def test_on_model
6
+ b = Building.new name: 'Foo!'
7
+ # This used to work but started failing. AR behavior is to make it true.
8
+ # send :clear_changes_information not working yields undefined method.
9
+ # assert !b.name_changed?
10
+ b.name = 'Bar.'
11
+ assert_equal 'Bar.', b.name
12
+ assert b.name_changed?
13
+ # Somehow this worked at one point, but should only work when the record is loaded via instantiate:
14
+ # assert_equal 'Foo!', b.name_was
15
+ end
16
+
17
+ #This test fails here but passes elsewhere.
18
+ def test_on_embedded_model
19
+ b = Building.new
20
+ r = b.build_restroom restroom_type: 'dirty'
21
+ assert !r.restroom_type_changed?
22
+ r.restroom_type = 'nasty'
23
+ assert r.restroom_type_changed?
24
+ assert_equal 'dirty', r.restroom_type_was
25
+ assert_equal 'nasty', r.restroom_type
26
+ end
27
+
28
+ def test_id_does_not_change_on_init
29
+ b = Building.new
30
+ r = b.build_restroom
31
+ assert !r.id_changed?
32
+ assert !r.changes.keys.include?('id')
33
+ end
34
+
35
+ end
@@ -1,4 +1,4 @@
1
- require_relative './test_helper'
1
+ require_relative './../test_helper'
2
2
 
3
3
  class EmbeddedModelAttributeTest < MiniTest::Test
4
4
 
@@ -1,4 +1,4 @@
1
- require_relative './test_helper'
1
+ require_relative './../test_helper'
2
2
 
3
3
  class EmbeddingTest < MiniTest::Test
4
4
 
@@ -5,6 +5,7 @@ require 'minitest/autorun'
5
5
  require 'active_record'
6
6
 
7
7
  require_relative './../lib/ar_doc_store'
8
+ ActiveRecord::Base.establish_connection(adapter: 'postgresql', database: 'ar_doc_store_test', username: 'postgres', password: 'postgres')
8
9
 
9
10
  # A building has many entrances and restrooms and some fields of its own
10
11
  # An entrance has a door, a route, and some fields of its own
@@ -26,7 +27,7 @@ class ARDuck
26
27
  @attributes = HashWithIndifferentAccess.new
27
28
  unless attrs.nil?
28
29
  attrs.each { |key, value|
29
- @attributes[key] = value
30
+ @attributes[key] = public_send("#{key}=", value)
30
31
  }
31
32
  end
32
33
  @_initialized = true
@@ -125,13 +126,14 @@ class Restroom
125
126
 
126
127
  end
127
128
 
128
- class Building < ARDuck
129
+ class Building < ActiveRecord::Base
129
130
  include ArDocStore::Model
130
131
  attribute :name, :string
131
132
  attribute :comments, as: :string
132
133
  attribute :finished, :boolean
133
134
  attribute :stories, as: :integer
134
135
  attribute :height, as: :float
136
+ attribute :architects, as: :array
135
137
  attribute :construction, as: :enumeration, values: %w{concrete wood brick plaster steel}
136
138
  attribute :multiconstruction, as: :enumeration, values: %w{concrete wood brick plaster steel}, multiple: true
137
139
  attribute :strict_enumeration, as: :enumeration, values: %w{happy sad glad bad}, strict: true
metadata CHANGED
@@ -1,43 +1,43 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar_doc_store
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.3
4
+ version: 0.2.0
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-04-16 00:00:00.000000000 Z
11
+ date: 2015-04-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - ">="
17
+ - - "~>"
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - ">="
24
+ - - "~>"
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
28
- name: hashie
28
+ name: pg
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - ">="
31
+ - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: 3.4.0
33
+ version: '0.17'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - ">="
38
+ - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: 3.4.0
40
+ version: '0.17'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bundler
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -83,26 +83,30 @@ files:
83
83
  - Rakefile
84
84
  - ar_doc_store.gemspec
85
85
  - lib/ar_doc_store.rb
86
- - lib/ar_doc_store/attribute_types/array.rb
87
- - lib/ar_doc_store/attribute_types/base.rb
88
- - lib/ar_doc_store/attribute_types/boolean.rb
89
- - lib/ar_doc_store/attribute_types/embeds_many.rb
90
- - lib/ar_doc_store/attribute_types/embeds_one.rb
91
- - lib/ar_doc_store/attribute_types/enumeration.rb
92
- - lib/ar_doc_store/attribute_types/float.rb
93
- - lib/ar_doc_store/attribute_types/integer.rb
94
- - lib/ar_doc_store/attribute_types/json.rb
95
- - lib/ar_doc_store/attribute_types/string.rb
96
- - lib/ar_doc_store/attribute_types/uuid.rb
86
+ - lib/ar_doc_store/attribute_types/array_attribute.rb
87
+ - lib/ar_doc_store/attribute_types/base_attribute.rb
88
+ - lib/ar_doc_store/attribute_types/boolean_attribute.rb
89
+ - lib/ar_doc_store/attribute_types/embeds_many_attribute.rb
90
+ - lib/ar_doc_store/attribute_types/embeds_one_attribute.rb
91
+ - lib/ar_doc_store/attribute_types/enumeration_attribute.rb
92
+ - lib/ar_doc_store/attribute_types/float_attribute.rb
93
+ - lib/ar_doc_store/attribute_types/integer_attribute.rb
94
+ - lib/ar_doc_store/attribute_types/string_attribute.rb
95
+ - lib/ar_doc_store/attribute_types/uuid_attribute.rb
97
96
  - lib/ar_doc_store/embeddable_model.rb
98
97
  - lib/ar_doc_store/embedding.rb
99
98
  - lib/ar_doc_store/model.rb
100
99
  - lib/ar_doc_store/storage.rb
101
100
  - lib/ar_doc_store/version.rb
102
- - test/dirty_attributes_test.rb
103
- - test/embedded_model_attribute_test.rb
104
- - test/embedding_test.rb
105
- - test/model_attribute_access_test.rb
101
+ - test/attribute_types/array_attribute_test.rb
102
+ - test/attribute_types/boolean_attribute_test.rb
103
+ - test/attribute_types/enumeration_attribute_test.rb
104
+ - test/attribute_types/float_attribute_test.rb
105
+ - test/attribute_types/integer_attribute_test.rb
106
+ - test/attribute_types/string_attribute_test.rb
107
+ - test/originals/dirty_attributes_test.rb
108
+ - test/originals/embedded_model_attribute_test.rb
109
+ - test/originals/embedding_test.rb
106
110
  - test/test_helper.rb
107
111
  homepage: https://github.com/dfurber/ar_doc_store
108
112
  licenses:
@@ -129,8 +133,13 @@ signing_key:
129
133
  specification_version: 4
130
134
  summary: A document storage gem meant for ActiveRecord PostgresQL JSON storage.
131
135
  test_files:
132
- - test/dirty_attributes_test.rb
133
- - test/embedded_model_attribute_test.rb
134
- - test/embedding_test.rb
135
- - test/model_attribute_access_test.rb
136
+ - test/attribute_types/array_attribute_test.rb
137
+ - test/attribute_types/boolean_attribute_test.rb
138
+ - test/attribute_types/enumeration_attribute_test.rb
139
+ - test/attribute_types/float_attribute_test.rb
140
+ - test/attribute_types/integer_attribute_test.rb
141
+ - test/attribute_types/string_attribute_test.rb
142
+ - test/originals/dirty_attributes_test.rb
143
+ - test/originals/embedded_model_attribute_test.rb
144
+ - test/originals/embedding_test.rb
136
145
  - test/test_helper.rb
@@ -1,23 +0,0 @@
1
- module ArDocStore
2
- module AttributeTypes
3
- class Base
4
- attr_accessor :conversion, :predicate, :options, :model, :attribute, :default
5
-
6
- def self.build(model, attribute, options={})
7
- new(model, attribute, options).build
8
- end
9
-
10
- def initialize(model, attribute, options)
11
- @model, @attribute, @options = model, attribute, options
12
- @model.virtual_attributes[attribute] = self
13
- @default = options.delete(:default)
14
- end
15
-
16
- def build
17
- model.store_attribute attribute, conversion, predicate, default
18
- end
19
-
20
- end
21
-
22
- end
23
- end
@@ -1,54 +0,0 @@
1
- module ArDocStore
2
- module AttributeTypes
3
-
4
- class EmbedsOneAttribute < Base
5
- def build
6
- assn_name = attribute.to_sym
7
- class_name = options[:class_name] || attribute.to_s.classify
8
- model.store_accessor :data, assn_name
9
- model.store_attribute_from_class class_name, assn_name
10
- create_embed_one_attributes_method(assn_name)
11
- create_embeds_one_accessors assn_name, class_name
12
- create_embeds_one_validation(assn_name)
13
- end
14
-
15
- def create_embeds_one_accessors(assn_name, class_name)
16
- model.class_eval do
17
- define_method "build_#{assn_name}", -> (attributes=nil) {
18
- class_name = class_name.constantize if class_name.respond_to?(:constantize)
19
- public_send "#{assn_name}=", class_name.new(attributes)
20
- public_send assn_name
21
- }
22
- define_method "ensure_#{assn_name}", -> {
23
- public_send "build_#{assn_name}" if public_send(assn_name).blank?
24
- }
25
- end
26
- end
27
-
28
- def create_embed_one_attributes_method(assn_name)
29
- model.class_eval do
30
- define_method "#{assn_name}_attributes=", -> (values) {
31
- values ||= {}
32
- values.symbolize_keys! if values.respond_to?(:symbolize_keys!)
33
- if values[:_destroy] && (values[:_destroy] == '1')
34
- self.public_send "#{assn_name}=", nil
35
- else
36
- public_send "#{assn_name}=", values
37
- end
38
- }
39
- end
40
- end
41
-
42
- def create_embeds_one_validation(assn_name)
43
- model.class_eval do
44
- validate_method = "validate_embedded_record_for_#{assn_name}"
45
- define_method validate_method, -> { validate_embeds_one assn_name }
46
- validate validate_method
47
- end
48
- end
49
-
50
-
51
- end
52
-
53
- end
54
- end
@@ -1,13 +0,0 @@
1
- class JsonAttribute < ArDocStore::AttributeTypes::Base
2
- def conversion
3
- Hashie::Mash
4
- end
5
-
6
- def predicate
7
- 'jsonb'
8
- end
9
-
10
- def type
11
- :object
12
- end
13
- end
@@ -1,24 +0,0 @@
1
- require_relative './test_helper'
2
-
3
- class DirtylAttributeTest < MiniTest::Test
4
-
5
- def test_dirty_attributes_on_model
6
- b = Building.new name: 'Foo!'
7
- assert_equal b.name_changed?, false
8
- b.name = 'Bar.'
9
- assert b.name_changed?
10
- assert_equal 'Foo!', b.name_was
11
- assert_equal 'Bar.', b.name
12
- end
13
-
14
- def test_dirty_attributes_on_embedded_model
15
- b = Building.new
16
- r = b.build_restroom is_signage_clear: true
17
- assert_equal r.is_signage_clear_changed?, false
18
- r.is_signage_clear = false
19
- assert r.is_signage_clear_changed?
20
- assert_equal true, r.is_signage_clear_was
21
- assert_equal false, r.is_signage_clear
22
- end
23
-
24
- end