jsonb_accessor 1.0.0.beta → 1.0.0.beta.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c764a056e39fc6979212aac29a31384cb694a38b
4
- data.tar.gz: 495df5a38ac759f3b07d718f818bddfbcb9449ec
3
+ metadata.gz: e5a63babf736e4f8208d2aef0238f2f2ec82fc7b
4
+ data.tar.gz: 72f1ef3b5c23839cd7fac5c016b3f5ab228b9ba6
5
5
  SHA512:
6
- metadata.gz: 8716e5c48b3122677518e20230d3d70c59b42eb9434ed4ebe8992f4ef9b797a4b9e2b2566960ed34a3a6cbf77baa785f0fe0853f9bf40198d9214360ced4bf8f
7
- data.tar.gz: 90389c14a8b8aa3b27da14301897e5ee836c713456ed562421ef989d6b3c88407d211ff22cc79fd76be318735660bb9129e4ce6ccd9bd7b17bbe7444f346b2b8
6
+ metadata.gz: 82287481f107b50bb00aa74a2e750010db9a0961110107604538ed976500bfec54fab7b5ee0f9ef4f013b3bb742e5c44d8dcfb2c5371a3286807c580705686ad
7
+ data.tar.gz: 71eab6fd8aa74450a93858d1d1970af547a13a5e9da49e43cdff6f55d0c78b5c164ef00f7ce36532ec2ce1caeeb3cff05dc0a1624f1c1c002623ee697e2782b2
data/README.md CHANGED
@@ -4,13 +4,20 @@
4
4
 
5
5
  Adds typed `jsonb` backed fields as first class citizens to your `ActiveRecord` models. This gem is similar in spirit to [HstoreAccessor](https://github.com/devmynd/hstore_accessor), but the `jsonb` column in PostgreSQL has a few distinct advantages, mostly around nested documents and support for collections.
6
6
 
7
+ It also adds generic scopes for querying `jsonb` columns.
8
+
9
+ ## 1.0 Beta
10
+
11
+ This README reflects the 1.0 beta. Method names and interfaces may still change.
12
+
7
13
  ## Table of Contents
8
14
 
9
15
  * [Installation](#installation)
10
16
  * [Usage](#usage)
11
- * [Validations](#validations)
17
+ * [Scopes](#scopes)
12
18
  * [Single-Table Inheritance](#single-table-inheritance)
13
19
  * [Dependencies](#dependencies)
20
+ * [Validations](#validations)
14
21
  * [Development](#development)
15
22
  * [Contributing](#contributing)
16
23
 
@@ -31,10 +38,10 @@ And then execute:
31
38
  First we must create a model which has a `jsonb` column available to store data into it:
32
39
 
33
40
  ```ruby
34
- class CreateProductsTable < ActiveRecord::Migration
41
+ class CreateProducts < ActiveRecord::Migration
35
42
  def change
36
43
  create_table :products do |t|
37
- t.jsonb :options
44
+ t.jsonb :data
38
45
  end
39
46
  end
40
47
  end
@@ -44,19 +51,141 @@ We can then declare the `jsonb` fields we wish to expose via the accessor:
44
51
 
45
52
  ```ruby
46
53
  class Product < ActiveRecord::Base
47
- jsonb_accessor(
48
- :options,
54
+ jsonb_accessor :data,
49
55
  title: :string,
50
- id_value: :value,
51
56
  external_id: :integer,
52
57
  reviewed_at: :datetime
53
- )
54
58
  end
55
59
  ```
56
60
 
57
- ## Validations
61
+ Any type the [`attribute` API](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute) supports. You can also implement your own type by following the example in the `attribute` documentation.
62
+
63
+ To pass through options like `default` and `array` to the `attribute` API, just put them in an array.
64
+
65
+ ```ruby
66
+ class Product < ActiveRecord::Base
67
+ jsonb_accessor :data,
68
+ title: [:string, default: "Untitled"],
69
+ previous_titles: [:string, array: true, default: []]
70
+ end
71
+ ```
72
+
73
+ You can also pass in a `store_key` option.
74
+
75
+ ```ruby
76
+ class Product < ActiveRecord::Base
77
+ jsonb_accessor :data, title: [:string, store_key: :t]
78
+ end
79
+ ```
80
+
81
+ This allows you to use `title` for your getters and setters, but use `t` as the key in the `jsonb` column.
82
+
83
+ ```ruby
84
+ product = Product.new(title: "Foo")
85
+ product.title #=> "Foo"
86
+ product.data #=> { "t" => "Foo" }
87
+ ```
88
+
89
+ ## Scopes
90
+
91
+ Jsonb Accessor provides several scopes to make it easier to query `jsonb` columns. `jsonb_contains`, `jsonb_number_where`, `jsonb_time_where`, and `jsonb_where` are available on all `ActiveRecord::Base` subclasses and don't require that you make use of the `jsonb_accessor` declaration.
92
+
93
+ If a class does have a `jsonb_accessor` declaration, then we define one custom scope. So, let's say we have a class that looks like this:
94
+
95
+ ```ruby
96
+ class Product < ActiveRecord::Base
97
+ jsonb_accessor :data,
98
+ name: :string,
99
+ price: [:integer, store_key: :p],
100
+ price_in_cents: :integer,
101
+ reviewed_at: :datetime
102
+ end
103
+ ```
104
+
105
+ Jsonb Accessor will add a `scope` to `Product` called `data_where`.
106
+
107
+ ```ruby
108
+ Product.all.data_where(name: "Granite Towel", price: 17)
109
+ ```
110
+
111
+ For number fields you can query using `<` or `>`or use plain english if that's what you prefer.
58
112
 
59
- Because this gem promotes attributes nested into the JSON column to first level attributes, most validations should just work. We still have to add some testing and support around this feature but feel free to try and leave us feedback if they're not working as expected.
113
+ ```ruby
114
+ Product.all.data_where(price: { <: 15 })
115
+ Product.all.data_where(price: { <=: 15 })
116
+ Product.all.data_where(price: { less_than: 15 })
117
+ Product.all.data_where(price: { less_than_or_equal_to: 15 })
118
+
119
+ Product.all.data_where(price: { >: 15 })
120
+ Product.all.data_where(price: { >=: 15 })
121
+ Product.all.data_where(price: { greater_than: 15 })
122
+ Product.all.data_where(price: { greater_than_or_equal_to: 15 })
123
+
124
+ Product.all.data_where(price: { greater_than: 15, less_than: 30 })
125
+ ```
126
+
127
+ For time related fields you can query using `before` and `after`.
128
+
129
+ ```ruby
130
+ Product.all.data_where(reviewed_at: { before: Time.current.beginning_of_week, after: 4.weeks.ago })
131
+ ```
132
+
133
+ This scope is a convenient wrapper around the `jsonb_where` `scope` that saves you from having to convert the given keys to the store keys and from specifying the column.
134
+
135
+ ### `jsonb_where`
136
+
137
+ Works just like the [`scope` above](#scopes) except that it does not convert the given keys to store keys and you must specify the column name. For example:
138
+
139
+ ```ruby
140
+ Product.all.jsonb_where(:data, reviewed_at: { before: Time.current }, p: { greater_than: 5 })
141
+
142
+ # instead of
143
+
144
+ Product.all.data_where(reviewed_at: { before: Time.current }, price: { greater_than: 5 })
145
+ ```
146
+ This scope makes use of the `jsonb_contains`, `jsonb_number_where`, and `jsonb_time_where` `scope`s.
147
+
148
+ ### `jsonb_contains`
149
+
150
+ Returns all records that contain the given JSON paths.
151
+
152
+ ```ruby
153
+ Product.all.jsonb_contains(:data, title: "foo")
154
+ Product.all.jsonb_contains(:data, reviewed_at: 10.minutes.ago, p: 12) # Using the store key
155
+ ```
156
+
157
+ **Note:** Under the hood, `jsonb_contains` uses the [`@>` operator in Postgres](https://www.postgresql.org/docs/9.5/static/functions-json.html) so when you include an array query, the stored array and the array used for the query do not need to match exactly. For example, when queried with `[1, 2]`, records that have arrays of `[2, 1, 3]` will be returned.
158
+
159
+ ### `jsonb_number_where`
160
+
161
+ Returns all records that match the given criteria.
162
+
163
+ ```ruby
164
+ Product.all.jsonb_number_where(:data, :price_in_cents, :greater_than, 300)
165
+ ```
166
+
167
+ It supports:
168
+
169
+ * `>`
170
+ * `>=`
171
+ * `greater_than`
172
+ * `greater_than_or_equal_to`
173
+ * `<`
174
+ * `<=`
175
+ * `less_than`
176
+ * `less_than_or_equal_to`
177
+
178
+ and it is indifferent to strings/symbols.
179
+
180
+ ### `jsonb_time_where`
181
+
182
+ Returns all records that match the given criteria.
183
+
184
+ ```ruby
185
+ Product.all.jsonb_time_where(:data, :reviewed_at, :before, 2.days.ago)
186
+ ```
187
+
188
+ It supports `before` and `after` and is indifferent to strings/symbols.
60
189
 
61
190
  ## Single-Table Inheritance
62
191
 
@@ -70,8 +199,8 @@ rows can have different values.
70
199
  We set up our table with an `jsonb` field:
71
200
 
72
201
  ```ruby
73
- # db/migration/<timestamp>_create_players_table.rb
74
- class CreateVehiclesTable < ActiveRecord::Migration
202
+ # db/migration/<timestamp>_create_players.rb
203
+ class CreateVehicles < ActiveRecord::Migration
75
204
  def change
76
205
  create_table :vehicles do |t|
77
206
  t.string :make
@@ -110,44 +239,16 @@ From here any attributes specific to any sub-class can be stored in the
110
239
  `jsonb` column avoiding sparse data. Indices can also be created on
111
240
  individual fields in an `jsonb` column.
112
241
 
113
- This approach was originally concieved by Joe Hirn in [this blog
242
+ This approach was originally conceived by Joe Hirn in [this blog
114
243
  post](http://www.devmynd.com/blog/2013-3-single-table-inheritance-hstore-lovely-combination).
115
244
 
116
- ## Scopes
117
-
118
- JsonbAccessor currently supports several scopes. Let's say we have a class that looks like this:
119
-
120
- ```ruby
121
- class Product < ActiveRecord::Base
122
- jsonb_accessor :data,
123
- approved: :boolean,
124
- name: :string,
125
- price: :integer,
126
- previous_prices: :integer_array,
127
- reviewed_at: :date_time
128
- end
129
- ```
130
-
131
- ### General Scopes
132
-
133
- #### `<jsonb_field>_contains`
134
-
135
- **Description:** returns all records that contain matching attributes in the specified `jsonb` field.
136
-
137
- ```ruby
138
- product_1 = Product.create!(name: "foo", approved: true, reviewed_at: 3.days.ago)
139
- product_2 = Product.create!(name: "bar", approved: true)
140
- product_3 = Product.create!(name: "foo", approved: false)
141
-
142
- Product.data_contains(name: "foo", approved: true) # => [product_1]
143
- ```
245
+ ## Validations
144
246
 
145
- **Note:** when including an array attribute, the stored array and the array used for the query do not need to match exactly. For example, when queried with `[1, 2]`, records that have arrays of `[2, 1, 3]` will be returned.
247
+ Because this gem promotes attributes nested into the JSON column to first level attributes, most validations should just work. Please leave us feedback if they're not working as expected.
146
248
 
147
249
  ## Dependencies
148
250
 
149
- - ActiveRecord 5.0
150
- - Ruby >= 2.2.2
251
+ - ActiveRecord >= 5.0
151
252
  - Postgres >= 9.4 (in order to use the [jsonb column type](http://www.postgresql.org/docs/9.4/static/datatype-json.html)).
152
253
 
153
254
  ## Development
data/UPGRADE_GUIDE.md ADDED
@@ -0,0 +1,67 @@
1
+ # Upgrading from 0.X.X to 1.0.0
2
+
3
+ ## Jsonb Accessor declaration
4
+
5
+ In 0.X.X you would write:
6
+
7
+ ```ruby
8
+ class Product < ActiveRecord::Base
9
+ jsonb_accessor :data,
10
+ :count, # doesn't specify a type
11
+ title: :string,
12
+ external_id: :integer,
13
+ reviewed_at: :date_time, # snake cased
14
+ previous_rankings: :integer_array, # `:type_array` key
15
+ external_rankings: :array # plain array
16
+ end
17
+ ```
18
+
19
+ In 1.0.0 you would write:
20
+
21
+ ```ruby
22
+ class Product < ActiveRecord::Base
23
+ jsonb_accessor :data,
24
+ count: :value, # all fields must specify a type
25
+ title: :string,
26
+ external_id: :integer,
27
+ reviewed_at: :datetime, # `:date_time` is now `:datetime`
28
+ previous_rankings: [:integer, array: true], # now just the type followed by `array: true`
29
+ external_rankings: [:value, array: true] # now the value type is specified as well as `array: true`
30
+ end
31
+ ```
32
+
33
+ There are several important differences. All fields must now specify a type, `:date_time` is now `:datetime`, and arrays are specified using a type and `array: true` instead of `type_array`.
34
+
35
+ Also, in order to use the `value` type you need to register it:
36
+
37
+ ```ruby
38
+ # in an initializer
39
+ ActiveRecord::Type.register(:value, ActiveRecord::Type::Value)
40
+ ```
41
+
42
+ ### Deeply nested objects
43
+
44
+ In 0.X.X you could write:
45
+
46
+ ```ruby
47
+ class Product < ActiveRecord::Base
48
+ jsonb_accessor :data,
49
+ ranking_info: {
50
+ original_rank: :integer,
51
+ current_rank: :integer,
52
+ metadata: {
53
+ ranked_on: :date
54
+ }
55
+ }
56
+ end
57
+ ```
58
+
59
+ Which would allow you to use getter and setter methods at any point in the structure.
60
+
61
+ ```ruby
62
+ Product.new(ranking_info: { original_rank: 3, current_rank: 5, metadata: { ranked_on: Date.today } })
63
+ product.ranking_info.original_rank # 3
64
+ product.ranking_info.metadata.ranked_on # Date.today
65
+ ```
66
+
67
+ 1.0.0 does not support this syntax. If you need these sort of methods, you can create your own type `class` and register it with `ActiveRecord::Type`. [Here's an example](http://api.rubyonrails.org/classes/ActiveRecord/Attributes/ClassMethods.html#method-i-attribute).
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ../
3
3
  specs:
4
- jsonb_accessor (1.0.0.beta)
4
+ jsonb_accessor (1.0.0.beta.1)
5
5
  activerecord (>= 5.0.0)
6
6
  pg (>= 0.18.1)
7
7
 
@@ -46,12 +46,12 @@ module JsonbAccessor
46
46
  scope(:jsonb_contains,
47
47
  -> (column_name, attributes) { where("#{table_name}.#{column_name} @> (?)::jsonb", attributes.to_json) })
48
48
 
49
- scope(:jsonb_number_query, lambda do |column_name, field_name, given_operator, value|
49
+ scope(:jsonb_number_where, lambda do |column_name, field_name, given_operator, value|
50
50
  operator = JsonbAccessor::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
51
51
  where("(#{table_name}.#{column_name} ->> ?)::float #{operator} ?", field_name, value)
52
52
  end)
53
53
 
54
- scope(:jsonb_time_query, lambda do |column_name, field_name, given_operator, value|
54
+ scope(:jsonb_time_where, lambda do |column_name, field_name, given_operator, value|
55
55
  operator = JsonbAccessor::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
56
56
  where("(#{table_name}.#{column_name} ->> ?)::timestamp #{operator} ?", field_name, value)
57
57
  end)
@@ -63,9 +63,9 @@ module JsonbAccessor
63
63
  attributes.each do |name, value|
64
64
  case value
65
65
  when IS_NUMBER_QUERY_ARGUMENTS
66
- value.each { |operator, query_value| query = query.jsonb_number_query(column_name, name, operator, query_value) }
66
+ value.each { |operator, query_value| query = query.jsonb_number_where(column_name, name, operator, query_value) }
67
67
  when IS_TIME_QUERY_ARGUMENTS
68
- value.each { |operator, query_value| query = query.jsonb_time_query(column_name, name, operator, query_value) }
68
+ value.each { |operator, query_value| query = query.jsonb_time_where(column_name, name, operator, query_value) }
69
69
  else
70
70
  contains_attributes[name] = value
71
71
  end
@@ -1,4 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
  module JsonbAccessor
3
- VERSION = "1.0.0.beta"
3
+ VERSION = "1.0.0.beta.1"
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jsonb_accessor
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.beta
4
+ version: 1.0.0.beta.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Michael Crismali
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: exe
12
12
  cert_chain: []
13
- date: 2016-10-18 00:00:00.000000000 Z
13
+ date: 2016-10-19 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -228,6 +228,7 @@ files:
228
228
  - LICENSE.txt
229
229
  - README.md
230
230
  - Rakefile
231
+ - UPGRADE_GUIDE.md
231
232
  - bin/console
232
233
  - bin/setup
233
234
  - db/config.yml