jsonb_accessor 1.0.0.beta.4 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9342c69dc4817b5f014149d149fd8fb92ad650ff
4
- data.tar.gz: d3b5f27e415c190f63b9275cc28c648fc2510023
3
+ metadata.gz: 4572af0f905157b8a8540b7c4fac70426908926c
4
+ data.tar.gz: 7fc82fc4c45831589b5de1d041cab1e651783447
5
5
  SHA512:
6
- metadata.gz: 2b2e8d7ffbc17b5b8455c8e95f686a1ad24a2f3cbd7f6166d7bcf2300922316221248617dd021fb0d3f59db45f5b9bb4ff912540c4876f0b3a92484d688fc29a
7
- data.tar.gz: 6e1453d01c791b69cf5ec9c0f525819bc8eed173fdbb4f914c8e190f7a5f6b7ae0ad8e966707a023299d69f2e33df1cb0af74b3ef7374053127cbe8c430a14dd
6
+ metadata.gz: bb272c8509fce56497c5863feb5879428cab9d90e42243c791caf14928e11a40f1a3c07af10574028be601874f49a26560354c85d4f561b5fb22d9258e79e944
7
+ data.tar.gz: c053957f581dbcb37aa77c8c7cdce51193ec073927abaa43832e9ad4330a8d8d60de45ab671cf4a48dacf24a4dddbc576b743abc02c6019979bd5901257f4556
data/.gitignore CHANGED
@@ -7,3 +7,5 @@
7
7
  /pkg/
8
8
  /spec/reports/
9
9
  /tmp/
10
+ gemfiles/.bundle
11
+ gemfiles/*.gemfile.lock
@@ -5,7 +5,7 @@ AllCops:
5
5
  - db/**/*
6
6
  - gemfiles/**/*
7
7
  - vendor/**/*
8
- Lint/SpaceBeforeFirstArg:
8
+ Style/SpaceBeforeFirstArg:
9
9
  Enabled: false
10
10
  Lint/UnusedBlockArgument:
11
11
  Enabled: false
@@ -59,3 +59,5 @@ Style/SpaceAroundEqualsInParameterDefault:
59
59
  Enabled: false
60
60
  Style/StringLiterals:
61
61
  EnforcedStyle: double_quotes
62
+ Performance/EndWith:
63
+ Enabled: false
@@ -1 +1 @@
1
- 2.3.1
1
+ 2.4.1
@@ -1,7 +1,9 @@
1
1
  language: ruby
2
2
  rvm:
3
- - 2.2.5
4
- - 2.3.1
3
+ - 2.3.8
4
+ - 2.4.5
5
+ - 2.5.3
6
+ - 2.6.1
5
7
  addons:
6
8
  postgresql: '9.4'
7
9
  before_install:
@@ -14,3 +16,4 @@ before_script:
14
16
  cache: bundler
15
17
  gemfile:
16
18
  - gemfiles/activerecord_5.0.0.gemfile
19
+ - gemfiles/activerecord_5.1.0.gemfile
data/Appraisals CHANGED
@@ -1,4 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  appraise "activerecord-5.0.0" do
2
- gemspec
3
4
  gem "activerecord", "~> 5.0.0"
4
5
  end
6
+
7
+ appraise "activerecord-5.1.0" do
8
+ gem "activerecord", "~> 5.1.3"
9
+ end
data/Gemfile CHANGED
@@ -1,7 +1,9 @@
1
- source 'https://rubygems.org'
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
2
4
 
3
5
  # Specify your gem's dependencies in jsonb_accessor.gemspec
4
6
  gemspec
5
7
 
6
8
  # Travis CI requires in Gemfile
7
- gem 'pg'
9
+ gem "pg"
data/README.md CHANGED
@@ -1,17 +1,13 @@
1
1
  # JSONb Accessor
2
2
 
3
- Created by &nbsp;&nbsp;&nbsp; [<img src="https://raw.githubusercontent.com/devmynd/jsonb_accessor/master/devmynd-logo.png" alt="DevMynd Logo" />](https://www.devmynd.com/)
3
+ Created by &nbsp;&nbsp;&nbsp; [<img src="https://raw.githubusercontent.com/madeintandem/jsonb_accessor/master/tandem-logo.png" alt="Tandem Logo" />](https://www.madeintandem.com/)
4
4
 
5
- [![Gem Version](https://badge.fury.io/rb/jsonb_accessor.svg)](http://badge.fury.io/rb/jsonb_accessor) &nbsp;&nbsp;&nbsp;[![Build Status](https://travis-ci.org/devmynd/jsonb_accessor.svg)](https://travis-ci.org/devmynd/jsonb_accessor) <img src="https://raw.githubusercontent.com/devmynd/jsonb_accessor/master/json-bee.png" alt="JSONb Accessor Logo" align="right" />
5
+ [![Gem Version](https://badge.fury.io/rb/jsonb_accessor.svg)](http://badge.fury.io/rb/jsonb_accessor) &nbsp;&nbsp;&nbsp;[![Build Status](https://travis-ci.org/madeintandem/jsonb_accessor.svg)](https://travis-ci.org/madeintandem/jsonb_accessor) <img src="https://raw.githubusercontent.com/madeintandem/jsonb_accessor/master/json-bee.png" alt="JSONb Accessor Logo" align="right" />
6
6
 
7
- 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.
7
+ Adds typed `jsonb` backed fields as first class citizens to your `ActiveRecord` models. This gem is similar in spirit to [HstoreAccessor](https://github.com/madeintandem/hstore_accessor), but the `jsonb` column in PostgreSQL has a few distinct advantages, mostly around nested documents and support for collections.
8
8
 
9
9
  It also adds generic scopes for querying `jsonb` columns.
10
10
 
11
- ## 1.0 Beta
12
-
13
- This README reflects the most recent 1.0 beta. Method names and interfaces may still change.
14
-
15
11
  ## Table of Contents
16
12
 
17
13
  * [Installation](#installation)
@@ -29,7 +25,7 @@ This README reflects the most recent 1.0 beta. Method names and interfaces may s
29
25
  Add this line to your application's `Gemfile`:
30
26
 
31
27
  ```ruby
32
- gem "jsonb_accessor", "1.0.0.beta.4"
28
+ gem "jsonb_accessor", "~> 1.0.0"
33
29
  ```
34
30
 
35
31
  And then execute:
@@ -141,6 +137,14 @@ For time related fields you can query using `before` and `after`.
141
137
  Product.all.data_where(reviewed_at: { before: Time.current.beginning_of_week, after: 4.weeks.ago })
142
138
  ```
143
139
 
140
+ If you want to search for records within a certain time, date, or number range, just pass in the range (Note: this is just shorthand for the above mentioned `before`/`after`/`less_than`/`less_than_or_equal_to`/`greater_than_or_equal_to`/etc options).
141
+
142
+ ```ruby
143
+ Product.all.data_where(price: 10..20)
144
+ Product.all.data_where(price: 10...20)
145
+ Product.all.data_where(reviewed_at: Time.current..3.days.from_now)
146
+ ```
147
+
144
148
  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.
145
149
 
146
150
  ### `jsonb_where`
@@ -164,6 +168,27 @@ Just the opposite of `jsonb_where`. Note that this will automatically exclude al
164
168
  Product.all.jsonb_where_not(:data, reviewed_at: { before: Time.current }, p: { greater_than: 5 })
165
169
  ```
166
170
 
171
+ ### `<jsonb_attribute>_order`
172
+
173
+ Orders your query according to values in the Jsonb Accessor fields similar to ActiveRecord's `order`.
174
+
175
+ ```ruby
176
+ Product.all.data_order(:price)
177
+ Product.all.data_order(:price, :reviewed_at)
178
+ Product.all.data_order(:price, reviewed_at: :desc)
179
+ ```
180
+
181
+ It will convert your given keys into store keys if necessary.
182
+
183
+ ### `jsonb_order`
184
+
185
+ Allows you to order by a Jsonb Accessor field.
186
+
187
+ ```ruby
188
+ Product.all.jsonb_order(:data, :price, :asc)
189
+ Product.all.jsonb_order(:data, :price, :desc)
190
+ ```
191
+
167
192
  ### `jsonb_contains`
168
193
 
169
194
  Returns all records that contain the given JSON paths.
@@ -284,7 +309,7 @@ From here any attributes specific to any sub-class can be stored in the
284
309
  individual fields in an `jsonb` column.
285
310
 
286
311
  This approach was originally conceived by Joe Hirn in [this blog
287
- post](http://www.devmynd.com/blog/2013-3-single-table-inheritance-hstore-lovely-combination).
312
+ post](https://madeintandem.com/blog/2013-3-single-table-inheritance-hstore-lovely-combination/).
288
313
 
289
314
  ## Validations
290
315
 
@@ -309,7 +334,7 @@ Run `bin/console` for an interactive prompt that will allow you to experiment.
309
334
 
310
335
  ## Contributing
311
336
 
312
- 1. [Fork it](https://github.com/devmynd/jsonb_accessor/fork)
337
+ 1. [Fork it](https://github.com/madeintandem/jsonb_accessor/fork)
313
338
  2. Create your feature branch (`git checkout -b my-new-feature`)
314
339
  3. Add tests and changes (run the tests with `rake`)
315
340
  4. Commit your changes (`git commit -am 'Add some feature'`)
data/Rakefile CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "rubygems"
3
4
  require "bundler/setup"
4
5
  require "bundler/gem_tasks"
@@ -10,4 +11,4 @@ StandaloneMigrations::Tasks.load_tasks
10
11
  RSpec::Core::RakeTask.new
11
12
  RuboCop::RakeTask.new
12
13
 
13
- task(default: [:rubocop, :spec])
14
+ task(default: %i[rubocop spec])
@@ -1,5 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
+
3
4
  # rubocop:disable Lint/UselessAssignment
4
5
 
5
6
  require "bundler/setup"
@@ -15,22 +15,22 @@ ActiveRecord::Schema.define(version: 20150407031737) do
15
15
  # These are extensions that must be enabled in order to support this database
16
16
  enable_extension "plpgsql"
17
17
 
18
- create_table "product_categories", force: :cascade do |t|
18
+ create_table "product_categories", id: :serial, force: :cascade do |t|
19
19
  t.jsonb "options"
20
20
  end
21
21
 
22
- create_table "products", force: :cascade do |t|
23
- t.jsonb "options"
24
- t.jsonb "data"
25
- t.string "string_type"
26
- t.integer "integer_type"
27
- t.integer "product_category_id"
28
- t.boolean "boolean_type"
29
- t.float "float_type"
30
- t.time "time_type"
31
- t.date "date_type"
22
+ create_table "products", id: :serial, force: :cascade do |t|
23
+ t.jsonb "options"
24
+ t.jsonb "data"
25
+ t.string "string_type"
26
+ t.integer "integer_type"
27
+ t.integer "product_category_id"
28
+ t.boolean "boolean_type"
29
+ t.float "float_type"
30
+ t.time "time_type"
31
+ t.date "date_type"
32
32
  t.datetime "datetime_type"
33
- t.decimal "decimal_type"
33
+ t.decimal "decimal_type"
34
34
  end
35
35
 
36
36
  end
@@ -5,4 +5,4 @@ source "https://rubygems.org"
5
5
  gem "pg"
6
6
  gem "activerecord", "~> 5.0.0"
7
7
 
8
- gemspec :path => "../"
8
+ gemspec path: "../"
@@ -0,0 +1,8 @@
1
+ # This file was generated by Appraisal
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "pg"
6
+ gem "activerecord", "~> 5.1.3"
7
+
8
+ gemspec path: "../"
@@ -1,4 +1,6 @@
1
1
  # coding: utf-8
2
+ # frozen_string_literal: true
3
+
2
4
  lib = File.expand_path("../lib", __FILE__)
3
5
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
6
  require "jsonb_accessor/version"
@@ -7,32 +9,32 @@ Gem::Specification.new do |spec|
7
9
  spec.name = "jsonb_accessor"
8
10
  spec.version = JsonbAccessor::VERSION
9
11
  spec.authors = ["Michael Crismali", "Joe Hirn", "Jason Haruska"]
10
- spec.email = ["michael.crismali@gmail.com", "joe@devmynd.com", "jason@haruska.com"]
12
+ spec.email = ["michael@crismali.com", "joe@devmynd.com", "jason@haruska.com"]
11
13
 
12
- spec.summary = %q{Adds typed jsonb backed fields to your ActiveRecord models.}
13
- spec.description = %q{Adds typed jsonb backed fields to your ActiveRecord models.}
14
+ spec.summary = "Adds typed jsonb backed fields to your ActiveRecord models."
15
+ spec.description = "Adds typed jsonb backed fields to your ActiveRecord models."
14
16
  spec.homepage = "https://github.com/devmynd/jsonb_accessor"
15
17
  spec.license = "MIT"
16
18
  spec.required_ruby_version = "~> 2.0"
17
19
 
18
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) || f.match(%r{png\z}) }
20
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) || f.match(/png\z/) }
19
21
  spec.bindir = "exe"
20
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
23
  spec.require_paths = ["lib"]
22
24
 
23
25
  spec.add_dependency "activerecord", ">= 5.0"
26
+ spec.add_dependency "activesupport", ">= 5.0"
24
27
  spec.add_dependency "pg", ">= 0.18.1"
25
28
 
26
- spec.add_development_dependency "appraisal"
27
- spec.add_development_dependency "bundler", "~> 1.9"
28
- spec.add_development_dependency "database_cleaner"
29
+ spec.add_development_dependency "appraisal", "~> 2.2.0"
30
+ spec.add_development_dependency "bundler", "~> 2.1.4"
31
+ spec.add_development_dependency "database_cleaner", "~> 1.6.0"
29
32
  spec.add_development_dependency "awesome_print"
30
33
  spec.add_development_dependency "pry"
31
34
  spec.add_development_dependency "pry-doc"
32
35
  spec.add_development_dependency "pry-nav"
33
- spec.add_development_dependency "rake", "~> 10.0"
34
- spec.add_development_dependency "rspec", "~> 3.2"
35
- spec.add_development_dependency "rubocop", "~> 0.31"
36
- spec.add_development_dependency "shoulda-matchers"
37
- spec.add_development_dependency "standalone_migrations"
36
+ spec.add_development_dependency "rake", ">= 12.3.3"
37
+ spec.add_development_dependency "rspec", "~> 3.6.0"
38
+ spec.add_development_dependency "rubocop", "~> 0.48.1"
39
+ spec.add_development_dependency "standalone_migrations", "~> 5.2.0"
38
40
  end
@@ -1,10 +1,12 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "active_record"
3
4
 
4
5
  require "active_record/connection_adapters/postgresql_adapter"
5
6
 
6
7
  require "jsonb_accessor/version"
7
8
  require "jsonb_accessor/macro"
9
+ require "jsonb_accessor/query_helper"
8
10
  require "jsonb_accessor/query_builder"
9
11
 
10
12
  module JsonbAccessor
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module JsonbAccessor
3
4
  module Macro
4
5
  module ClassMethods
@@ -33,7 +34,7 @@ module JsonbAccessor
33
34
  end
34
35
 
35
36
  # Get store keys to default values mapping
36
- store_keys_and_defaults = ::JsonbAccessor::QueryBuilder.convert_keys_to_store_keys(names_and_defaults, public_send(store_key_mapping_method_name))
37
+ store_keys_and_defaults = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(names_and_defaults, public_send(store_key_mapping_method_name))
37
38
 
38
39
  # Define jsonb_defaults_mapping_for_<jsonb_attribute>
39
40
  defaults_mapping_method_name = "jsonb_defaults_mapping_for_#{jsonb_attribute}"
@@ -45,7 +46,14 @@ module JsonbAccessor
45
46
  end
46
47
 
47
48
  all_defaults_mapping = public_send(defaults_mapping_method_name)
48
- attribute jsonb_attribute, :jsonb, default: all_defaults_mapping if all_defaults_mapping.present?
49
+ # Fields may have procs as default value. This means `all_defaults_mapping` may contain procs as values. To make this work
50
+ # with the attributes API, we need to wrap `all_defaults_mapping` with a proc itself, making sure it returns a plain hash
51
+ # each time it is evaluated.
52
+ all_defaults_mapping_proc =
53
+ if all_defaults_mapping.present?
54
+ -> { all_defaults_mapping.map { |key, value| [key, value.respond_to?(:call) ? value.call : value] }.to_h }
55
+ end
56
+ attribute jsonb_attribute, :jsonb, default: all_defaults_mapping_proc if all_defaults_mapping_proc.present?
49
57
 
50
58
  # Setters are in a module to allow users to override them and still be able to use `super`.
51
59
  setters = Module.new do
@@ -66,7 +74,7 @@ module JsonbAccessor
66
74
  empty_store_key_attributes = names_to_store_keys.values.each_with_object({}) { |name, defaults| defaults[name] = nil }
67
75
  empty_named_attributes = names_to_store_keys.keys.each_with_object({}) { |name, defaults| defaults[name] = nil }
68
76
 
69
- store_key_attributes = ::JsonbAccessor::QueryBuilder.convert_keys_to_store_keys(value, names_to_store_keys)
77
+ store_key_attributes = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(value, names_to_store_keys)
70
78
  write_attribute(jsonb_attribute, empty_store_key_attributes.merge(store_key_attributes))
71
79
 
72
80
  empty_named_attributes.merge(value).each { |name, attribute_value| write_attribute(name, attribute_value) }
@@ -76,25 +84,42 @@ module JsonbAccessor
76
84
 
77
85
  # Makes sure new objects have the appropriate values in their jsonb fields.
78
86
  after_initialize do
79
- jsonb_values = public_send(jsonb_attribute) || {}
80
- jsonb_values.each do |store_key, value|
81
- name = names_and_store_keys.key(store_key)
82
- write_attribute(name, value) if name
87
+ if has_attribute?(jsonb_attribute)
88
+ jsonb_values = public_send(jsonb_attribute) || {}
89
+ jsonb_values.each do |store_key, value|
90
+ name = names_and_store_keys.key(store_key)
91
+ next unless name
92
+
93
+ write_attribute(name, value)
94
+ clear_attribute_change(name) if persisted?
95
+ end
83
96
  end
84
- clear_changes_information if persisted?
85
97
  end
86
98
 
87
99
  # <jsonb_attribute>_where scope
88
100
  scope("#{jsonb_attribute}_where", lambda do |attributes|
89
- store_key_attributes = ::JsonbAccessor::QueryBuilder.convert_keys_to_store_keys(attributes, all.model.public_send(store_key_mapping_method_name))
101
+ store_key_attributes = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(attributes, all.model.public_send(store_key_mapping_method_name))
90
102
  jsonb_where(jsonb_attribute, store_key_attributes)
91
103
  end)
92
104
 
93
105
  # <jsonb_attribute>_where_not scope
94
106
  scope("#{jsonb_attribute}_where_not", lambda do |attributes|
95
- store_key_attributes = ::JsonbAccessor::QueryBuilder.convert_keys_to_store_keys(attributes, all.model.public_send(store_key_mapping_method_name))
107
+ store_key_attributes = ::JsonbAccessor::QueryHelper.convert_keys_to_store_keys(attributes, all.model.public_send(store_key_mapping_method_name))
96
108
  jsonb_where_not(jsonb_attribute, store_key_attributes)
97
109
  end)
110
+
111
+ # <jsonb_attribute>_order scope
112
+ scope("#{jsonb_attribute}_order", lambda do |*args|
113
+ ordering_options = args.extract_options!
114
+ order_by_defaults = args.each_with_object({}) { |attribute, config| config[attribute] = :asc }
115
+ store_key_mapping = all.model.public_send(store_key_mapping_method_name)
116
+
117
+ order_by_defaults.merge(ordering_options).reduce(all) do |query, (name, direction)|
118
+ key = store_key_mapping[name.to_s]
119
+ order_query = jsonb_order(jsonb_attribute, key, direction)
120
+ query.merge(order_query)
121
+ end
122
+ end)
98
123
  end
99
124
  end
100
125
  end
@@ -1,111 +1,41 @@
1
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
- ORDER_DIRECTIONS = [:asc, :desc, "asc", "desc"].freeze
43
2
 
3
+ module JsonbAccessor
44
4
  module QueryBuilder
45
5
  extend ActiveSupport::Concern
46
- InvalidColumnName = Class.new(StandardError)
47
- InvalidFieldName = Class.new(StandardError)
48
- InvalidDirection = Class.new(StandardError)
49
-
50
- def self.validate_column_name!(query, column_name)
51
- if query.model.columns.none? { |column| column.name == column_name.to_s }
52
- raise InvalidColumnName, "a column named `#{column_name}` does not exist on the `#{query.model.table_name}` table"
53
- end
54
- end
55
-
56
- def self.validate_field_name!(query, column_name, field_name)
57
- store_keys = query.model.public_send("jsonb_store_key_mapping_for_#{column_name}").values
58
- if store_keys.exclude?(field_name.to_s)
59
- valid_field_names = store_keys.map { |key| "`#{key}`" }.join(", ")
60
- raise InvalidFieldName, "`#{field_name}` is not a valid field name, valid field names include: #{valid_field_names}"
61
- end
62
- end
63
-
64
- def self.validate_direction!(option)
65
- if ORDER_DIRECTIONS.exclude?(option)
66
- raise InvalidDirection, "`#{option}` is not a valid direction for ordering, only `asc` and `desc` are accepted"
67
- end
68
- end
69
-
70
- def self.convert_keys_to_store_keys(attributes, store_key_mapping)
71
- attributes.each_with_object({}) do |(name, value), new_attributes|
72
- store_key = store_key_mapping[name.to_s]
73
- new_attributes[store_key] = value
74
- end
75
- end
76
6
 
77
7
  included do
78
8
  scope(:jsonb_contains, lambda do |column_name, attributes|
79
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
9
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
80
10
  where("#{table_name}.#{column_name} @> (?)::jsonb", attributes.to_json)
81
11
  end)
82
12
 
83
13
  scope(:jsonb_excludes, lambda do |column_name, attributes|
84
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
14
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
85
15
  where.not("#{table_name}.#{column_name} @> (?)::jsonb", attributes.to_json)
86
16
  end)
87
17
 
88
18
  scope(:jsonb_number_where, lambda do |column_name, field_name, given_operator, value|
89
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
90
- operator = JsonbAccessor::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
19
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
20
+ operator = JsonbAccessor::QueryHelper::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
91
21
  where("(#{table_name}.#{column_name} ->> ?)::float #{operator} ?", field_name, value)
92
22
  end)
93
23
 
94
24
  scope(:jsonb_number_where_not, lambda do |column_name, field_name, given_operator, value|
95
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
96
- operator = JsonbAccessor::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
25
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
26
+ operator = JsonbAccessor::QueryHelper::NUMBER_OPERATORS_MAP.fetch(given_operator.to_s)
97
27
  where.not("(#{table_name}.#{column_name} ->> ?)::float #{operator} ?", field_name, value)
98
28
  end)
99
29
 
100
30
  scope(:jsonb_time_where, lambda do |column_name, field_name, given_operator, value|
101
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
102
- operator = JsonbAccessor::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
31
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
32
+ operator = JsonbAccessor::QueryHelper::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
103
33
  where("(#{table_name}.#{column_name} ->> ?)::timestamp #{operator} ?", field_name, value)
104
34
  end)
105
35
 
106
36
  scope(:jsonb_time_where_not, lambda do |column_name, field_name, given_operator, value|
107
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
108
- operator = JsonbAccessor::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
37
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
38
+ operator = JsonbAccessor::QueryHelper::TIME_OPERATORS_MAP.fetch(given_operator.to_s)
109
39
  where.not("(#{table_name}.#{column_name} ->> ?)::timestamp #{operator} ?", field_name, value)
110
40
  end)
111
41
 
@@ -113,11 +43,10 @@ module JsonbAccessor
113
43
  query = all
114
44
  contains_attributes = {}
115
45
 
116
- attributes.each do |name, value|
117
- case value
118
- when IS_NUMBER_QUERY_ARGUMENTS
46
+ JsonbAccessor::QueryHelper.convert_ranges(attributes).each do |name, value|
47
+ if JsonbAccessor::QueryHelper.number_query_arguments?(value)
119
48
  value.each { |operator, query_value| query = query.jsonb_number_where(column_name, name, operator, query_value) }
120
- when IS_TIME_QUERY_ARGUMENTS
49
+ elsif JsonbAccessor::QueryHelper.time_query_arguments?(value)
121
50
  value.each { |operator, query_value| query = query.jsonb_time_where(column_name, name, operator, query_value) }
122
51
  else
123
52
  contains_attributes[name] = value
@@ -132,10 +61,13 @@ module JsonbAccessor
132
61
  excludes_attributes = {}
133
62
 
134
63
  attributes.each do |name, value|
135
- case value
136
- when IS_NUMBER_QUERY_ARGUMENTS
64
+ if value.is_a?(Range)
65
+ raise JsonbAccessor::QueryHelper::NotSupported, "`jsonb_where_not` scope does not accept ranges as arguments. Given `#{value}` for `#{name}` field"
66
+ end
67
+
68
+ if JsonbAccessor::QueryHelper.number_query_arguments?(value)
137
69
  value.each { |operator, query_value| query = query.jsonb_number_where_not(column_name, name, operator, query_value) }
138
- when IS_TIME_QUERY_ARGUMENTS
70
+ elsif JsonbAccessor::QueryHelper.time_query_arguments?(value)
139
71
  value.each { |operator, query_value| query = query.jsonb_time_where_not(column_name, name, operator, query_value) }
140
72
  else
141
73
  excludes_attributes[name] = value
@@ -146,10 +78,10 @@ module JsonbAccessor
146
78
  end)
147
79
 
148
80
  scope(:jsonb_order, lambda do |column_name, field_name, direction|
149
- JsonbAccessor::QueryBuilder.validate_column_name!(all, column_name)
150
- JsonbAccessor::QueryBuilder.validate_field_name!(all, column_name, field_name)
151
- JsonbAccessor::QueryBuilder.validate_direction!(direction)
152
- order("(#{table_name}.#{column_name} -> '#{field_name}') #{direction}")
81
+ JsonbAccessor::QueryHelper.validate_column_name!(all, column_name)
82
+ JsonbAccessor::QueryHelper.validate_field_name!(all, column_name, field_name)
83
+ JsonbAccessor::QueryHelper.validate_direction!(direction)
84
+ order(Arel.sql("(#{table_name}.#{column_name} -> '#{field_name}') #{direction}"))
153
85
  end)
154
86
  end
155
87
  end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module JsonbAccessor
4
+ module QueryHelper
5
+ # Errors
6
+ InvalidColumnName = Class.new(StandardError)
7
+ InvalidFieldName = Class.new(StandardError)
8
+ InvalidDirection = Class.new(StandardError)
9
+ NotSupported = Class.new(StandardError)
10
+
11
+ # Constants
12
+ GREATER_THAN = ">"
13
+ GREATER_THAN_OR_EQUAL_TO = ">="
14
+ LESS_THAN = "<"
15
+ LESS_THAN_OR_EQUAL_TO = "<="
16
+
17
+ NUMBER_OPERATORS_MAP = {
18
+ GREATER_THAN => GREATER_THAN,
19
+ "greater_than" => GREATER_THAN,
20
+ "gt" => GREATER_THAN,
21
+ GREATER_THAN_OR_EQUAL_TO => GREATER_THAN_OR_EQUAL_TO,
22
+ "greater_than_or_equal_to" => GREATER_THAN_OR_EQUAL_TO,
23
+ "gte" => GREATER_THAN_OR_EQUAL_TO,
24
+ LESS_THAN => LESS_THAN,
25
+ "less_than" => LESS_THAN,
26
+ "lt" => LESS_THAN,
27
+ LESS_THAN_OR_EQUAL_TO => LESS_THAN_OR_EQUAL_TO,
28
+ "less_than_or_equal_to" => LESS_THAN_OR_EQUAL_TO,
29
+ "lte" => LESS_THAN_OR_EQUAL_TO
30
+ }.freeze
31
+
32
+ NUMBER_OPERATORS = NUMBER_OPERATORS_MAP.keys.freeze
33
+
34
+ TIME_OPERATORS_MAP = {
35
+ "after" => GREATER_THAN,
36
+ "before" => LESS_THAN
37
+ }.freeze
38
+
39
+ TIME_OPERATORS = TIME_OPERATORS_MAP.keys.freeze
40
+
41
+ ORDER_DIRECTIONS = [:asc, :desc, "asc", "desc"].freeze
42
+
43
+ class << self
44
+ def validate_column_name!(query, column_name)
45
+ if query.model.columns.none? { |column| column.name == column_name.to_s }
46
+ raise InvalidColumnName, "a column named `#{column_name}` does not exist on the `#{query.model.table_name}` table"
47
+ end
48
+ end
49
+
50
+ def validate_field_name!(query, column_name, field_name)
51
+ store_keys = query.model.public_send("jsonb_store_key_mapping_for_#{column_name}").values
52
+ if store_keys.exclude?(field_name.to_s)
53
+ valid_field_names = store_keys.map { |key| "`#{key}`" }.join(", ")
54
+ raise InvalidFieldName, "`#{field_name}` is not a valid field name, valid field names include: #{valid_field_names}"
55
+ end
56
+ end
57
+
58
+ def validate_direction!(option)
59
+ if ORDER_DIRECTIONS.exclude?(option)
60
+ raise InvalidDirection, "`#{option}` is not a valid direction for ordering, only `asc` and `desc` are accepted"
61
+ end
62
+ end
63
+
64
+ def convert_keys_to_store_keys(attributes, store_key_mapping)
65
+ attributes.each_with_object({}) do |(name, value), new_attributes|
66
+ store_key = store_key_mapping[name.to_s]
67
+ new_attributes[store_key] = value
68
+ end
69
+ end
70
+
71
+ def number_query_arguments?(arg)
72
+ arg.is_a?(Hash) && arg.keys.map(&:to_s).all? { |key| NUMBER_OPERATORS.include?(key) }
73
+ end
74
+
75
+ def time_query_arguments?(arg)
76
+ arg.is_a?(Hash) && arg.keys.map(&:to_s).all? { |key| TIME_OPERATORS.include?(key) }
77
+ end
78
+
79
+ def convert_number_ranges(attributes)
80
+ attributes.each_with_object({}) do |(name, value), new_attributes|
81
+ is_range = value.is_a?(Range)
82
+
83
+ new_attributes[name] = if is_range && value.first.is_a?(Numeric) && value.exclude_end?
84
+ { greater_than_or_equal_to: value.first, less_than: value.end }
85
+ elsif is_range && value.first.is_a?(Numeric)
86
+ { greater_than_or_equal_to: value.first, less_than_or_equal_to: value.end }
87
+ else
88
+ value
89
+ end
90
+ end
91
+ end
92
+
93
+ def convert_time_ranges(attributes)
94
+ attributes.each_with_object({}) do |(name, value), new_attributes|
95
+ is_range = value.is_a?(Range)
96
+
97
+ if is_range && (value.first.is_a?(Time) || value.first.is_a?(Date))
98
+ start_time = value.first
99
+ end_time = value.end
100
+ new_attributes[name] = { before: end_time, after: start_time }
101
+ else
102
+ new_attributes[name] = value
103
+ end
104
+ end
105
+ end
106
+
107
+ def convert_ranges(attributes)
108
+ %i[convert_number_ranges convert_time_ranges].reduce(attributes) do |new_attributes, converter_method|
109
+ public_send(converter_method, new_attributes)
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  module JsonbAccessor
3
- VERSION = "1.0.0.beta.4"
4
+ VERSION = "1.1.0"
4
5
  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
4
+ version: 1.1.0
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: 2017-04-28 00:00:00.000000000 Z
13
+ date: 2020-06-29 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: activerecord
@@ -26,6 +26,20 @@ dependencies:
26
26
  - - ">="
27
27
  - !ruby/object:Gem::Version
28
28
  version: '5.0'
29
+ - !ruby/object:Gem::Dependency
30
+ name: activesupport
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - ">="
34
+ - !ruby/object:Gem::Version
35
+ version: '5.0'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '5.0'
29
43
  - !ruby/object:Gem::Dependency
30
44
  name: pg
31
45
  requirement: !ruby/object:Gem::Requirement
@@ -44,44 +58,44 @@ dependencies:
44
58
  name: appraisal
45
59
  requirement: !ruby/object:Gem::Requirement
46
60
  requirements:
47
- - - ">="
61
+ - - "~>"
48
62
  - !ruby/object:Gem::Version
49
- version: '0'
63
+ version: 2.2.0
50
64
  type: :development
51
65
  prerelease: false
52
66
  version_requirements: !ruby/object:Gem::Requirement
53
67
  requirements:
54
- - - ">="
68
+ - - "~>"
55
69
  - !ruby/object:Gem::Version
56
- version: '0'
70
+ version: 2.2.0
57
71
  - !ruby/object:Gem::Dependency
58
72
  name: bundler
59
73
  requirement: !ruby/object:Gem::Requirement
60
74
  requirements:
61
75
  - - "~>"
62
76
  - !ruby/object:Gem::Version
63
- version: '1.9'
77
+ version: 2.1.4
64
78
  type: :development
65
79
  prerelease: false
66
80
  version_requirements: !ruby/object:Gem::Requirement
67
81
  requirements:
68
82
  - - "~>"
69
83
  - !ruby/object:Gem::Version
70
- version: '1.9'
84
+ version: 2.1.4
71
85
  - !ruby/object:Gem::Dependency
72
86
  name: database_cleaner
73
87
  requirement: !ruby/object:Gem::Requirement
74
88
  requirements:
75
- - - ">="
89
+ - - "~>"
76
90
  - !ruby/object:Gem::Version
77
- version: '0'
91
+ version: 1.6.0
78
92
  type: :development
79
93
  prerelease: false
80
94
  version_requirements: !ruby/object:Gem::Requirement
81
95
  requirements:
82
- - - ">="
96
+ - - "~>"
83
97
  - !ruby/object:Gem::Version
84
- version: '0'
98
+ version: 1.6.0
85
99
  - !ruby/object:Gem::Dependency
86
100
  name: awesome_print
87
101
  requirement: !ruby/object:Gem::Requirement
@@ -142,75 +156,61 @@ dependencies:
142
156
  name: rake
143
157
  requirement: !ruby/object:Gem::Requirement
144
158
  requirements:
145
- - - "~>"
159
+ - - ">="
146
160
  - !ruby/object:Gem::Version
147
- version: '10.0'
161
+ version: 12.3.3
148
162
  type: :development
149
163
  prerelease: false
150
164
  version_requirements: !ruby/object:Gem::Requirement
151
165
  requirements:
152
- - - "~>"
166
+ - - ">="
153
167
  - !ruby/object:Gem::Version
154
- version: '10.0'
168
+ version: 12.3.3
155
169
  - !ruby/object:Gem::Dependency
156
170
  name: rspec
157
171
  requirement: !ruby/object:Gem::Requirement
158
172
  requirements:
159
173
  - - "~>"
160
174
  - !ruby/object:Gem::Version
161
- version: '3.2'
175
+ version: 3.6.0
162
176
  type: :development
163
177
  prerelease: false
164
178
  version_requirements: !ruby/object:Gem::Requirement
165
179
  requirements:
166
180
  - - "~>"
167
181
  - !ruby/object:Gem::Version
168
- version: '3.2'
182
+ version: 3.6.0
169
183
  - !ruby/object:Gem::Dependency
170
184
  name: rubocop
171
185
  requirement: !ruby/object:Gem::Requirement
172
186
  requirements:
173
187
  - - "~>"
174
188
  - !ruby/object:Gem::Version
175
- version: '0.31'
189
+ version: 0.48.1
176
190
  type: :development
177
191
  prerelease: false
178
192
  version_requirements: !ruby/object:Gem::Requirement
179
193
  requirements:
180
194
  - - "~>"
181
195
  - !ruby/object:Gem::Version
182
- version: '0.31'
183
- - !ruby/object:Gem::Dependency
184
- name: shoulda-matchers
185
- requirement: !ruby/object:Gem::Requirement
186
- requirements:
187
- - - ">="
188
- - !ruby/object:Gem::Version
189
- version: '0'
190
- type: :development
191
- prerelease: false
192
- version_requirements: !ruby/object:Gem::Requirement
193
- requirements:
194
- - - ">="
195
- - !ruby/object:Gem::Version
196
- version: '0'
196
+ version: 0.48.1
197
197
  - !ruby/object:Gem::Dependency
198
198
  name: standalone_migrations
199
199
  requirement: !ruby/object:Gem::Requirement
200
200
  requirements:
201
- - - ">="
201
+ - - "~>"
202
202
  - !ruby/object:Gem::Version
203
- version: '0'
203
+ version: 5.2.0
204
204
  type: :development
205
205
  prerelease: false
206
206
  version_requirements: !ruby/object:Gem::Requirement
207
207
  requirements:
208
- - - ">="
208
+ - - "~>"
209
209
  - !ruby/object:Gem::Version
210
- version: '0'
210
+ version: 5.2.0
211
211
  description: Adds typed jsonb backed fields to your ActiveRecord models.
212
212
  email:
213
- - michael.crismali@gmail.com
213
+ - michael@crismali.com
214
214
  - joe@devmynd.com
215
215
  - jason@haruska.com
216
216
  executables: []
@@ -235,11 +235,12 @@ files:
235
235
  - db/migrate/20150407031737_set_up_testing_db.rb
236
236
  - db/schema.rb
237
237
  - gemfiles/activerecord_5.0.0.gemfile
238
- - gemfiles/activerecord_5.0.0.gemfile.lock
238
+ - gemfiles/activerecord_5.1.0.gemfile
239
239
  - jsonb_accessor.gemspec
240
240
  - lib/jsonb_accessor.rb
241
241
  - lib/jsonb_accessor/macro.rb
242
242
  - lib/jsonb_accessor/query_builder.rb
243
+ - lib/jsonb_accessor/query_helper.rb
243
244
  - lib/jsonb_accessor/version.rb
244
245
  homepage: https://github.com/devmynd/jsonb_accessor
245
246
  licenses:
@@ -256,12 +257,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
256
257
  version: '2.0'
257
258
  required_rubygems_version: !ruby/object:Gem::Requirement
258
259
  requirements:
259
- - - ">"
260
+ - - ">="
260
261
  - !ruby/object:Gem::Version
261
- version: 1.3.1
262
+ version: '0'
262
263
  requirements: []
263
264
  rubyforge_project:
264
- rubygems_version: 2.5.1
265
+ rubygems_version: 2.6.11
265
266
  signing_key:
266
267
  specification_version: 4
267
268
  summary: Adds typed jsonb backed fields to your ActiveRecord models.
@@ -1,140 +0,0 @@
1
- PATH
2
- remote: ..
3
- specs:
4
- jsonb_accessor (1.0.0.beta.4)
5
- activerecord (>= 5.0)
6
- pg (>= 0.18.1)
7
-
8
- GEM
9
- remote: https://rubygems.org/
10
- specs:
11
- actionpack (5.0.2)
12
- actionview (= 5.0.2)
13
- activesupport (= 5.0.2)
14
- rack (~> 2.0)
15
- rack-test (~> 0.6.3)
16
- rails-dom-testing (~> 2.0)
17
- rails-html-sanitizer (~> 1.0, >= 1.0.2)
18
- actionview (5.0.2)
19
- activesupport (= 5.0.2)
20
- builder (~> 3.1)
21
- erubis (~> 2.7.0)
22
- rails-dom-testing (~> 2.0)
23
- rails-html-sanitizer (~> 1.0, >= 1.0.3)
24
- activemodel (5.0.2)
25
- activesupport (= 5.0.2)
26
- activerecord (5.0.2)
27
- activemodel (= 5.0.2)
28
- activesupport (= 5.0.2)
29
- arel (~> 7.0)
30
- activesupport (5.0.2)
31
- concurrent-ruby (~> 1.0, >= 1.0.2)
32
- i18n (~> 0.7)
33
- minitest (~> 5.1)
34
- tzinfo (~> 1.1)
35
- appraisal (2.1.0)
36
- bundler
37
- rake
38
- thor (>= 0.14.0)
39
- arel (7.1.4)
40
- ast (2.3.0)
41
- awesome_print (1.7.0)
42
- builder (3.2.3)
43
- coderay (1.1.1)
44
- concurrent-ruby (1.0.5)
45
- database_cleaner (1.5.3)
46
- diff-lcs (1.3)
47
- erubis (2.7.0)
48
- i18n (0.8.1)
49
- loofah (2.0.3)
50
- nokogiri (>= 1.5.9)
51
- method_source (0.8.2)
52
- mini_portile2 (2.1.0)
53
- minitest (5.10.1)
54
- nokogiri (1.7.0.1)
55
- mini_portile2 (~> 2.1.0)
56
- parser (2.4.0.0)
57
- ast (~> 2.2)
58
- pg (0.20.0)
59
- powerpack (0.1.1)
60
- pry (0.10.4)
61
- coderay (~> 1.1.0)
62
- method_source (~> 0.8.1)
63
- slop (~> 3.4)
64
- pry-doc (0.10.0)
65
- pry (~> 0.9)
66
- yard (~> 0.9)
67
- pry-nav (0.2.4)
68
- pry (>= 0.9.10, < 0.11.0)
69
- rack (2.0.1)
70
- rack-test (0.6.3)
71
- rack (>= 1.0)
72
- rails-dom-testing (2.0.2)
73
- activesupport (>= 4.2.0, < 6.0)
74
- nokogiri (~> 1.6)
75
- rails-html-sanitizer (1.0.3)
76
- loofah (~> 2.0)
77
- railties (5.0.2)
78
- actionpack (= 5.0.2)
79
- activesupport (= 5.0.2)
80
- method_source
81
- rake (>= 0.8.7)
82
- thor (>= 0.18.1, < 2.0)
83
- rainbow (2.2.1)
84
- rake (10.5.0)
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)
92
- diff-lcs (>= 1.2.0, < 2.0)
93
- rspec-support (~> 3.5.0)
94
- rspec-mocks (3.5.0)
95
- diff-lcs (>= 1.2.0, < 2.0)
96
- rspec-support (~> 3.5.0)
97
- rspec-support (3.5.0)
98
- rubocop (0.47.1)
99
- parser (>= 2.3.3.1, < 3.0)
100
- powerpack (~> 0.1)
101
- rainbow (>= 1.99.1, < 3.0)
102
- ruby-progressbar (~> 1.7)
103
- unicode-display_width (~> 1.0, >= 1.0.1)
104
- ruby-progressbar (1.8.1)
105
- shoulda-matchers (3.1.1)
106
- activesupport (>= 4.0.0)
107
- slop (3.6.0)
108
- standalone_migrations (5.0.0)
109
- activerecord (>= 4.2.7, < 5.1.0)
110
- railties (>= 4.2.7, < 5.1.0)
111
- rake (~> 10.0)
112
- thor (0.19.4)
113
- thread_safe (0.3.6)
114
- tzinfo (1.2.2)
115
- thread_safe (~> 0.1)
116
- unicode-display_width (1.1.3)
117
- yard (0.9.8)
118
-
119
- PLATFORMS
120
- ruby
121
-
122
- DEPENDENCIES
123
- activerecord (~> 5.0.0)
124
- appraisal
125
- awesome_print
126
- bundler (~> 1.9)
127
- database_cleaner
128
- jsonb_accessor!
129
- pg
130
- pry
131
- pry-doc
132
- pry-nav
133
- rake (~> 10.0)
134
- rspec (~> 3.2)
135
- rubocop (~> 0.31)
136
- shoulda-matchers
137
- standalone_migrations
138
-
139
- BUNDLED WITH
140
- 1.14.6