jsonb_accessor 0.4.0.beta → 1.0.0.beta
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -0
- data/.travis.yml +2 -3
- data/Appraisals +0 -5
- data/README.md +7 -213
- data/Rakefile +1 -0
- data/bin/console +3 -5
- data/db/schema.rb +0 -1
- data/gemfiles/activerecord_5.0.0.gemfile.lock +21 -23
- data/jsonb_accessor.gemspec +3 -4
- data/lib/jsonb_accessor/macro.rb +44 -156
- data/lib/jsonb_accessor/query_builder.rb +78 -0
- data/lib/jsonb_accessor/version.rb +2 -1
- data/lib/jsonb_accessor.rb +3 -6
- metadata +11 -31
- data/gemfiles/activerecord_4.2.4.gemfile +0 -8
- data/gemfiles/activerecord_4.2.4.gemfile.lock +0 -146
- data/lib/jsonb_accessor/class_builder.rb +0 -101
- data/lib/jsonb_accessor/fields_map.rb +0 -32
- data/lib/jsonb_accessor/helpers.rb +0 -19
- data/lib/jsonb_accessor/nested_base.rb +0 -35
- data/lib/jsonb_accessor/type_helper.rb +0 -68
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c764a056e39fc6979212aac29a31384cb694a38b
|
4
|
+
data.tar.gz: 495df5a38ac759f3b07d718f818bddfbcb9449ec
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8716e5c48b3122677518e20230d3d70c59b42eb9434ed4ebe8992f4ef9b797a4b9e2b2566960ed34a3a6cbf77baa785f0fe0853f9bf40198d9214360ced4bf8f
|
7
|
+
data.tar.gz: 90389c14a8b8aa3b27da14301897e5ee836c713456ed562421ef989d6b3c88407d211ff22cc79fd76be318735660bb9129e4ce6ccd9bd7b17bbe7444f346b2b8
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.2.
|
4
|
-
- 2.3.
|
3
|
+
- 2.2.5
|
4
|
+
- 2.3.1
|
5
5
|
addons:
|
6
6
|
postgresql: '9.4'
|
7
7
|
before_install:
|
@@ -13,5 +13,4 @@ before_script:
|
|
13
13
|
- bundle exec rake db:migrate
|
14
14
|
cache: bundler
|
15
15
|
gemfile:
|
16
|
-
- gemfiles/activerecord_4.2.4.gemfile
|
17
16
|
- gemfiles/activerecord_5.0.0.gemfile
|
data/Appraisals
CHANGED
data/README.md
CHANGED
@@ -1,19 +1,15 @@
|
|
1
1
|
# JSONb Accessor
|
2
2
|
|
3
|
-
[![Gem Version](https://badge.fury.io/rb/jsonb_accessor.svg)](http://badge.fury.io/rb/jsonb_accessor) [![Build Status](https://travis-ci.org/devmynd/jsonb_accessor.svg)](https://travis-ci.org/devmynd/jsonb_accessor)
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/jsonb_accessor.svg)](http://badge.fury.io/rb/jsonb_accessor) [![Build Status](https://travis-ci.org/devmynd/jsonb_accessor.svg)](https://travis-ci.org/devmynd/jsonb_accessor)
|
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
7
|
## Table of Contents
|
8
8
|
|
9
9
|
* [Installation](#installation)
|
10
|
-
* [Rails 5](#rails-5)
|
11
10
|
* [Usage](#usage)
|
12
|
-
* [ActiveRecord Methods Generated for Fields](#activerecord-methods-generated-for-fields)
|
13
11
|
* [Validations](#validations)
|
14
12
|
* [Single-Table Inheritance](#single-table-inheritance)
|
15
|
-
* [Scopes](#scopes)
|
16
|
-
* [Migrations](#migrations)
|
17
13
|
* [Dependencies](#dependencies)
|
18
14
|
* [Development](#development)
|
19
15
|
* [Contributing](#contributing)
|
@@ -30,10 +26,6 @@ And then execute:
|
|
30
26
|
|
31
27
|
$ bundle install
|
32
28
|
|
33
|
-
## Rails 5
|
34
|
-
|
35
|
-
Version 0.4.X will run on Rails 5, but behavior around type coercion for array and other collection field types behaves differently. When you upgrade to 0.4.X make sure you do not depend on subtle type coercion rules.
|
36
|
-
|
37
29
|
## Usage
|
38
30
|
|
39
31
|
First we must create a model which has a `jsonb` column available to store data into it:
|
@@ -54,82 +46,14 @@ We can then declare the `jsonb` fields we wish to expose via the accessor:
|
|
54
46
|
class Product < ActiveRecord::Base
|
55
47
|
jsonb_accessor(
|
56
48
|
:options,
|
57
|
-
:count, # => value type
|
58
49
|
title: :string,
|
59
50
|
id_value: :value,
|
60
51
|
external_id: :integer,
|
61
|
-
reviewed_at: :
|
52
|
+
reviewed_at: :datetime
|
62
53
|
)
|
63
54
|
end
|
64
55
|
```
|
65
56
|
|
66
|
-
JSONb Accessor accepts both untyped and typed key definitions. Untyped keys are treated as-is and no additional casting is performed. This allows the freedom of dynamic values alongside the power types, which is especially convenient when saving nested form attributes. Typed keys will be cast to their respective values using the same mechanism ActiveRecord uses to coerce standard attribute columns. It's as close to a real column as you can get and the goal is to keep it that way.
|
67
|
-
|
68
|
-
All untyped keys must be defined prior to typed columns. You can declare a typed column with type `value` for explicit dynamic behavior. For reference, the `jsonb_accessor` macro is defined thusly.
|
69
|
-
|
70
|
-
```ruby
|
71
|
-
def jsonb_accessor(jsonb_attribute, *value_fields, **typed_fields)
|
72
|
-
...
|
73
|
-
end
|
74
|
-
```
|
75
|
-
|
76
|
-
There's quite a bit more to do do and document but we're excited to get this out there while we work on it some more.
|
77
|
-
|
78
|
-
## ActiveRecord Methods Generated for Fields
|
79
|
-
|
80
|
-
```ruby
|
81
|
-
class Product < ActiveRecord::Base
|
82
|
-
jsonb_accessor :data, field: :string
|
83
|
-
end
|
84
|
-
```
|
85
|
-
|
86
|
-
* `field`
|
87
|
-
* `field=`
|
88
|
-
* `field?`
|
89
|
-
* `field_changed?`
|
90
|
-
* `field_was`
|
91
|
-
* `field_change`
|
92
|
-
* `reset_field!`
|
93
|
-
* `restore_field!`
|
94
|
-
* `field_will_change!`
|
95
|
-
|
96
|
-
### Supported Types
|
97
|
-
|
98
|
-
Because the underlying storage mechanism is JSON, we attempt to abide by the limitations of what can be represented natively. We use [ActiveRecord::Type](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/type.rb) for seralization, but any type defined in the [Postgres connection adapter](https://github.com/rails/rails/blob/master/activerecord/lib/active_record/connection_adapters/postgresql/oid.rb) will also be accepted. Beware of the impact of using complex Postgres column types such as inet, enum, hstore, etc... We plan to restrict which types are allowed in a future patch.
|
99
|
-
|
100
|
-
The following types are explicitly supported.
|
101
|
-
|
102
|
-
* big_integer
|
103
|
-
* binary
|
104
|
-
* boolean
|
105
|
-
* date
|
106
|
-
* date_time
|
107
|
-
* decimal
|
108
|
-
* float
|
109
|
-
* integer
|
110
|
-
* string
|
111
|
-
* text
|
112
|
-
* time
|
113
|
-
* value
|
114
|
-
|
115
|
-
|
116
|
-
Typed arrays are also supported by specifying `:type_array` (i.e. `:float_array`). `:array` is interpreted as an array of `value` types.
|
117
|
-
|
118
|
-
Support for nested types is also available but experimental at this point. If you must, you may try something like this for nested objects.
|
119
|
-
|
120
|
-
```ruby
|
121
|
-
class Product < ActiveRecord::Base
|
122
|
-
jsonb_accessor(
|
123
|
-
:options,
|
124
|
-
nested_object: { key: :integer }
|
125
|
-
)
|
126
|
-
end
|
127
|
-
|
128
|
-
p = Product.new
|
129
|
-
p.nested_object.key = "10"
|
130
|
-
puts p.nested_object.key #=> 10
|
131
|
-
```
|
132
|
-
|
133
57
|
## Validations
|
134
58
|
|
135
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.
|
@@ -208,7 +132,7 @@ end
|
|
208
132
|
|
209
133
|
#### `<jsonb_field>_contains`
|
210
134
|
|
211
|
-
**Description:** returns all records that contain matching attributes in the specified `jsonb` field.
|
135
|
+
**Description:** returns all records that contain matching attributes in the specified `jsonb` field.
|
212
136
|
|
213
137
|
```ruby
|
214
138
|
product_1 = Product.create!(name: "foo", approved: true, reviewed_at: 3.days.ago)
|
@@ -218,143 +142,13 @@ product_3 = Product.create!(name: "foo", approved: false)
|
|
218
142
|
Product.data_contains(name: "foo", approved: true) # => [product_1]
|
219
143
|
```
|
220
144
|
|
221
|
-
**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.
|
222
|
-
|
223
|
-
#### `with_<jsonb_defined_field>`
|
224
|
-
|
225
|
-
**Description:** returns all records with the given value in the field. This is defined for all `jsonb_accessor` defined fields. It's a convenience method that allows you to do `Product.with_name("foo")` instead of `Product.data_contains(name: "foo")`.
|
226
|
-
|
227
|
-
```ruby
|
228
|
-
product_1 = Product.create!(name: "foo")
|
229
|
-
product_2 = Product.create!(name: "bar")
|
230
|
-
|
231
|
-
Product.with_name("foo") # => [product_1]
|
232
|
-
```
|
233
|
-
|
234
|
-
**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.
|
235
|
-
|
236
|
-
### Integer, Big Integer, Decimal, and Float Scopes
|
237
|
-
|
238
|
-
#### `<jsonb_defined_field>_gt`
|
239
|
-
|
240
|
-
**Description:** returns all records with a value that is greater than the argument.
|
241
|
-
|
242
|
-
```ruby
|
243
|
-
product_1 = Product.create!(price: 10)
|
244
|
-
product_2 = Product.create!(price: 11)
|
245
|
-
|
246
|
-
Product.price_gt(10) # => [product_2]
|
247
|
-
```
|
248
|
-
|
249
|
-
#### `<jsonb_defined_field>_gte`
|
250
|
-
|
251
|
-
**Description:** returns all records with a value that is greater than or equal to the argument.
|
252
|
-
|
253
|
-
```ruby
|
254
|
-
product_1 = Product.create!(price: 10)
|
255
|
-
product_2 = Product.create!(price: 11)
|
256
|
-
product_3 = Product.create!(price: 9)
|
257
|
-
|
258
|
-
Product.price_gte(10) # => [product_1, product_2]
|
259
|
-
```
|
260
|
-
|
261
|
-
#### `<jsonb_defined_field>_lt`
|
262
|
-
|
263
|
-
**Description:** returns all records with a value that is less than the argument.
|
264
|
-
|
265
|
-
```ruby
|
266
|
-
product_1 = Product.create!(price: 10)
|
267
|
-
product_2 = Product.create!(price: 11)
|
268
|
-
|
269
|
-
Product.price_lt(11) # => [product_1]
|
270
|
-
```
|
271
|
-
|
272
|
-
#### `<jsonb_defined_field>_lte`
|
273
|
-
|
274
|
-
**Description:** returns all records with a value that is less than or equal to the argument.
|
275
|
-
|
276
|
-
```ruby
|
277
|
-
product_1 = Product.create!(price: 10)
|
278
|
-
product_2 = Product.create!(price: 11)
|
279
|
-
product_3 = Product.create!(price: 12)
|
280
|
-
|
281
|
-
Product.price_lte(11) # => [product_1, product_2]
|
282
|
-
```
|
283
|
-
|
284
|
-
|
285
|
-
### Boolean Scopes
|
286
|
-
|
287
|
-
#### `is_<jsonb_defined_field>`
|
288
|
-
|
289
|
-
**Description:** returns all records where the value is `true`.
|
290
|
-
|
291
|
-
```ruby
|
292
|
-
product_1 = Product.create!(approved: true)
|
293
|
-
product_2 = Product.create!(approved: false)
|
294
|
-
|
295
|
-
Product.is_approved # => [product_1]
|
296
|
-
```
|
297
|
-
|
298
|
-
#### `not_<jsonb_defined_field>`
|
299
|
-
|
300
|
-
**Description:** returns all records where the value is `false`.
|
301
|
-
|
302
|
-
```ruby
|
303
|
-
product_1 = Product.create!(approved: true)
|
304
|
-
product_2 = Product.create!(approved: false)
|
305
|
-
|
306
|
-
Product.not_approved # => [product_2]
|
307
|
-
```
|
308
|
-
|
309
|
-
### Date, DateTime Scopes
|
310
|
-
|
311
|
-
#### `<jsonb_defined_field>_before`
|
312
|
-
|
313
|
-
**Description:** returns all records where the value is before the argument. Also supports JSON string arguments.
|
314
|
-
|
315
|
-
```ruby
|
316
|
-
product_1 = Product.create!(reviewed_at: 3.days.ago)
|
317
|
-
product_2 = Product.create!(reviewed_at: 5.days.ago)
|
318
|
-
|
319
|
-
Product.reviewed_at_before(4.days.ago) # => [product_2]
|
320
|
-
Product.reviewed_at_before(4.days.ago.to_json) # => [product_2]
|
321
|
-
```
|
322
|
-
|
323
|
-
#### `<jsonb_defined_field>_after`
|
324
|
-
|
325
|
-
**Description:** returns all records where the value is after the argument. Also supports JSON string arguments.
|
326
|
-
|
327
|
-
```ruby
|
328
|
-
product_1 = Product.create!(reviewed_at: 3.days.from_now)
|
329
|
-
product_2 = Product.create!(reviewed_at: 5.days.from_now)
|
330
|
-
|
331
|
-
Product.reviewed_at_after(4.days.from_now) # => [product_2]
|
332
|
-
Product.reviewed_at_after(4.days.from_now.to_json) # => [product_2]
|
333
|
-
```
|
334
|
-
|
335
|
-
### Array Scopes
|
336
|
-
|
337
|
-
#### `<jsonb_defined_fields>_contains`
|
338
|
-
|
339
|
-
**Description:** returns all records where the value is contained in the array field.
|
340
|
-
|
341
|
-
```ruby
|
342
|
-
product_1 = Product.create!(previous_prices: [3])
|
343
|
-
product_2 = Product.create!(previous_prices: [4, 5, 6])
|
344
|
-
|
345
|
-
Product.previous_prices_contains(5) # => [product_2]
|
346
|
-
```
|
347
|
-
|
348
|
-
## Migrations
|
349
|
-
|
350
|
-
Coming soon...
|
351
|
-
|
352
|
-
`jsonb` supports `GIN`, `GIST`, `btree` and `hash` indexes over `json` column. We have plans to add migrations helpers for generating these indexes for you.
|
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.
|
353
146
|
|
354
147
|
## Dependencies
|
355
148
|
|
356
|
-
- ActiveRecord
|
357
|
-
-
|
149
|
+
- ActiveRecord 5.0
|
150
|
+
- Ruby >= 2.2.2
|
151
|
+
- Postgres >= 9.4 (in order to use the [jsonb column type](http://www.postgresql.org/docs/9.4/static/datatype-json.html)).
|
358
152
|
|
359
153
|
## Development
|
360
154
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
2
3
|
# rubocop:disable Lint/UselessAssignment
|
3
4
|
|
4
5
|
require "bundler/setup"
|
@@ -6,11 +7,8 @@ require "jsonb_accessor"
|
|
6
7
|
require "rspec"
|
7
8
|
require File.expand_path("../../spec/spec_helper.rb", __FILE__)
|
8
9
|
|
9
|
-
|
10
|
-
|
11
|
-
database: "jsonb_accessor",
|
12
|
-
username: "postgres"
|
13
|
-
)
|
10
|
+
dbconfig = YAML.load(File.open("db/config.yml"))
|
11
|
+
ActiveRecord::Base.establish_connection(dbconfig["development"])
|
14
12
|
|
15
13
|
x = Product.new
|
16
14
|
|
data/db/schema.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: ../
|
3
3
|
specs:
|
4
|
-
jsonb_accessor (0.
|
5
|
-
activerecord (>=
|
4
|
+
jsonb_accessor (1.0.0.beta)
|
5
|
+
activerecord (>= 5.0.0)
|
6
6
|
pg (>= 0.18.1)
|
7
7
|
|
8
8
|
GEM
|
@@ -38,8 +38,6 @@ GEM
|
|
38
38
|
thor (>= 0.14.0)
|
39
39
|
arel (7.1.4)
|
40
40
|
ast (2.3.0)
|
41
|
-
astrolabe (1.3.1)
|
42
|
-
parser (~> 2.2)
|
43
41
|
awesome_print (1.7.0)
|
44
42
|
builder (3.2.2)
|
45
43
|
coderay (1.1.1)
|
@@ -84,25 +82,25 @@ GEM
|
|
84
82
|
thor (>= 0.18.1, < 2.0)
|
85
83
|
rainbow (2.1.0)
|
86
84
|
rake (10.5.0)
|
87
|
-
rspec (3.
|
88
|
-
rspec-core (~> 3.
|
89
|
-
rspec-expectations (~> 3.
|
90
|
-
rspec-mocks (~> 3.
|
91
|
-
rspec-core (3.
|
92
|
-
rspec-support (~> 3.
|
93
|
-
rspec-expectations (3.
|
85
|
+
rspec (3.5.0)
|
86
|
+
rspec-core (~> 3.5.0)
|
87
|
+
rspec-expectations (~> 3.5.0)
|
88
|
+
rspec-mocks (~> 3.5.0)
|
89
|
+
rspec-core (3.5.4)
|
90
|
+
rspec-support (~> 3.5.0)
|
91
|
+
rspec-expectations (3.5.0)
|
94
92
|
diff-lcs (>= 1.2.0, < 2.0)
|
95
|
-
rspec-support (~> 3.
|
96
|
-
rspec-mocks (3.
|
93
|
+
rspec-support (~> 3.5.0)
|
94
|
+
rspec-mocks (3.5.0)
|
97
95
|
diff-lcs (>= 1.2.0, < 2.0)
|
98
|
-
rspec-support (~> 3.
|
99
|
-
rspec-support (3.
|
100
|
-
rubocop (0.
|
101
|
-
|
102
|
-
parser (>= 2.2.2.1, < 3.0)
|
96
|
+
rspec-support (~> 3.5.0)
|
97
|
+
rspec-support (3.5.0)
|
98
|
+
rubocop (0.44.1)
|
99
|
+
parser (>= 2.3.1.1, < 3.0)
|
103
100
|
powerpack (~> 0.1)
|
104
101
|
rainbow (>= 1.99.1, < 3.0)
|
105
|
-
ruby-progressbar (~> 1.
|
102
|
+
ruby-progressbar (~> 1.7)
|
103
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
106
104
|
ruby-progressbar (1.8.1)
|
107
105
|
shoulda-matchers (3.1.1)
|
108
106
|
activesupport (>= 4.0.0)
|
@@ -115,13 +113,13 @@ GEM
|
|
115
113
|
thread_safe (0.3.5)
|
116
114
|
tzinfo (1.2.2)
|
117
115
|
thread_safe (~> 0.1)
|
116
|
+
unicode-display_width (1.1.1)
|
118
117
|
yard (0.9.5)
|
119
118
|
|
120
119
|
PLATFORMS
|
121
120
|
ruby
|
122
121
|
|
123
122
|
DEPENDENCIES
|
124
|
-
actionpack
|
125
123
|
activerecord (= 5.0.0)
|
126
124
|
appraisal
|
127
125
|
awesome_print
|
@@ -133,10 +131,10 @@ DEPENDENCIES
|
|
133
131
|
pry-doc
|
134
132
|
pry-nav
|
135
133
|
rake (~> 10.0)
|
136
|
-
rspec (~> 3.2
|
137
|
-
rubocop (
|
134
|
+
rspec (~> 3.2)
|
135
|
+
rubocop (~> 0.31)
|
138
136
|
shoulda-matchers
|
139
137
|
standalone_migrations
|
140
138
|
|
141
139
|
BUNDLED WITH
|
142
|
-
1.
|
140
|
+
1.13.5
|
data/jsonb_accessor.gemspec
CHANGED
@@ -20,10 +20,9 @@ Gem::Specification.new do |spec|
|
|
20
20
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
21
21
|
spec.require_paths = ["lib"]
|
22
22
|
|
23
|
-
spec.add_dependency "activerecord", ">=
|
23
|
+
spec.add_dependency "activerecord", ">= 5.0.0"
|
24
24
|
spec.add_dependency "pg", ">= 0.18.1"
|
25
25
|
|
26
|
-
spec.add_development_dependency "actionpack"
|
27
26
|
spec.add_development_dependency "appraisal"
|
28
27
|
spec.add_development_dependency "bundler", "~> 1.9"
|
29
28
|
spec.add_development_dependency "database_cleaner"
|
@@ -32,8 +31,8 @@ Gem::Specification.new do |spec|
|
|
32
31
|
spec.add_development_dependency "pry-doc"
|
33
32
|
spec.add_development_dependency "pry-nav"
|
34
33
|
spec.add_development_dependency "rake", "~> 10.0"
|
35
|
-
spec.add_development_dependency "rspec", "~> 3.2
|
36
|
-
spec.add_development_dependency "rubocop", "0.31
|
34
|
+
spec.add_development_dependency "rspec", "~> 3.2"
|
35
|
+
spec.add_development_dependency "rubocop", "~> 0.31"
|
37
36
|
spec.add_development_dependency "shoulda-matchers"
|
38
37
|
spec.add_development_dependency "standalone_migrations"
|
39
38
|
end
|
data/lib/jsonb_accessor/macro.rb
CHANGED
@@ -1,174 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
module JsonbAccessor
|
2
3
|
module Macro
|
3
4
|
module ClassMethods
|
4
|
-
def jsonb_accessor(jsonb_attribute,
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
_register_jsonb_classes_for_cleanup
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def _register_jsonb_classes_for_cleanup
|
29
|
-
return unless ENV["RACK_ENV"] == "development"
|
30
|
-
|
31
|
-
class_name = CLASS_PREFIX + name
|
32
|
-
clean_up_proc = proc do
|
33
|
-
if JsonbAccessor.constants.any? { |c| c.to_s == class_name }
|
34
|
-
JsonbAccessor.send(:remove_const, class_name)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
if ActiveRecord::VERSION::MAJOR == 5 && defined?(ActiveSupport::Reloader)
|
39
|
-
ActiveSupport::Reloader.to_complete(&clean_up_proc)
|
40
|
-
elsif defined?(ActionDispatch)
|
41
|
-
ActionDispatch::Reloader.to_cleanup(&clean_up_proc)
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
def _initialize_jsonb_attrs(jsonb_attribute, fields_map, jsonb_attribute_initialization_method_name)
|
46
|
-
define_method(jsonb_attribute_initialization_method_name) do
|
47
|
-
if has_attribute?(jsonb_attribute)
|
48
|
-
jsonb_attribute_hash = send(jsonb_attribute) || {}
|
49
|
-
|
50
|
-
fields_map.typed_fields.keys.each do |field|
|
51
|
-
write_attribute(field, jsonb_attribute_hash[field.to_s])
|
5
|
+
def jsonb_accessor(jsonb_attribute, field_types)
|
6
|
+
field_names = field_types.keys
|
7
|
+
names_and_store_keys = field_types.each_with_object({}) do |(name, type), mapping|
|
8
|
+
_type, options = Array(type)
|
9
|
+
mapping[name.to_s] = (options.try(:delete, :store_key) || name).to_s
|
10
|
+
end
|
11
|
+
# Defines virtual attributes for each jsonb field.
|
12
|
+
field_types.each do |name, type|
|
13
|
+
attribute name, *type
|
14
|
+
end
|
15
|
+
|
16
|
+
# Setters are in a module to allow users to override them and still be able to use `super`.
|
17
|
+
setters = Module.new do
|
18
|
+
# Overrides the setter created by `attribute` above to make sure the jsonb attribute is kept in sync.
|
19
|
+
names_and_store_keys.each do |name, store_key|
|
20
|
+
define_method("#{name}=") do |value|
|
21
|
+
super(value)
|
22
|
+
new_values = (public_send(jsonb_attribute) || {}).merge(store_key => public_send(name))
|
23
|
+
write_attribute(jsonb_attribute, new_values)
|
52
24
|
end
|
53
|
-
|
54
|
-
fields_map.nested_fields.keys.each do |field|
|
55
|
-
send("#{field}=", jsonb_attribute_hash[field.to_s])
|
56
|
-
end
|
57
|
-
|
58
|
-
send(:clear_attribute_changes, fields_map.names)
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
after_initialize(jsonb_attribute_initialization_method_name.to_sym)
|
63
|
-
end
|
64
|
-
|
65
|
-
def _create_jsonb_attribute_scope_name(jsonb_attribute, jsonb_attribute_scope_name)
|
66
|
-
scope jsonb_attribute_scope_name, (lambda do |attributes|
|
67
|
-
query_options = new(attributes).send(jsonb_attribute)
|
68
|
-
fields = attributes.keys.map(&:to_s)
|
69
|
-
query_options.delete_if { |key, value| fields.exclude?(key) }
|
70
|
-
query_json = TypeHelper.type_cast_as_jsonb(query_options)
|
71
|
-
where("#{table_name}.#{jsonb_attribute} @> ?", query_json)
|
72
|
-
end)
|
73
|
-
end
|
74
|
-
|
75
|
-
def _create_jsonb_scopes(jsonb_attribute, fields_map, jsonb_attribute_scope_name)
|
76
|
-
__create_jsonb_standard_scopes(fields_map, jsonb_attribute_scope_name)
|
77
|
-
__create_jsonb_typed_scopes(jsonb_attribute, fields_map)
|
78
|
-
end
|
79
|
-
|
80
|
-
def __create_jsonb_standard_scopes(fields_map, jsonb_attribute_scope_name)
|
81
|
-
fields_map.names.each do |field|
|
82
|
-
scope "with_#{field}", -> (value) { send(jsonb_attribute_scope_name, field => value) }
|
83
|
-
end
|
84
|
-
end
|
85
|
-
|
86
|
-
def __create_jsonb_typed_scopes(jsonb_attribute, fields_map)
|
87
|
-
fields_map.typed_fields.each do |field, type|
|
88
|
-
case type
|
89
|
-
when :boolean
|
90
|
-
___create_jsonb_boolean_scopes(field)
|
91
|
-
when :integer, :float, :decimal, :big_integer
|
92
|
-
___create_jsonb_numeric_scopes(field, jsonb_attribute, type)
|
93
|
-
when :date_time, :date
|
94
|
-
___create_jsonb_date_time_scopes(field, jsonb_attribute, type)
|
95
|
-
when /array/
|
96
|
-
___create_jsonb_array_scopes(field)
|
97
25
|
end
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def ___create_jsonb_boolean_scopes(field)
|
102
|
-
scope "is_#{field}", -> { send("with_#{field}", true) }
|
103
|
-
scope "not_#{field}", -> { send("with_#{field}", false) }
|
104
|
-
end
|
105
|
-
|
106
|
-
def ___create_jsonb_numeric_scopes(field, jsonb_attribute, type)
|
107
|
-
safe_type = type.to_s.gsub("big_", "")
|
108
|
-
scope "__numeric_#{field}_comparator", -> (value, operator) { where("((#{table_name}.#{jsonb_attribute}) ->> ?)::#{safe_type} #{operator} ?", field, value) }
|
109
|
-
scope "#{field}_lt", -> (value) { send("__numeric_#{field}_comparator", value, "<") }
|
110
|
-
scope "#{field}_lte", -> (value) { send("__numeric_#{field}_comparator", value, "<=") }
|
111
|
-
scope "#{field}_gte", -> (value) { send("__numeric_#{field}_comparator", value, ">=") }
|
112
|
-
scope "#{field}_gt", -> (value) { send("__numeric_#{field}_comparator", value, ">") }
|
113
|
-
end
|
114
|
-
|
115
|
-
def ___create_jsonb_date_time_scopes(field, jsonb_attribute, type)
|
116
|
-
scope "__date_time_#{field}_comparator", -> (value, operator) { where("((#{table_name}.#{jsonb_attribute}) ->> ?)::timestamp #{operator} ?::timestamp", field, value.to_json) }
|
117
|
-
scope "#{field}_before", -> (value) { send("__date_time_#{field}_comparator", value, "<") }
|
118
|
-
scope "#{field}_after", -> (value) { send("__date_time_#{field}_comparator", value, ">") }
|
119
|
-
end
|
120
26
|
|
121
|
-
|
122
|
-
scope "#{field}_contains", -> (value) { send("with_#{field}", [value]) }
|
123
|
-
end
|
124
|
-
|
125
|
-
def _create_jsonb_accessor_methods(jsonb_attribute, jsonb_attribute_initialization_method_name, fields_map)
|
126
|
-
jsonb_accessor_methods = Module.new do
|
27
|
+
# Overrides the jsonb attribute setter to make sure the jsonb fields are kept in sync.
|
127
28
|
define_method("#{jsonb_attribute}=") do |value|
|
128
|
-
|
129
|
-
|
130
|
-
|
29
|
+
indifferent_value = value.try(:with_indifferent_access) || {}
|
30
|
+
value_with_store_keys = names_and_store_keys.each_with_object({}) do |(name, store_key), new_value|
|
31
|
+
new_value[store_key] = indifferent_value[name]
|
32
|
+
end
|
131
33
|
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
34
|
+
super(value_with_store_keys)
|
35
|
+
|
36
|
+
new_attributes = field_names.each_with_object({}) { |name, defaults| defaults[name] = nil }.merge(value || {})
|
37
|
+
new_attributes.each { |name, new_value| write_attribute(name, new_value) }
|
136
38
|
end
|
137
39
|
end
|
40
|
+
include setters
|
138
41
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
fields_map.typed_fields.each do |field, type|
|
146
|
-
attribute(field.to_s, TypeHelper.fetch(type))
|
147
|
-
|
148
|
-
jsonb_accessor_methods.instance_eval do
|
149
|
-
define_method("#{field}=") do |value, *args, &block|
|
150
|
-
super(value, *args, &block)
|
151
|
-
new_jsonb_value = (send(jsonb_attribute) || {}).merge(field => attributes[field.to_s])
|
152
|
-
write_attribute(jsonb_attribute, new_jsonb_value)
|
153
|
-
end
|
42
|
+
# Makes sure new objects have the appropriate values in their jsonb fields.
|
43
|
+
after_initialize do
|
44
|
+
jsonb_values = public_send(jsonb_attribute) || {}
|
45
|
+
jsonb_values.each do |store_key, value|
|
46
|
+
name = names_and_store_keys.key(store_key)
|
47
|
+
write_attribute(name, value)
|
154
48
|
end
|
49
|
+
clear_changes_information if persisted?
|
155
50
|
end
|
156
|
-
end
|
157
51
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
instance_class = send("#{jsonb_attribute}_classes")[field]
|
164
|
-
instance = cast_nested_field_value(value, instance_class, __method__)
|
165
|
-
|
166
|
-
new_jsonb_value = (send(jsonb_attribute) || {}).merge(field.to_s => instance.attributes)
|
167
|
-
write_attribute(jsonb_attribute, new_jsonb_value)
|
168
|
-
super(instance)
|
169
|
-
end
|
52
|
+
# <jsonb_attribute>_where scope
|
53
|
+
scope("#{jsonb_attribute}_where", lambda do |attributes|
|
54
|
+
store_key_attributes = attributes.each_with_object({}) do |(name, value), new_attributes|
|
55
|
+
store_key = names_and_store_keys[name.to_s]
|
56
|
+
new_attributes[store_key] = value
|
170
57
|
end
|
171
|
-
|
58
|
+
jsonb_where(jsonb_attribute, store_key_attributes)
|
59
|
+
end)
|
172
60
|
end
|
173
61
|
end
|
174
62
|
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module JsonbAccessor
|
3
|
+
GREATER_THAN = ">"
|
4
|
+
GREATER_THAN_OR_EQUAL_TO = ">="
|
5
|
+
LESS_THAN = "<"
|
6
|
+
LESS_THAN_OR_EQUAL_TO = "<="
|
7
|
+
|
8
|
+
NUMBER_OPERATORS_MAP = {
|
9
|
+
GREATER_THAN => GREATER_THAN,
|
10
|
+
"greater_than" => GREATER_THAN,
|
11
|
+
"gt" => GREATER_THAN,
|
12
|
+
GREATER_THAN_OR_EQUAL_TO => GREATER_THAN_OR_EQUAL_TO,
|
13
|
+
"greater_than_or_equal_to" => GREATER_THAN_OR_EQUAL_TO,
|
14
|
+
"gte" => GREATER_THAN_OR_EQUAL_TO,
|
15
|
+
LESS_THAN => LESS_THAN,
|
16
|
+
"less_than" => LESS_THAN,
|
17
|
+
"lt" => LESS_THAN,
|
18
|
+
LESS_THAN_OR_EQUAL_TO => LESS_THAN_OR_EQUAL_TO,
|
19
|
+
"less_than_or_equal_to" => LESS_THAN_OR_EQUAL_TO,
|
20
|
+
"lte" => LESS_THAN_OR_EQUAL_TO
|
21
|
+
}.freeze
|
22
|
+
|
23
|
+
NUMBER_OPERATORS = NUMBER_OPERATORS_MAP.keys.freeze
|
24
|
+
|
25
|
+
TIME_OPERATORS_MAP = {
|
26
|
+
"after" => GREATER_THAN,
|
27
|
+
"before" => LESS_THAN
|
28
|
+
}.freeze
|
29
|
+
|
30
|
+
TIME_OPERATORS = TIME_OPERATORS_MAP.keys.freeze
|
31
|
+
|
32
|
+
IS_NUMBER_QUERY_ARGUMENTS = lambda do |arg|
|
33
|
+
arg.is_a?(Hash) &&
|
34
|
+
arg.keys.map(&:to_s).all? { |key| JsonbAccessor::NUMBER_OPERATORS.include?(key) }
|
35
|
+
end
|
36
|
+
|
37
|
+
IS_TIME_QUERY_ARGUMENTS = lambda do |arg|
|
38
|
+
arg.is_a?(Hash) &&
|
39
|
+
arg.keys.map(&:to_s).all? { |key| JsonbAccessor::TIME_OPERATORS.include?(key) }
|
40
|
+
end
|
41
|
+
|
42
|
+
module QueryBuilder
|
43
|
+
extend ActiveSupport::Concern
|
44
|
+
|
45
|
+
included do
|
46
|
+
scope(:jsonb_contains,
|
47
|
+
-> (column_name, attributes) { where("#{table_name}.#{column_name} @> (?)::jsonb", attributes.to_json) })
|
48
|
+
|
49
|
+
scope(:jsonb_number_query, lambda do |column_name, field_name, given_operator, value|
|
50
|
+
operator = JsonbAccessor::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
|
51
|
+
where("(#{table_name}.#{column_name} ->> ?)::float #{operator} ?", field_name, value)
|
52
|
+
end)
|
53
|
+
|
54
|
+
scope(:jsonb_time_query, lambda do |column_name, field_name, given_operator, value|
|
55
|
+
operator = JsonbAccessor::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
|
56
|
+
where("(#{table_name}.#{column_name} ->> ?)::timestamp #{operator} ?", field_name, value)
|
57
|
+
end)
|
58
|
+
|
59
|
+
scope(:jsonb_where, lambda do |column_name, attributes|
|
60
|
+
query = all
|
61
|
+
contains_attributes = {}
|
62
|
+
|
63
|
+
attributes.each do |name, value|
|
64
|
+
case value
|
65
|
+
when IS_NUMBER_QUERY_ARGUMENTS
|
66
|
+
value.each { |operator, query_value| query = query.jsonb_number_query(column_name, name, operator, query_value) }
|
67
|
+
when IS_TIME_QUERY_ARGUMENTS
|
68
|
+
value.each { |operator, query_value| query = query.jsonb_time_query(column_name, name, operator, query_value) }
|
69
|
+
else
|
70
|
+
contains_attributes[name] = value
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
query.jsonb_contains(column_name, contains_attributes)
|
75
|
+
end)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/jsonb_accessor.rb
CHANGED
@@ -1,14 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
require "active_record"
|
2
3
|
|
3
4
|
require "active_record/connection_adapters/postgresql_adapter"
|
4
5
|
|
5
6
|
require "jsonb_accessor/version"
|
6
|
-
require "jsonb_accessor/fields_map"
|
7
|
-
require "jsonb_accessor/helpers"
|
8
|
-
require "jsonb_accessor/type_helper"
|
9
|
-
require "jsonb_accessor/nested_base"
|
10
|
-
require "jsonb_accessor/class_builder"
|
11
7
|
require "jsonb_accessor/macro"
|
8
|
+
require "jsonb_accessor/query_builder"
|
12
9
|
|
13
10
|
module JsonbAccessor
|
14
11
|
extend ActiveSupport::Concern
|
@@ -17,5 +14,5 @@ end
|
|
17
14
|
|
18
15
|
ActiveSupport.on_load(:active_record) do
|
19
16
|
ActiveRecord::Base.send(:include, JsonbAccessor)
|
20
|
-
ActiveRecord::Base.send(:include, JsonbAccessor::
|
17
|
+
ActiveRecord::Base.send(:include, JsonbAccessor::QueryBuilder)
|
21
18
|
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: 0.
|
4
|
+
version: 1.0.0.beta
|
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-
|
13
|
+
date: 2016-10-18 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activerecord
|
@@ -18,14 +18,14 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - ">="
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version:
|
21
|
+
version: 5.0.0
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - ">="
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version:
|
28
|
+
version: 5.0.0
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: pg
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
@@ -40,20 +40,6 @@ dependencies:
|
|
40
40
|
- - ">="
|
41
41
|
- !ruby/object:Gem::Version
|
42
42
|
version: 0.18.1
|
43
|
-
- !ruby/object:Gem::Dependency
|
44
|
-
name: actionpack
|
45
|
-
requirement: !ruby/object:Gem::Requirement
|
46
|
-
requirements:
|
47
|
-
- - ">="
|
48
|
-
- !ruby/object:Gem::Version
|
49
|
-
version: '0'
|
50
|
-
type: :development
|
51
|
-
prerelease: false
|
52
|
-
version_requirements: !ruby/object:Gem::Requirement
|
53
|
-
requirements:
|
54
|
-
- - ">="
|
55
|
-
- !ruby/object:Gem::Version
|
56
|
-
version: '0'
|
57
43
|
- !ruby/object:Gem::Dependency
|
58
44
|
name: appraisal
|
59
45
|
requirement: !ruby/object:Gem::Requirement
|
@@ -172,28 +158,28 @@ dependencies:
|
|
172
158
|
requirements:
|
173
159
|
- - "~>"
|
174
160
|
- !ruby/object:Gem::Version
|
175
|
-
version: 3.2
|
161
|
+
version: '3.2'
|
176
162
|
type: :development
|
177
163
|
prerelease: false
|
178
164
|
version_requirements: !ruby/object:Gem::Requirement
|
179
165
|
requirements:
|
180
166
|
- - "~>"
|
181
167
|
- !ruby/object:Gem::Version
|
182
|
-
version: 3.2
|
168
|
+
version: '3.2'
|
183
169
|
- !ruby/object:Gem::Dependency
|
184
170
|
name: rubocop
|
185
171
|
requirement: !ruby/object:Gem::Requirement
|
186
172
|
requirements:
|
187
|
-
- -
|
173
|
+
- - "~>"
|
188
174
|
- !ruby/object:Gem::Version
|
189
|
-
version: 0.31
|
175
|
+
version: '0.31'
|
190
176
|
type: :development
|
191
177
|
prerelease: false
|
192
178
|
version_requirements: !ruby/object:Gem::Requirement
|
193
179
|
requirements:
|
194
|
-
- -
|
180
|
+
- - "~>"
|
195
181
|
- !ruby/object:Gem::Version
|
196
|
-
version: 0.31
|
182
|
+
version: '0.31'
|
197
183
|
- !ruby/object:Gem::Dependency
|
198
184
|
name: shoulda-matchers
|
199
185
|
requirement: !ruby/object:Gem::Requirement
|
@@ -247,18 +233,12 @@ files:
|
|
247
233
|
- db/config.yml
|
248
234
|
- db/migrate/20150407031737_set_up_testing_db.rb
|
249
235
|
- db/schema.rb
|
250
|
-
- gemfiles/activerecord_4.2.4.gemfile
|
251
|
-
- gemfiles/activerecord_4.2.4.gemfile.lock
|
252
236
|
- gemfiles/activerecord_5.0.0.gemfile
|
253
237
|
- gemfiles/activerecord_5.0.0.gemfile.lock
|
254
238
|
- jsonb_accessor.gemspec
|
255
239
|
- lib/jsonb_accessor.rb
|
256
|
-
- lib/jsonb_accessor/class_builder.rb
|
257
|
-
- lib/jsonb_accessor/fields_map.rb
|
258
|
-
- lib/jsonb_accessor/helpers.rb
|
259
240
|
- lib/jsonb_accessor/macro.rb
|
260
|
-
- lib/jsonb_accessor/
|
261
|
-
- lib/jsonb_accessor/type_helper.rb
|
241
|
+
- lib/jsonb_accessor/query_builder.rb
|
262
242
|
- lib/jsonb_accessor/version.rb
|
263
243
|
homepage: https://github.com/devmynd/jsonb_accessor
|
264
244
|
licenses:
|
@@ -1,146 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: ../
|
3
|
-
specs:
|
4
|
-
jsonb_accessor (0.4.0.beta)
|
5
|
-
activerecord (>= 4.2.1)
|
6
|
-
pg (>= 0.18.1)
|
7
|
-
|
8
|
-
GEM
|
9
|
-
remote: https://rubygems.org/
|
10
|
-
specs:
|
11
|
-
actionpack (4.2.4)
|
12
|
-
actionview (= 4.2.4)
|
13
|
-
activesupport (= 4.2.4)
|
14
|
-
rack (~> 1.6)
|
15
|
-
rack-test (~> 0.6.2)
|
16
|
-
rails-dom-testing (~> 1.0, >= 1.0.5)
|
17
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
18
|
-
actionview (4.2.4)
|
19
|
-
activesupport (= 4.2.4)
|
20
|
-
builder (~> 3.1)
|
21
|
-
erubis (~> 2.7.0)
|
22
|
-
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
-
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
24
|
-
activemodel (4.2.4)
|
25
|
-
activesupport (= 4.2.4)
|
26
|
-
builder (~> 3.1)
|
27
|
-
activerecord (4.2.4)
|
28
|
-
activemodel (= 4.2.4)
|
29
|
-
activesupport (= 4.2.4)
|
30
|
-
arel (~> 6.0)
|
31
|
-
activesupport (4.2.4)
|
32
|
-
i18n (~> 0.7)
|
33
|
-
json (~> 1.7, >= 1.7.7)
|
34
|
-
minitest (~> 5.1)
|
35
|
-
thread_safe (~> 0.3, >= 0.3.4)
|
36
|
-
tzinfo (~> 1.1)
|
37
|
-
appraisal (2.1.0)
|
38
|
-
bundler
|
39
|
-
rake
|
40
|
-
thor (>= 0.14.0)
|
41
|
-
arel (6.0.3)
|
42
|
-
ast (2.1.0)
|
43
|
-
astrolabe (1.3.1)
|
44
|
-
parser (~> 2.2)
|
45
|
-
awesome_print (1.6.1)
|
46
|
-
builder (3.2.2)
|
47
|
-
coderay (1.1.0)
|
48
|
-
database_cleaner (1.5.1)
|
49
|
-
diff-lcs (1.2.5)
|
50
|
-
erubis (2.7.0)
|
51
|
-
i18n (0.7.0)
|
52
|
-
json (1.8.3)
|
53
|
-
loofah (2.0.3)
|
54
|
-
nokogiri (>= 1.5.9)
|
55
|
-
method_source (0.8.2)
|
56
|
-
mini_portile (0.6.2)
|
57
|
-
minitest (5.8.1)
|
58
|
-
nokogiri (1.6.6.2)
|
59
|
-
mini_portile (~> 0.6.0)
|
60
|
-
parser (2.2.3.0)
|
61
|
-
ast (>= 1.1, < 3.0)
|
62
|
-
pg (0.18.4)
|
63
|
-
powerpack (0.1.1)
|
64
|
-
pry (0.10.3)
|
65
|
-
coderay (~> 1.1.0)
|
66
|
-
method_source (~> 0.8.1)
|
67
|
-
slop (~> 3.4)
|
68
|
-
pry-doc (0.8.0)
|
69
|
-
pry (~> 0.9)
|
70
|
-
yard (~> 0.8)
|
71
|
-
pry-nav (0.2.4)
|
72
|
-
pry (>= 0.9.10, < 0.11.0)
|
73
|
-
rack (1.6.4)
|
74
|
-
rack-test (0.6.3)
|
75
|
-
rack (>= 1.0)
|
76
|
-
rails-deprecated_sanitizer (1.0.3)
|
77
|
-
activesupport (>= 4.2.0.alpha)
|
78
|
-
rails-dom-testing (1.0.7)
|
79
|
-
activesupport (>= 4.2.0.beta, < 5.0)
|
80
|
-
nokogiri (~> 1.6.0)
|
81
|
-
rails-deprecated_sanitizer (>= 1.0.1)
|
82
|
-
rails-html-sanitizer (1.0.2)
|
83
|
-
loofah (~> 2.0)
|
84
|
-
railties (4.2.4)
|
85
|
-
actionpack (= 4.2.4)
|
86
|
-
activesupport (= 4.2.4)
|
87
|
-
rake (>= 0.8.7)
|
88
|
-
thor (>= 0.18.1, < 2.0)
|
89
|
-
rainbow (2.0.0)
|
90
|
-
rake (10.4.2)
|
91
|
-
rspec (3.2.0)
|
92
|
-
rspec-core (~> 3.2.0)
|
93
|
-
rspec-expectations (~> 3.2.0)
|
94
|
-
rspec-mocks (~> 3.2.0)
|
95
|
-
rspec-core (3.2.3)
|
96
|
-
rspec-support (~> 3.2.0)
|
97
|
-
rspec-expectations (3.2.1)
|
98
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
99
|
-
rspec-support (~> 3.2.0)
|
100
|
-
rspec-mocks (3.2.1)
|
101
|
-
diff-lcs (>= 1.2.0, < 2.0)
|
102
|
-
rspec-support (~> 3.2.0)
|
103
|
-
rspec-support (3.2.2)
|
104
|
-
rubocop (0.31.0)
|
105
|
-
astrolabe (~> 1.3)
|
106
|
-
parser (>= 2.2.2.1, < 3.0)
|
107
|
-
powerpack (~> 0.1)
|
108
|
-
rainbow (>= 1.99.1, < 3.0)
|
109
|
-
ruby-progressbar (~> 1.4)
|
110
|
-
ruby-progressbar (1.7.5)
|
111
|
-
shoulda-matchers (3.0.0)
|
112
|
-
activesupport (>= 4.0.0)
|
113
|
-
slop (3.6.0)
|
114
|
-
standalone_migrations (4.0.2)
|
115
|
-
activerecord (~> 4.2.0)
|
116
|
-
railties (~> 4.2.0)
|
117
|
-
rake (~> 10.0)
|
118
|
-
thor (0.19.1)
|
119
|
-
thread_safe (0.3.5)
|
120
|
-
tzinfo (1.2.2)
|
121
|
-
thread_safe (~> 0.1)
|
122
|
-
yard (0.8.7.6)
|
123
|
-
|
124
|
-
PLATFORMS
|
125
|
-
ruby
|
126
|
-
|
127
|
-
DEPENDENCIES
|
128
|
-
actionpack
|
129
|
-
activerecord (= 4.2.4)
|
130
|
-
appraisal
|
131
|
-
awesome_print
|
132
|
-
bundler (~> 1.9)
|
133
|
-
database_cleaner
|
134
|
-
jsonb_accessor!
|
135
|
-
pg
|
136
|
-
pry
|
137
|
-
pry-doc
|
138
|
-
pry-nav
|
139
|
-
rake (~> 10.0)
|
140
|
-
rspec (~> 3.2.0)
|
141
|
-
rubocop (= 0.31.0)
|
142
|
-
shoulda-matchers
|
143
|
-
standalone_migrations
|
144
|
-
|
145
|
-
BUNDLED WITH
|
146
|
-
1.11.2
|
@@ -1,101 +0,0 @@
|
|
1
|
-
module JsonbAccessor
|
2
|
-
UnknownValue = Class.new(StandardError)
|
3
|
-
CLASS_PREFIX = "JA"
|
4
|
-
|
5
|
-
module ClassBuilder
|
6
|
-
class << self
|
7
|
-
def generate_class(namespace, new_class_name, attribute_definitions)
|
8
|
-
fields_map = JsonbAccessor::FieldsMap.new([], attribute_definitions)
|
9
|
-
klass = generate_new_class(new_class_name, fields_map, namespace)
|
10
|
-
nested_classes = generate_nested_classes(klass, fields_map.nested_fields)
|
11
|
-
|
12
|
-
define_class_methods(klass, nested_classes, new_class_name)
|
13
|
-
define_attributes_and_data_types(klass, fields_map)
|
14
|
-
define_typed_accessors(klass, fields_map)
|
15
|
-
define_nested_accessors(klass, fields_map)
|
16
|
-
|
17
|
-
klass
|
18
|
-
end
|
19
|
-
|
20
|
-
def generate_nested_classes(klass, nested_attributes)
|
21
|
-
nested_attributes.each_with_object({}) do |(attribute_name, nested_attrs), nested_classes|
|
22
|
-
nested_classes[attribute_name] = generate_class(klass, attribute_name, nested_attrs)
|
23
|
-
end
|
24
|
-
end
|
25
|
-
|
26
|
-
def generate_class_namespace(name)
|
27
|
-
class_name = CLASS_PREFIX + name.gsub(CONSTANT_SEPARATOR, "")
|
28
|
-
if JsonbAccessor.constants.any? { |c| c.to_s == class_name }
|
29
|
-
class_namespace = JsonbAccessor.const_get(class_name)
|
30
|
-
else
|
31
|
-
class_namespace = Module.new
|
32
|
-
JsonbAccessor.const_set(class_name, class_namespace)
|
33
|
-
end
|
34
|
-
class_namespace
|
35
|
-
end
|
36
|
-
|
37
|
-
def generate_attribute_namespace(attribute_name, class_namespace)
|
38
|
-
attribute_namespace = Module.new
|
39
|
-
name = generate_constant_name(attribute_name)
|
40
|
-
class_namespace.const_set(name, attribute_namespace)
|
41
|
-
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
def define_nested_accessors(klass, fields_map)
|
46
|
-
klass.class_eval do
|
47
|
-
fields_map.nested_fields.keys.each do |attribute_name|
|
48
|
-
attr_reader attribute_name
|
49
|
-
|
50
|
-
define_method("#{attribute_name}=") do |value|
|
51
|
-
instance_class = nested_classes[attribute_name]
|
52
|
-
instance = cast_nested_field_value(value, instance_class, __method__)
|
53
|
-
|
54
|
-
instance_variable_set("@#{attribute_name}", instance)
|
55
|
-
attributes[attribute_name] = instance.attributes
|
56
|
-
update_parent
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
def define_typed_accessors(klass, fields_map)
|
63
|
-
klass.class_eval do
|
64
|
-
fields_map.typed_fields.keys.each do |attribute_name|
|
65
|
-
define_method(attribute_name) { attributes[attribute_name] }
|
66
|
-
|
67
|
-
cast_method_name = ActiveRecord::VERSION::MAJOR == 5 ? :cast : :type_cast_from_user
|
68
|
-
define_method("#{attribute_name}=") do |value|
|
69
|
-
cast_value = attributes_and_data_types[attribute_name].public_send(cast_method_name, value)
|
70
|
-
attributes[attribute_name] = cast_value
|
71
|
-
update_parent
|
72
|
-
end
|
73
|
-
end
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def define_attributes_and_data_types(klass, fields_map)
|
78
|
-
klass.send(:define_method, :attributes_and_data_types) do
|
79
|
-
@attributes_and_data_types ||= fields_map.typed_fields.each_with_object({}) do |(name, type), attrs_and_data_types|
|
80
|
-
attrs_and_data_types[name] = TypeHelper.fetch(type)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
def define_class_methods(klass, nested_classes, attribute_name)
|
86
|
-
klass.singleton_class.send(:define_method, :nested_classes) { nested_classes }
|
87
|
-
klass.singleton_class.send(:define_method, :attribute_on_parent_name) { attribute_name }
|
88
|
-
end
|
89
|
-
|
90
|
-
def generate_new_class(new_class_name, fields_map, namespace)
|
91
|
-
klass = Class.new(NestedBase)
|
92
|
-
new_class_name_camelized = generate_constant_name(new_class_name)
|
93
|
-
namespace.const_set(new_class_name_camelized, klass)
|
94
|
-
end
|
95
|
-
|
96
|
-
def generate_constant_name(attribute_name)
|
97
|
-
"#{CLASS_PREFIX}#{attribute_name.to_s.camelize}"
|
98
|
-
end
|
99
|
-
end
|
100
|
-
end
|
101
|
-
end
|
@@ -1,32 +0,0 @@
|
|
1
|
-
module JsonbAccessor
|
2
|
-
class FieldsMap
|
3
|
-
attr_accessor :nested_fields, :typed_fields
|
4
|
-
|
5
|
-
def initialize(value_fields, typed_and_nested_fields)
|
6
|
-
grouped_fields = extract_typed_and_nested_fields(typed_and_nested_fields)
|
7
|
-
nested_fields, typed_fields = grouped_fields.values_at(:nested, :typed)
|
8
|
-
|
9
|
-
self.typed_fields = implicitly_typed_fields(value_fields).merge(typed_fields)
|
10
|
-
self.nested_fields = nested_fields
|
11
|
-
end
|
12
|
-
|
13
|
-
def names
|
14
|
-
typed_fields.keys + nested_fields.keys
|
15
|
-
end
|
16
|
-
|
17
|
-
private
|
18
|
-
|
19
|
-
def implicitly_typed_fields(value_fields)
|
20
|
-
value_fields.each_with_object({}) do |field_name, implicitly_typed_fields|
|
21
|
-
implicitly_typed_fields[field_name] = :value
|
22
|
-
end
|
23
|
-
end
|
24
|
-
|
25
|
-
def extract_typed_and_nested_fields(typed_and_nested_fields)
|
26
|
-
typed_and_nested_fields.each_with_object(nested: {}, typed: {}) do |(attribute_name, type_or_nested), grouped_attributes|
|
27
|
-
group = type_or_nested.is_a?(Hash) ? grouped_attributes[:nested] : grouped_attributes[:typed]
|
28
|
-
group[attribute_name] = type_or_nested
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
module JsonbAccessor
|
2
|
-
module Helpers
|
3
|
-
def cast_nested_field_value(value, klass, method_name)
|
4
|
-
case value
|
5
|
-
when klass
|
6
|
-
instance = klass.new(value.attributes)
|
7
|
-
when Hash
|
8
|
-
instance = klass.new(value)
|
9
|
-
when nil
|
10
|
-
instance = klass.new
|
11
|
-
else
|
12
|
-
raise UnknownValue, "unable to set value '#{value}' is not a hash, `nil`, or an instance of #{klass} in #{method_name}"
|
13
|
-
end
|
14
|
-
|
15
|
-
instance.parent = self
|
16
|
-
instance
|
17
|
-
end
|
18
|
-
end
|
19
|
-
end
|
@@ -1,35 +0,0 @@
|
|
1
|
-
module JsonbAccessor
|
2
|
-
class NestedBase
|
3
|
-
include Helpers
|
4
|
-
|
5
|
-
attr_accessor :attributes, :parent
|
6
|
-
alias_method :to_h, :attributes
|
7
|
-
|
8
|
-
delegate :[], to: :attributes
|
9
|
-
delegate :nested_classes, :attribute_on_parent_name, to: :class
|
10
|
-
|
11
|
-
def initialize(attributes = {})
|
12
|
-
self.attributes = {}.with_indifferent_access
|
13
|
-
|
14
|
-
nested_classes.keys.each do |key|
|
15
|
-
send("#{key}=", nil)
|
16
|
-
end
|
17
|
-
|
18
|
-
attributes.each do |name, value|
|
19
|
-
send("#{name}=", value)
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
def update_parent
|
24
|
-
parent.send("#{attribute_on_parent_name}=", self) if parent
|
25
|
-
end
|
26
|
-
|
27
|
-
def []=(key, value)
|
28
|
-
send("#{key}=", value)
|
29
|
-
end
|
30
|
-
|
31
|
-
def ==(suspect)
|
32
|
-
self.class == suspect.class && attributes == suspect.attributes
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
module JsonbAccessor
|
2
|
-
CONSTANT_SEPARATOR = "::"
|
3
|
-
module TypeHelper
|
4
|
-
ARRAY_MATCHER = /_array\z/
|
5
|
-
UnknownType = Class.new(StandardError)
|
6
|
-
|
7
|
-
class << self
|
8
|
-
def fetch(type)
|
9
|
-
case type
|
10
|
-
when :array
|
11
|
-
new_array(value)
|
12
|
-
when ARRAY_MATCHER
|
13
|
-
fetch_active_record_array_type(type)
|
14
|
-
when :value
|
15
|
-
value
|
16
|
-
else
|
17
|
-
fetch_active_record_type(type)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def type_cast_as_jsonb(suspect)
|
22
|
-
user_cast_method_name, db_case_method_name = ActiveRecord::VERSION::MAJOR == 5 ? [:cast, :serialize] : [:type_cast_from_user, :type_cast_for_database]
|
23
|
-
type_cast_hash = jsonb.public_send(user_cast_method_name, suspect)
|
24
|
-
jsonb.public_send(db_case_method_name, type_cast_hash)
|
25
|
-
end
|
26
|
-
|
27
|
-
private
|
28
|
-
|
29
|
-
def jsonb
|
30
|
-
@jsonb ||= fetch(:jsonb)
|
31
|
-
end
|
32
|
-
|
33
|
-
def fetch_active_record_array_type(type)
|
34
|
-
subtype = type.to_s.sub(ARRAY_MATCHER, "")
|
35
|
-
new_array(fetch_active_record_type(subtype))
|
36
|
-
end
|
37
|
-
|
38
|
-
def fetch_active_record_type(type)
|
39
|
-
class_name = type.to_s.camelize
|
40
|
-
klass = value_descendants.find do |ar_type|
|
41
|
-
ar_type.to_s.split(CONSTANT_SEPARATOR).last == class_name
|
42
|
-
end
|
43
|
-
|
44
|
-
if klass
|
45
|
-
klass.new
|
46
|
-
else
|
47
|
-
raise JsonbAccessor::TypeHelper::UnknownType
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
def value_descendants
|
52
|
-
grouped_types = ActiveRecord::Type::Value.descendants.group_by do |ar_type|
|
53
|
-
!!ar_type.to_s.match(ActiveRecord::ConnectionAdapters::PostgreSQL::OID.to_s)
|
54
|
-
end
|
55
|
-
|
56
|
-
grouped_types[true] + grouped_types[false]
|
57
|
-
end
|
58
|
-
|
59
|
-
def value
|
60
|
-
ActiveRecord::Type::Value.new
|
61
|
-
end
|
62
|
-
|
63
|
-
def new_array(subtype)
|
64
|
-
ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Array.new(subtype)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|