ar_doc_store 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a3700fe9780b88ba20e28eb918e3b49c307750cc
4
- data.tar.gz: 0ccdb51dd3e3b5b2c27176282b6f4d0fab67953d
3
+ metadata.gz: 389dfcb42c57c087c024326589ff09ca183bd224
4
+ data.tar.gz: ad6f5ac50966c3e2d8979cda69383c28dcaa0908
5
5
  SHA512:
6
- metadata.gz: e1794e597e28722089e23f160a3caf8f3376427ce57dfd9a5e942ccb24b3820cf37d5c886cc87cfc867451a70d29ed2ee8dbdab43f7380025e3f0284d12c271a
7
- data.tar.gz: 8f70729d52793af0a9dc970957ab986970d617792518f80b872c22b5026ead45afc7d641602e63fc6917d77d245f9f93e78c04446a4ac5f9bd72698813aa0fb7
6
+ metadata.gz: 4416430c4d7d03f97294e0b9fb31b3d03af2bb220d097975ff7ecd4104c27dfe8909125039c09c278dbefc633ebf5a303e95cf6f84e2a95fda10e1907a5d5dcd
7
+ data.tar.gz: bc49e9c3ed777a4f7fe140f6811ede9ba42123968545c602d121c4a86ad05e307791b21a2bc537eeee25958756d806592bc39d33bd855929cec62d494a4fab44
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # ArDocStore
2
2
 
3
- ArDocStore (pronounced R-Dock-Store) is a gem helps you implement a document store in ActiveRecord models that have access to the PostgreSQL JSON data type. You add a json column to your table called "data", include the ArDocStore::Model module, and then add schema-less attributes that get stored in the data column. With Ransack, these attributes are searchable as if they were real columns.
3
+ ArDocStore (pronounced R-Dock-Store) is a gem helps you implement a document store in ActiveRecord models that have access to the PostgreSQL JSON data type. You add a json column to your table called "data", include the ArDocStore::Model module, and then add schema-less attributes that get stored in the data column. With Ransack, these attributes are searchable as if they were real columns.
4
4
 
5
5
  There is also support for embedding models within models. These embedded models can be accessed from Rails form builders using fields_for.
6
6
 
@@ -8,7 +8,7 @@ The use case is primarily when you have a rapidly evolving schema with scores of
8
8
 
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
- * Document Store Gymnastics: http://rob.conery.io/2015/03/01/document-storage-gymnastics-in-postgres/
11
+ * Document Store Gymnastics: http://rob.conery.io/2015/03/01/document-storage-gymnastics-in-postgres/
12
12
  * Query JSON with Rails 4.2 and Postgres 9.4: http://robertbeene.com/rails-4-2-and-postgresql-9-4/
13
13
  * PG as NoSQL: http://thebuild.com%5Cpresentations%5Cpg-as-nosql-pgday-fosdem-2013.pdf
14
14
  * Why JSON in PostgreSQL is Awesome: https://functionwhatwhat.com/json-in-postgresql/
@@ -49,21 +49,30 @@ class Building < ActiveRecord::Base
49
49
  end
50
50
  ```
51
51
 
52
+ You don't have to use "data" as the column name. Use something else like do:
53
+ ```ruby
54
+ class Building < ActiveRecord::Base
55
+ include ArDocStore::Model
56
+ self.json_column = :json_data
57
+ end
58
+ ```
59
+
60
+
52
61
  ### Attributes
53
62
 
54
63
  Now there are several ways to play but all boil down to one method on the model:
55
64
  ```ruby
56
65
  class Building < ActiveRecord::Base
57
66
  include ArDocStore::Model
58
-
67
+
59
68
  attribute :name, :string
60
69
  attribute :width, :float
61
70
  attribute :height, as: :float # the :as is optional but if you like the consistency with SimpleForm
62
71
  attribute :storeys, :integer
63
72
  attribute :finished, :boolean
64
- attribute :construction_type, :enumeration,
65
- values: %w{wood plaster mud brick},
66
- multiple: true,
73
+ attribute :construction_type, :enumeration,
74
+ values: %w{wood plaster mud brick},
75
+ multiple: true,
67
76
  strict: true
68
77
  end
69
78
  ```
@@ -81,10 +90,10 @@ You noticed the enumeration type on the construction_type takes an array. That's
81
90
 
82
91
  ```ruby
83
92
  class Building ...
84
- attribute :construction_type,
85
- :enumeration,
86
- values: %w{wood plaster mud brick},
87
- multiple: true,
93
+ attribute :construction_type,
94
+ :enumeration,
95
+ values: %w{wood plaster mud brick},
96
+ multiple: true,
88
97
  strict: true
89
98
  end
90
99
  building = Building.new
@@ -101,17 +110,17 @@ Let's say that a building has a door. The building is not the only thing in our
101
110
  ```ruby
102
111
  class Door
103
112
  include ArDocStore::EmbeddableModel
104
-
105
- enumerates :door_type,
106
- multiple: true,
113
+
114
+ enumerates :door_type,
115
+ multiple: true,
107
116
  values: %w{single double french sliding push pull}
108
117
  attribute :open_handle,
109
- as: :enumeration,
110
- multiple: true,
118
+ as: :enumeration,
119
+ multiple: true,
111
120
  values: %w{push pull plate knob handle}
112
- attribute :close_handle,
113
- as: :enumeration,
114
- multiple: true,
121
+ attribute :close_handle,
122
+ as: :enumeration,
123
+ multiple: true,
115
124
  values: %w{push pull plate knob handle}
116
125
  attribute :clear_distance, as: :integer
117
126
  attribute :opening_force, as: :integer
@@ -1,20 +1,20 @@
1
1
  module ArDocStore
2
2
  module AttributeTypes
3
-
3
+
4
4
  class ArrayAttribute < BaseAttribute
5
5
 
6
6
  def build
7
7
  key = attribute.to_sym
8
8
  default_value = default
9
9
  model.class_eval do
10
- store_accessor :data, key
10
+ store_accessor json_column, key
11
11
  define_method key, -> {
12
- value = read_store_attribute(:data, key)
12
+ value = read_store_attribute(json_column, key)
13
13
  value or default_value
14
14
  }
15
15
  define_method "#{key}=".to_sym, -> (value) {
16
16
  value = nil if value == ['']
17
- write_store_attribute(:data, key, value)
17
+ write_store_attribute(json_column, key, value)
18
18
  }
19
19
  add_ransacker(key, 'text')
20
20
  end
@@ -28,4 +28,3 @@ module ArDocStore
28
28
 
29
29
  end
30
30
  end
31
-
@@ -26,7 +26,7 @@ module ArDocStore
26
26
  model.class_eval do
27
27
  add_ransacker(attribute, predicate)
28
28
  define_method attribute.to_sym, -> {
29
- value = read_store_attribute(:data, attribute)
29
+ value = read_store_attribute(json_column, attribute)
30
30
  if value
31
31
  value.public_send(typecast_method)
32
32
  elsif default_value
@@ -36,9 +36,9 @@ module ArDocStore
36
36
  }
37
37
  define_method "#{attribute}=".to_sym, -> (value) {
38
38
  if value == '' || value.nil?
39
- write_store_attribute :data, attribute, nil
39
+ write_store_attribute json_column, attribute, nil
40
40
  else
41
- write_store_attribute(:data, attribute, value.public_send(typecast_method))
41
+ write_store_attribute(json_column, attribute, value.public_send(typecast_method))
42
42
  end
43
43
  }
44
44
  end
@@ -5,11 +5,11 @@ module ArDocStore
5
5
  def build
6
6
  key = attribute.to_sym
7
7
  model.class_eval do
8
- store_accessor :data, key
8
+ store_accessor json_column, key
9
9
  define_method "#{key}?".to_sym, -> { public_send(key) == true }
10
10
  define_method "#{key}=".to_sym, -> (value) {
11
11
  res = ArDocStore.convert_boolean(value)
12
- write_store_attribute(:data, key, res)
12
+ write_store_attribute(json_column, key, res)
13
13
  }
14
14
  add_ransacker(key, 'bool')
15
15
  end
@@ -1,9 +1,9 @@
1
1
  module ArDocStore
2
-
2
+
3
3
  class EmbeddedCollection < Array
4
4
  attr_accessor :parent
5
5
  end
6
-
6
+
7
7
  module AttributeTypes
8
8
 
9
9
  class EmbedsManyAttribute < BaseAttribute
@@ -11,7 +11,7 @@ module ArDocStore
11
11
  def build
12
12
  assn_name = attribute.to_sym
13
13
  class_name = options[:class_name] || attribute.to_s.classify
14
- model.store_accessor :data, assn_name
14
+ model.store_accessor model.json_column, assn_name
15
15
  create_reader_for assn_name, class_name
16
16
  create_writer_for assn_name, class_name
17
17
  create_build_method_for assn_name, class_name
@@ -19,23 +19,23 @@ module ArDocStore
19
19
  create_embeds_many_attributes_method(class_name, assn_name)
20
20
  create_embeds_many_validation(assn_name)
21
21
  end
22
-
22
+
23
23
  private
24
-
24
+
25
25
  def add_method(method, block)
26
26
  model.class_eval do
27
27
  define_method method, block
28
28
  end
29
29
  end
30
-
30
+
31
31
  def create_reader_for(assn_name, class_name)
32
32
  add_method assn_name.to_sym, -> {
33
33
  ivar = "@#{assn_name}"
34
34
  instance_variable_get(ivar) || begin
35
35
  my_class_name = class_name.constantize
36
- items = read_store_attribute(:data, assn_name)
36
+ items = read_store_attribute(json_column, 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.build(item) }
38
+ items = ArDocStore::EmbeddedCollection.new items.map { |item| item.is_a?(my_class_name) ? item : my_class_name.build(item) }
39
39
  else
40
40
  items ||= ArDocStore::EmbeddedCollection.new
41
41
  end
@@ -60,10 +60,10 @@ module ArDocStore
60
60
  items = []
61
61
  end
62
62
  items.parent = self
63
- instance_variable_set "@#{assn_name}", write_store_attribute(:data, assn_name, items)
63
+ instance_variable_set "@#{assn_name}", write_store_attribute(json_column, assn_name, items)
64
64
  }
65
65
  end
66
-
66
+
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
@@ -74,18 +74,18 @@ module ArDocStore
74
74
  item
75
75
  }
76
76
  end
77
-
77
+
78
78
  def create_ensure_method_for(assn_name)
79
79
  method = -> { public_send "build_#{assn_name.to_s.singularize}" if self.public_send(assn_name).blank? }
80
80
  add_method "ensure_#{assn_name.to_s.singularize}", method
81
81
  add_method "ensure_#{assn_name}", method
82
82
  end
83
-
83
+
84
84
  def create_embeds_many_attributes_method(class_name, assn_name)
85
85
  add_method "#{assn_name}_attributes=", -> (values) {
86
86
  return if values.blank?
87
87
  # if it's a single item then wrap it in an array but how to tell?
88
-
88
+
89
89
  if values.respond_to?(:each)
90
90
  if values.respond_to?(:values)
91
91
  values = values.values
@@ -103,9 +103,9 @@ module ArDocStore
103
103
  validate_method = "validate_embedded_record_for_#{assn_name}"
104
104
  define_method validate_method, -> { validate_embeds_many assn_name }
105
105
  validate validate_method
106
- end
106
+ end
107
107
  end
108
-
108
+
109
109
 
110
110
  end
111
111
 
@@ -113,7 +113,7 @@ module ArDocStore
113
113
  attr_reader :models, :assn_name, :parent, :class_name
114
114
  def initialize(parent, class_name, assn_name, models, values)
115
115
  @parent, @class_name, @assn_name, @models, @values = parent, class_name, assn_name, models, values
116
- values.each { |value|
116
+ values.each { |value|
117
117
  value = value.symbolize_keys
118
118
  if value.key?(:id)
119
119
  process_existing_model(value)
@@ -122,22 +122,22 @@ module ArDocStore
122
122
  end
123
123
  }
124
124
  end
125
-
125
+
126
126
  private
127
-
127
+
128
128
  attr_writer :models, :values
129
129
  attr_reader :values, :assn_name
130
-
130
+
131
131
  def process_existing_model(value)
132
132
  return false unless value.key?(:id)
133
133
  model = find_model_by_value(value)
134
134
  model && destroy_or_update(model, value) or add(value)
135
135
  end
136
-
136
+
137
137
  def destroy_or_update(model, value)
138
138
  destroy(model, value) or update_attributes(model, value)
139
139
  end
140
-
140
+
141
141
  def add(value)
142
142
  models << class_name.constantize.new(value)
143
143
  end
@@ -145,15 +145,15 @@ module ArDocStore
145
145
  def destroy(model, value)
146
146
  wants_to_die?(value) && models.delete(model)
147
147
  end
148
-
148
+
149
149
  def update_attributes(model, value)
150
150
  model.attributes = value
151
151
  end
152
-
152
+
153
153
  def wants_to_die?(value)
154
154
  value.key?(:_destroy) && ArDocStore.convert_boolean(value[:_destroy])
155
155
  end
156
-
156
+
157
157
  def find_model_by_value(value)
158
158
  models.detect { |item| item.id == value[:id] }
159
159
  end
@@ -15,7 +15,7 @@ module ArDocStore
15
15
  model.class_eval <<-CODE, __FILE__, __LINE__ + 1
16
16
  def #{attribute}
17
17
  @#{attribute} || begin
18
- item = read_store_attribute :data, :#{attribute}
18
+ item = read_store_attribute json_column, :#{attribute}
19
19
  item = #{class_name}.build(item) unless item.is_a?(#{class_name})
20
20
  @#{attribute} = item
21
21
  end
@@ -29,7 +29,7 @@ module ArDocStore
29
29
  end
30
30
  value = #{class_name}.build value
31
31
  @#{attribute} = value
32
- write_store_attribute :data, :#{attribute}, value
32
+ write_store_attribute json_column, :#{attribute}, value
33
33
  end
34
34
  CODE
35
35
  end
@@ -67,7 +67,7 @@ module ArDocStore
67
67
  validate :validate_embedded_record_for_#{attribute}
68
68
  CODE
69
69
  end
70
-
70
+
71
71
  end
72
72
 
73
73
  end
@@ -7,17 +7,17 @@ module ArDocStore
7
7
  def build
8
8
  key = attribute.to_sym
9
9
  model.class_eval do
10
- store_accessor :data, key
10
+ store_accessor json_column, key
11
11
  define_method key, -> {
12
- value = read_store_attribute(:data, key)
12
+ value = read_store_attribute(json_column, key)
13
13
  unless value
14
14
  value = SecureRandom.uuid
15
- write_store_attribute :data, key, value
15
+ write_store_attribute json_column, key, value
16
16
  end
17
17
  value
18
18
  }
19
19
  define_method "#{key}=".to_sym, -> (value) {
20
- write_store_attribute(:data, key, value)
20
+ write_store_attribute(json_column, key, value)
21
21
  }
22
22
  add_ransacker(key, 'text')
23
23
  end
@@ -21,17 +21,17 @@ module ArDocStore
21
21
 
22
22
  class_attribute :virtual_attributes
23
23
  self.virtual_attributes ||= HashWithIndifferentAccess.new
24
-
24
+
25
25
  delegate :as_json, to: :attributes
26
-
26
+
27
27
  attribute :id, :uuid
28
28
 
29
29
  end
30
30
 
31
31
  end
32
-
32
+
33
33
  module InstanceMethods
34
-
34
+
35
35
  def initialize(attrs=HashWithIndifferentAccess.new)
36
36
  @_initialized = true
37
37
  initialize_attributes attrs
@@ -61,7 +61,7 @@ module ArDocStore
61
61
  end
62
62
  self
63
63
  end
64
-
64
+
65
65
  def persisted?
66
66
  false
67
67
  end
@@ -98,16 +98,16 @@ module ArDocStore
98
98
  end
99
99
 
100
100
  end
101
-
101
+
102
102
  module ClassMethods
103
-
103
+
104
104
  #:nodoc:
105
105
  def store_accessor(store, key)
106
106
  self.virtual_attributes ||= HashWithIndifferentAccess.new
107
107
  self.virtual_attributes = virtual_attributes.merge key => true
108
108
  key = key.to_sym
109
- define_method key, -> { read_store_attribute(:data, key) }
110
- define_method "#{key}=".to_sym, -> (value) { write_store_attribute :data, key, value }
109
+ define_method key, -> { read_store_attribute(json_column, key) }
110
+ define_method "#{key}=".to_sym, -> (value) { write_store_attribute json_column, key, value }
111
111
  end
112
112
 
113
113
  def build(attrs=HashWithIndifferentAccess.new)
@@ -124,4 +124,3 @@ module ArDocStore
124
124
 
125
125
  end
126
126
  end
127
-
@@ -1,20 +1,26 @@
1
1
  module ArDocStore
2
2
  module Storage
3
-
3
+
4
4
  def self.included(mod)
5
-
5
+
6
+ mod.class_attribute :json_column
7
+ mod.json_column ||= :data
6
8
  mod.class_attribute :virtual_attributes
7
9
  mod.virtual_attributes ||= HashWithIndifferentAccess.new
8
-
10
+
9
11
  mod.send :include, InstanceMethods
10
12
  mod.send :extend, ClassMethods
11
13
  end
12
-
14
+
13
15
  module InstanceMethods
14
-
16
+
17
+ def json_column
18
+ self.class.json_column
19
+ end
20
+
15
21
  def write_attribute(name, value)
16
22
  if is_stored_attribute?(name)
17
- write_store_attribute :data, attribute_name_from_foreign_key(name), value
23
+ write_store_attribute json_column, attribute_name_from_foreign_key(name), value
18
24
  else
19
25
  super
20
26
  end
@@ -22,14 +28,14 @@ module ArDocStore
22
28
 
23
29
  def read_attribute(name)
24
30
  if is_stored_attribute?(name)
25
- read_store_attribute :data, attribute_name_from_foreign_key(name)
31
+ read_store_attribute json_column, attribute_name_from_foreign_key(name)
26
32
  else
27
33
  super
28
34
  end
29
35
  end
30
36
 
31
37
  private
32
-
38
+
33
39
  def is_stored_attribute?(name)
34
40
  name = name.to_sym
35
41
  is_store_accessor_method?(name) || name =~ /data\-\>\>/
@@ -37,7 +43,7 @@ module ArDocStore
37
43
 
38
44
  def is_store_accessor_method?(name)
39
45
  name = name.to_sym
40
- self.class.stored_attributes[:data] && self.class.stored_attributes[:data].include?(name)
46
+ self.class.stored_attributes[json_column] && self.class.stored_attributes[json_column].include?(name)
41
47
  end
42
48
 
43
49
  def attribute_name_from_foreign_key(name)
@@ -58,9 +64,9 @@ module ArDocStore
58
64
  super clean_attribute_names_for_arel(attribute_names)
59
65
  end
60
66
  end
61
-
67
+
62
68
  module ClassMethods
63
-
69
+
64
70
  def attribute(name, *args)
65
71
  type = args.shift if args.first.is_a?(Symbol)
66
72
  options = args.extract_options!
@@ -147,8 +153,8 @@ module ArDocStore
147
153
  attribute field, options
148
154
  end
149
155
 
150
-
156
+
151
157
  end
152
-
158
+
153
159
  end
154
- end
160
+ end
@@ -1,3 +1,3 @@
1
1
  module ArDocStore
2
- VERSION = "0.2.0"
2
+ VERSION = "0.2.1"
3
3
  end
data/test/test_helper.rb CHANGED
@@ -45,8 +45,8 @@ class ARDuck
45
45
 
46
46
  def self.store_accessor(store, key)
47
47
  key = key.to_sym
48
- define_method key, -> { read_store_attribute(:data, key) }
49
- define_method "#{key}=".to_sym, -> (value) { write_store_attribute :data, key, value }
48
+ define_method key, -> { read_store_attribute(json_column, key) }
49
+ define_method "#{key}=".to_sym, -> (value) { write_store_attribute json_column, key, value }
50
50
  end
51
51
 
52
52
  def read_store_attribute(store, key)
@@ -113,7 +113,7 @@ class Restroom
113
113
  include ArDocStore::EmbeddableModel
114
114
  embeds_one :route
115
115
  embeds_one :door
116
-
116
+
117
117
  enumerates :restroom_type, values: %w{single double dirty nasty clean}
118
118
 
119
119
  attribute :is_restroom_provided, as: :boolean
@@ -121,9 +121,9 @@ class Restroom
121
121
 
122
122
  embeds_one :stall_area_dimensions, class_name: 'Dimensions'
123
123
  embeds_one :sink_area_dimensions, class_name: 'Dimensions'
124
-
124
+
125
125
  validates :restroom_type, presence: true
126
-
126
+
127
127
  end
128
128
 
129
129
  class Building < ActiveRecord::Base
@@ -141,4 +141,3 @@ class Building < ActiveRecord::Base
141
141
  embeds_many :entrances
142
142
  embeds_many :restrooms
143
143
  end
144
-
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.2.0
4
+ version: 0.2.1
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-23 00:00:00.000000000 Z
11
+ date: 2016-04-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord