pax_global_header 0000666 0000000 0000000 00000000064 15157213547 0014524 g ustar 00root root 0000000 0000000 52 comment=cef7a810a48ce9c3b8a79015591fddfb1a963e5a
madeintandem-jsonb_accessor-7626103/ 0000775 0000000 0000000 00000000000 15157213547 0017337 5 ustar 00root root 0000000 0000000 madeintandem-jsonb_accessor-7626103/.github/ 0000775 0000000 0000000 00000000000 15157213547 0020677 5 ustar 00root root 0000000 0000000 madeintandem-jsonb_accessor-7626103/.github/dependabot.yml 0000664 0000000 0000000 00000000166 15157213547 0023532 0 ustar 00root root 0000000 0000000 version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'weekly'
madeintandem-jsonb_accessor-7626103/.github/workflows/ 0000775 0000000 0000000 00000000000 15157213547 0022734 5 ustar 00root root 0000000 0000000 madeintandem-jsonb_accessor-7626103/.github/workflows/ci.yml 0000664 0000000 0000000 00000005437 15157213547 0024063 0 ustar 00root root 0000000 0000000 name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
lint:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: "3.4"
bundler-cache: true
- run: bundle exec rubocop
tests:
needs: lint
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
ruby: ["ruby-3.2", "ruby-3.3", "ruby-3.4", "ruby-4.0", "jruby-9.4", "jruby-10.0"]
activerecord: ["6.1", "7.0", "7.1", "7.2", "8.0", "8.1"]
postgresql: ["13", "14", "15", "16", "17", "18"]
exclude:
# fails due to "ArgumentError: when initializing an Active Record adapter with a config hash, that should be the only argument" in db:schema:load
- ruby: "jruby-9.4"
activerecord: "7.2"
- ruby: "jruby-9.4"
activerecord: "8.0"
# fails due to "Because activerecord >= 8.0.0.beta1 depends on Ruby >= 3.2.0 and Gemfile depends on activerecord ~> 8.1, Ruby >= 3.2.0 is required. So, because current Ruby version is = 3.1.7, version solving has failed."
- ruby: "jruby-9.4"
activerecord: "8.1"
# https://github.com/jruby/activerecord-jdbc-adapter/issues/1173
- ruby: "jruby-10.0"
activerecord: "8.0"
# https://github.com/jruby/activerecord-jdbc-adapter/issues/1184
- ruby: "jruby-10.0"
activerecord: "8.1"
name: "Active Record ${{ matrix.activerecord }} with PostgreSQL ${{ matrix.postgresql }} on ${{ matrix.ruby }}"
services:
db:
image: postgres:${{ matrix.postgresql }}
env:
POSTGRES_HOST_AUTH_METHOD: trust
POSTGRES_DB: jsonb_accessor
ports: ['5432:5432']
options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- run: echo "gem 'activerecord', '~> ${{ matrix.activerecord }}.0'" > Gemfile.local
- if: matrix.activerecord == '6.1' # see https://github.com/rails/rails/pull/54264#issuecomment-2596149819 and https://www.ruby-lang.org/en/news/2024/12/25/ruby-3-4-0-released/#standard-library-updates and https://www.ruby-lang.org/en/news/2025/12/25/ruby-4-0-0-released/#stdlib-updates
run: printf "gem 'concurrent-ruby', '< 1.3.5'\ngem 'mutex_m'\ngem 'base64'\ngem 'bigdecimal'\ngem 'logger'\n" >> Gemfile.local
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
ruby-version: ${{ matrix.ruby }}
bundler-cache: true
- run: bundle exec rake db:schema:load
- run: bundle exec rake spec
madeintandem-jsonb_accessor-7626103/.github/workflows/push_gem.yml 0000664 0000000 0000000 00000002142 15157213547 0025265 0 ustar 00root root 0000000 0000000 name: Publish gem to rubygems.org
on:
push:
tags:
- 'v*'
permissions:
contents: read
jobs:
push:
if: github.repository == 'madeintandem/jsonb_accessor'
runs-on: ubuntu-24.04
permissions:
contents: write
id-token: write
strategy:
matrix:
ruby: ["ruby-3.4", "jruby-9.4"]
steps:
- uses: step-security/harden-runner@fa2e9d605c4eeb9fcad4c99c224cee0c6c7f3594 # v2.16.0
with:
egress-policy: audit
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- if: matrix.ruby == 'jruby-9.4' # cribbed from https://github.com/ruby/psych/blob/v5.2.4/.github/workflows/push_gem.yml
run: |
sudo apt install default-jdk maven
gem update --system
gem install ruby-maven rake-compiler --no-document
rake compile
- uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with:
bundler-cache: true
ruby-version: ${{ matrix.ruby }}
- uses: rubygems/release-gem@e9a6361a0b14562539327c2a02373edc56dd3169 # v1.1.4
madeintandem-jsonb_accessor-7626103/.gitignore 0000664 0000000 0000000 00000000200 15157213547 0021317 0 ustar 00root root 0000000 0000000 /.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
gemfiles/.bundle
gemfiles/*.gemfile.lock
madeintandem-jsonb_accessor-7626103/.rspec 0000664 0000000 0000000 00000000032 15157213547 0020447 0 ustar 00root root 0000000 0000000 --format progress
--color
madeintandem-jsonb_accessor-7626103/.rubocop.yml 0000664 0000000 0000000 00000002466 15157213547 0021621 0 ustar 00root root 0000000 0000000 AllCops:
NewCops: enable
TargetRubyVersion: 3.2.8
SuggestExtensions: false
Exclude:
- "db/**/*"
- "gemfiles/**/*"
- "vendor/**/*"
Layout/SpaceBeforeFirstArg:
Enabled: false
Layout/LineLength:
Enabled: false
Layout/SpaceAroundEqualsInParameterDefault:
Enabled: false
Lint/UnusedBlockArgument:
Enabled: false
Lint/UnusedMethodArgument:
Enabled: false
Metrics/AbcSize:
Enabled: false
Metrics/ClassLength:
Enabled: false
Metrics/CyclomaticComplexity:
Enabled: false
Metrics/MethodLength:
Enabled: false
Metrics/ModuleLength:
Enabled: false
Metrics/PerceivedComplexity:
Enabled: false
Metrics/BlockLength:
Enabled: false
Style/ClassAndModuleChildren:
Enabled: false
Style/ClassVars:
Enabled: false
Style/Documentation:
Enabled: false
Style/DoubleNegation:
Enabled: false
Naming/FileName:
Enabled: false
Style/GuardClause:
Enabled: false
Style/NilComparison:
Enabled: false
Style/RescueModifier:
Enabled: false
Style/SignalException:
Enabled: false
Style/SingleLineMethods:
Enabled: false
Style/StringLiterals:
EnforcedStyle: double_quotes
Naming/BinaryOperatorParameterName:
Enabled: false
Naming/VariableNumber:
Enabled: false
Gemspec/RequiredRubyVersion:
Enabled: false
Gemspec/RequireMFA:
Enabled: false
Gemspec/DevelopmentDependencies:
EnforcedStyle: gemspec
madeintandem-jsonb_accessor-7626103/.ruby-version 0000664 0000000 0000000 00000000006 15157213547 0022000 0 ustar 00root root 0000000 0000000 3.4.3
madeintandem-jsonb_accessor-7626103/CHANGELOG.md 0000664 0000000 0000000 00000005312 15157213547 0021151 0 ustar 00root root 0000000 0000000 # Changelog
## [Unreleased]
## [1.4.2] - 2026-03-20
### Fixed
- Bug fix: Persisted records with sparse JSONB data returned `nil` instead of declared defaults for fields absent from the column. This regression was introduced in 1.4.1 during the `prefix`/`suffix` refactor, where `options.delete(:default)` mutated the options hash before passing it to `attribute`, stripping the default from the virtual attribute definition.
### Changed
- Documented the backward-compatible positional hash syntax in `jsonb_accessor`, where definitions are passed as a plain hash (jsonb_accessor :col, { foo: :string })
## [1.4.1] - 2026-01-22
### Added
- Support for `prefix` and `suffix` options to customize attribute accessor names while preserving original keys in the JSONB column. [#173](https://github.com/madeintandem/jsonb_accessor/issues/173)
## [1.4] - 2023-10-15
### Breaking change
- `jsonb_accessor` dropped support for Ruby 2 and Rails versions lower than 6.1. Support for ActiveRecord::Enum was also
dropped because ActiveRecord 7.1 now requires each enum field to be backed by a database column. Enums will still work
when using AR versions lower than 7.1. This is a limitation of Rails, not of this gem.
### Fixed
- Bug fix: An array of datetimes previously caused an error. https://github.com/madeintandem/jsonb_accessor/pull/169. Thanks @bekicot.
- Rails 7.1 is officially supported and tested against.
## [1.3.10] - 2023-05-30
### No changes
A new release was necessary to fix the corrupted 1.3.9 Java release on RubyGems.
## [1.3.9] - 2023-05-30
### No changes
A new release was necessary to fix the corrupted 1.3.8 Java release on RubyGems.
## [1.3.8] - 2023-05-29
### Fixes
- Support for ActiveRecord::Enum. [#163](https://github.com/madeintandem/jsonb_accessor/pull/163)
## [1.3.7] - 2022-12-29
- jruby support. jsonb_accessor now depends on `activerecord-jdbcpostgresql-adapter` instead of `pg` when the RUBY_PLATFORM is java. [#157](https://github.com/madeintandem/jsonb_accessor/pull/157)
## [1.3.6] - 2022-09-23
### Fixed
- Bug fix: Datetime values were not properly deserialized [#155](https://github.com/madeintandem/jsonb_accessor/pull/155)
## [1.3.5] - 2022-07-23
### Fixed
- Bug fix: Attributes defined outside of jsonb_accessor are not written [#149](https://github.com/madeintandem/jsonb_accessor/pull/149)
## [1.3.4] - 2022-02-02
### Fixed
- Bug fix: Raised ActiveModel::MissingAttributeError when model was initialized without the jsonb_accessor field [#145](https://github.com/madeintandem/jsonb_accessor/issues/145)
## [1.3.3] - 2022-01-29
### Fixed
- Bug fix: DateTime objects are now correctly written without timezone
information [#137](https://github.com/madeintandem/jsonb_accessor/pull/137).
Thanks @caiohsramos
madeintandem-jsonb_accessor-7626103/CODE_OF_CONDUCT.md 0000664 0000000 0000000 00000002615 15157213547 0022142 0 ustar 00root root 0000000 0000000 # Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
madeintandem-jsonb_accessor-7626103/Gemfile 0000664 0000000 0000000 00000000367 15157213547 0020640 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
source "https://rubygems.org"
# Specify your gem's dependencies in jsonb_accessor.gemspec
gemspec
local_gemfile = File.expand_path("Gemfile.local", __dir__)
eval_gemfile local_gemfile if File.exist?(local_gemfile)
madeintandem-jsonb_accessor-7626103/LICENSE.txt 0000664 0000000 0000000 00000002073 15157213547 0021164 0 ustar 00root root 0000000 0000000 The MIT License (MIT)
Copyright (c) 2015 Michael Crismali
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
madeintandem-jsonb_accessor-7626103/Makefile 0000664 0000000 0000000 00000000715 15157213547 0021002 0 ustar 00root root 0000000 0000000 build-gem:
@docker build --build-arg RUBY_PLATFORM=ruby --build-arg RUBY_VERSION=3.2.2 -t jsonb_accessor-ruby:3.2.2 .
@docker run --rm -v $(PWD):/usr/src/app -w /usr/src/app jsonb_accessor-ruby:3.2.2 gem build
build-gem-java:
@docker build --build-arg RUBY_PLATFORM=jruby --build-arg RUBY_VERSION=9.4.2-jdk -t jsonb_accessor-jruby:9.4.2-jdk .
@docker run --rm -v $(PWD):/usr/src/app -w /usr/src/app jsonb_accessor-jruby:9.4.2-jdk gem build --platform java
madeintandem-jsonb_accessor-7626103/README.md 0000664 0000000 0000000 00000032032 15157213547 0020616 0 ustar 00root root 0000000 0000000 # JSONb Accessor
Created by [
](https://www.madeintandem.com/)
[](http://badge.fury.io/rb/jsonb_accessor) 
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.
It also adds generic scopes for querying `jsonb` columns.
## ⚠️ Status
This gem is in maintenance mode and no active development of new features is planned. The major focus is to keep it working with new Ruby/Rails versions and fix any bugs reported. Any PRs for feature requests or enhancements will be reviewed and merged -- so contributions are encouraged!
## Table of Contents
- [Installation](#installation)
- [Usage](#usage)
- [Scopes](#scopes)
- [Single-Table Inheritance](#single-table-inheritance)
- [Dependencies](#dependencies)
- [Validations](#validations)
- [Upgrading](#upgrading)
- [Development](#development)
- [Contributing](#contributing)
## Installation
Add this line to your application's `Gemfile`:
```ruby
gem "jsonb_accessor"
```
And then execute:
$ bundle install
## Usage
First we must create a model which has a `jsonb` column available to store data into it:
```ruby
class CreateProducts < ActiveRecord::Migration
def change
create_table :products do |t|
t.jsonb :data
end
end
end
```
We can then declare the `jsonb` fields we wish to expose via the accessor:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
title: :string,
external_id: :integer,
reviewed_at: :datetime
end
```
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.
To pass through options like `default` and `array` to the `attribute` API, just put them in an array.
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
title: [:string, default: "Untitled"],
previous_titles: [:string, array: true, default: []]
end
```
The `default` option works pretty much as you would expect in practice; if no values are set for the attributes, a hash of the specified default values is saved to the jsonb column.
You can also pass in a `store_key` option.
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data, title: [:string, store_key: :t]
end
```
This allows you to use `title` for your getters and setters, but use `t` as the key in the `jsonb` column.
```ruby
product = Product.new(title: "Foo")
product.title #=> "Foo"
product.data #=> { "t" => "Foo" }
```
You can also pass in a `prefix` or `suffix` option.
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
title: [:string, prefix: :data],
external_id: [:integer, suffix: :attr]
end
```
This allows you to use `data_title` and `external_id_attr` for your getters and setters, but use `title` and `external_id` as the key in the `jsonb`.
Also, you can pass `true` as a value for `prefix` or `suffix` to use the json_accessor name.
```ruby
product = Product.new(data_title: "Foo", external_id_attr: 12314122)
product.data_title #=> "Foo"
product.external_id_attr #=> 12314122
product.data #=> { "title" => "Foo", "external_id" => 12314122 }
```
### Global Options
You can apply options to all fields by passing an options hash as the second parameter:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data, { prefix: true },
title: :string,
external_id: :integer,
price: :decimal
end
```
This applies the `prefix` to all fields. You can still override it for individual fields:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data, { prefix: :product },
title: :string,
external_id: [:integer, prefix: :custom],
price: :decimal
end
product = Product.new(product_title: "Widget", custom_external_id: 123, product_price: 19.99)
product.product_title #=> "Widget"
product.custom_external_id #=> 123
product.product_price #=> 19.99
product.data #=> { "title" => "Widget", "external_id" => 123, "price" => 19.99 }
```
Global options currently support `:prefix` and `:suffix`.
## Scopes
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.
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:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
name: :string,
price: [:integer, store_key: :p],
price_in_cents: :integer,
reviewed_at: :datetime
end
```
Jsonb Accessor will add a `scope` to `Product` called like the json column with `_where` suffix, in our case `data_where`.
```ruby
Product.all.data_where(name: "Granite Towel", price: 17)
```
Similarly, it will also add a `data_where_not` `scope` to `Product`.
```ruby
Product.all.data_where_not(name: "Plasma Fork")
```
For number fields you can query using `<` or `>`or use plain english if that's what you prefer.
```ruby
Product.all.data_where(price: { <: 15 })
Product.all.data_where(price: { <=: 15 })
Product.all.data_where(price: { less_than: 15 })
Product.all.data_where(price: { less_than_or_equal_to: 15 })
Product.all.data_where(price: { >: 15 })
Product.all.data_where(price: { >=: 15 })
Product.all.data_where(price: { greater_than: 15 })
Product.all.data_where(price: { greater_than_or_equal_to: 15 })
Product.all.data_where(price: { greater_than: 15, less_than: 30 })
```
For time related fields you can query using `before` and `after`.
```ruby
Product.all.data_where(reviewed_at: { before: Time.current.beginning_of_week, after: 4.weeks.ago })
```
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).
```ruby
Product.all.data_where(price: 10..20)
Product.all.data_where(price: 10...20)
Product.all.data_where(reviewed_at: Time.current..3.days.from_now)
```
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.
### `jsonb_where`
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:
```ruby
Product.all.jsonb_where(:data, reviewed_at: { before: Time.current }, p: { greater_than: 5 })
# instead of
Product.all.data_where(reviewed_at: { before: Time.current }, price: { greater_than: 5 })
```
This scope makes use of the `jsonb_contains`, `jsonb_number_where`, and `jsonb_time_where` `scope`s.
### `jsonb_where_not`
Just the opposite of `jsonb_where`. Note that this will automatically exclude all records that contain `null` in their jsonb column (the `data` column, in the example below).
```ruby
Product.all.jsonb_where_not(:data, reviewed_at: { before: Time.current }, p: { greater_than: 5 })
```
### `_order`
Orders your query according to values in the Jsonb Accessor fields similar to ActiveRecord's `order`.
```ruby
Product.all.data_order(:price)
Product.all.data_order(:price, :reviewed_at)
Product.all.data_order(:price, reviewed_at: :desc)
```
It will convert your given keys into store keys if necessary.
### `jsonb_order`
Allows you to order by a Jsonb Accessor field.
```ruby
Product.all.jsonb_order(:data, :price, :asc)
Product.all.jsonb_order(:data, :price, :desc)
```
### `jsonb_contains`
Returns all records that contain the given JSON paths.
```ruby
Product.all.jsonb_contains(:data, title: "foo")
Product.all.jsonb_contains(:data, reviewed_at: 10.minutes.ago, p: 12) # Using the store key
```
**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.
### `jsonb_excludes`
Returns all records that exclude the given JSON paths. Pretty much the opposite of `jsonb_contains`. Note that this will automatically exclude all records that contain `null` in their jsonb column (the `data` column, in the example below).
```ruby
Product.all.jsonb_excludes(:data, title: "foo")
Product.all.jsonb_excludes(:data, reviewed_at: 10.minutes.ago, p: 12) # Using the store key
```
### `jsonb_number_where`
Returns all records that match the given criteria.
```ruby
Product.all.jsonb_number_where(:data, :price_in_cents, :greater_than, 300)
```
It supports:
- `>`
- `>=`
- `greater_than`
- `greater_than_or_equal_to`
- `<`
- `<=`
- `less_than`
- `less_than_or_equal_to`
and it is indifferent to strings/symbols.
### `jsonb_number_where_not`
Returns all records that do not match the given criteria. It's the opposite of `jsonb_number_where`. Note that this will automatically exclude all records that contain `null` in their jsonb column (the `data` column, in the example below).
```ruby
Product.all.jsonb_number_where_not(:data, :price_in_cents, :greater_than, 300)
```
### `jsonb_time_where`
Returns all records that match the given criteria.
```ruby
Product.all.jsonb_time_where(:data, :reviewed_at, :before, 2.days.ago)
```
It supports `before` and `after` and is indifferent to strings/symbols.
### `jsonb_time_where_not`
Returns all records that match the given criteria. The opposite of `jsonb_time_where`. Note that this will automatically exclude all records that contain `null` in their jsonb column (the `data` column, in the example below).
```ruby
Product.all.jsonb_time_where_not(:data, :reviewed_at, :before, 2.days.ago)
```
## Single-Table Inheritance
One of the big issues with `ActiveRecord` single-table inheritance (STI)
is sparse columns. Essentially, as sub-types of the original table
diverge further from their parent more columns are left empty in a given
table. Postgres' `jsonb` type provides part of the solution in that
the values in an `jsonb` column does not impose a structure - different
rows can have different values.
We set up our table with an `jsonb` field:
```ruby
# db/migration/_create_players.rb
class CreateVehicles < ActiveRecord::Migration
def change
create_table :vehicles do |t|
t.string :make
t.string :model
t.integer :model_year
t.string :type
t.jsonb :data
end
end
end
```
And for our models:
```ruby
# app/models/vehicle.rb
class Vehicle < ActiveRecord::Base
end
# app/models/vehicles/automobile.rb
class Automobile < Vehicle
jsonb_accessor :data,
axle_count: :integer,
weight: :float
end
# app/models/vehicles/airplane.rb
class Airplane < Vehicle
jsonb_accessor :data,
engine_type: :string,
safety_rating: :integer
end
```
From here any attributes specific to any sub-class can be stored in the
`jsonb` column avoiding sparse data. Indices can also be created on
individual fields in an `jsonb` column.
This approach was originally conceived by Joe Hirn in [this blog
post](https://madeintandem.com/blog/2013-3-single-table-inheritance-hstore-lovely-combination/).
## Validations
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.
## Dependencies
We actively test the following in CI, older versions may work but are **not** supported:
- Ruby >= 3.2 or JRuby >= 9.4
- Rails >= 6.1
- PostgreSQL >= 13
JRuby isn't fully supported due to errors, see the CI matrix -- contributions to improve support are welcome!
## Development
After checking out the repo, run `bin/setup` to install dependencies (make sure postgres is running first).
Run `bin/console` for an interactive prompt that will allow you to experiment.
`rake` will run Rubocop and the specs.
## Contributing
1. [Fork it](https://github.com/madeintandem/jsonb_accessor/fork)
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Add tests and changes (run the tests with `rake`)
4. Commit your changes (`git commit -am 'Add some feature'`)
5. Push to the branch (`git push origin my-new-feature`)
6. Create a new Pull Request
## Alternatives
- https://github.com/DmitryTsepelev/store_model 💪
- https://github.com/palkan/store_attribute ❤️
- https://github.com/jrochkind/attr_json 🤩
madeintandem-jsonb_accessor-7626103/Rakefile 0000664 0000000 0000000 00000001645 15157213547 0021012 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
require "rubygems"
require "bundler/setup"
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "rubocop/rake_task"
require "active_record"
require "erb"
RSpec::Core::RakeTask.new
RuboCop::RakeTask.new
# rubocop:disable Style/MixinUsage
include ActiveRecord::Tasks
# rubocop:enable Style/MixinUsage
root = File.expand_path __dir__
db_dir = File.join(root, "db")
DatabaseTasks.root = root
DatabaseTasks.db_dir = db_dir
DatabaseTasks.database_configuration = YAML.safe_load(ERB.new(File.read(File.join(db_dir, "config.yml"))).result, aliases: true)
DatabaseTasks.migrations_paths = [File.join(db_dir, "migrate")]
DatabaseTasks.env = "test"
task :environment do
ActiveRecord::Base.configurations = DatabaseTasks.database_configuration
ActiveRecord::Base.establish_connection DatabaseTasks.env.to_sym
end
load "active_record/railties/databases.rake"
task(default: %i[rubocop spec])
madeintandem-jsonb_accessor-7626103/UPGRADE_GUIDE.md 0000664 0000000 0000000 00000003773 15157213547 0021677 0 ustar 00root root 0000000 0000000 # Upgrading from 0.X.X to 1.0.0
## Jsonb Accessor declaration
In 0.X.X you would write:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
:count, # doesn't specify a type
title: :string,
external_id: :integer,
reviewed_at: :date_time, # snake cased
previous_rankings: :integer_array, # `:type_array` key
external_rankings: :array # plain array
end
```
In 1.0.0 you would write:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
count: :value, # all fields must specify a type
title: :string,
external_id: :integer,
reviewed_at: :datetime, # `:date_time` is now `:datetime`
previous_rankings: [:integer, array: true], # now just the type followed by `array: true`
external_rankings: [:value, array: true] # now the value type is specified as well as `array: true`
end
```
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`.
Also, in order to use the `value` type you need to register it:
```ruby
# in an initializer
ActiveRecord::Type.register(:value, ActiveRecord::Type::Value)
```
### Deeply nested objects
In 0.X.X you could write:
```ruby
class Product < ActiveRecord::Base
jsonb_accessor :data,
ranking_info: {
original_rank: :integer,
current_rank: :integer,
metadata: {
ranked_on: :date
}
}
end
```
Which would allow you to use getter and setter methods at any point in the structure.
```ruby
Product.new(ranking_info: { original_rank: 3, current_rank: 5, metadata: { ranked_on: Date.today } })
product.ranking_info.original_rank # 3
product.ranking_info.metadata.ranked_on # Date.today
```
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).
madeintandem-jsonb_accessor-7626103/bin/ 0000775 0000000 0000000 00000000000 15157213547 0020107 5 ustar 00root root 0000000 0000000 madeintandem-jsonb_accessor-7626103/bin/console 0000775 0000000 0000000 00000000673 15157213547 0021505 0 ustar 00root root 0000000 0000000 #!/usr/bin/env ruby
# frozen_string_literal: true
require "bundler/setup"
require "jsonb_accessor"
require "rspec"
require File.expand_path("../spec/spec_helper.rb", __dir__)
dbconfig = YAML.safe_load(ERB.new(File.read(File.join("db", "config.yml"))).result, aliases: true)
ActiveRecord::Base.establish_connection(dbconfig["test"])
# rubocop:disable Lint/UselessAssignment
x = Product.new
# rubocop:enable Lint/UselessAssignment
Pry.start
madeintandem-jsonb_accessor-7626103/bin/setup 0000775 0000000 0000000 00000000122 15157213547 0021170 0 ustar 00root root 0000000 0000000 #!/bin/bash
set -euo pipefail
IFS=$'\n\t'
bundle
rake db:create
rake db:migrate
madeintandem-jsonb_accessor-7626103/db/ 0000775 0000000 0000000 00000000000 15157213547 0017724 5 ustar 00root root 0000000 0000000 madeintandem-jsonb_accessor-7626103/db/config.yml 0000664 0000000 0000000 00000000320 15157213547 0021707 0 ustar 00root root 0000000 0000000 default: &default
adapter: postgresql
database: jsonb_accessor
host: <%= ENV.fetch("DATABASE_HOST") { "127.0.0.1" } %>
username: <%= ENV.fetch("DATABASE_USER") { "postgres" } %>
test:
<<: *default
madeintandem-jsonb_accessor-7626103/db/migrate/ 0000775 0000000 0000000 00000000000 15157213547 0021354 5 ustar 00root root 0000000 0000000 madeintandem-jsonb_accessor-7626103/db/migrate/20150407031737_set_up_testing_db.rb 0000664 0000000 0000000 00000001021 15157213547 0027043 0 ustar 00root root 0000000 0000000 # frozen_string_literal: true
class SetUpTestingDb < ActiveRecord::Migration[5.0]
def change
create_table :products do |t|
t.jsonb :options
t.jsonb :data
t.string :string_type
t.integer :integer_type
t.integer :product_category_id
t.boolean :boolean_type
t.float :float_type
t.time :time_type
t.date :date_type
t.datetime :datetime_type
t.decimal :decimal_type
end
create_table :product_categories do |t|
t.jsonb :options
end
end
end
madeintandem-jsonb_accessor-7626103/db/schema.rb 0000664 0000000 0000000 00000002524 15157213547 0021514 0 ustar 00root root 0000000 0000000 # This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# This file is the source Rails uses to define your schema when running `bin/rails
# db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to
# be faster and is potentially less error prone than running all of your
# migrations from scratch. Old migrations may fail to apply correctly if those
# migrations use external dependencies or application code.
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2015_04_07_031737) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "product_categories", id: :serial, force: :cascade do |t|
t.jsonb "options"
end
create_table "products", id: :serial, force: :cascade do |t|
t.jsonb "options"
t.jsonb "data"
t.string "string_type"
t.integer "integer_type"
t.integer "product_category_id"
t.boolean "boolean_type"
t.float "float_type"
t.time "time_type"
t.date "date_type"
t.datetime "datetime_type", precision: nil
t.decimal "decimal_type"
end
end
madeintandem-jsonb_accessor-7626103/json-bee.png 0000664 0000000 0000000 00000132001 15157213547 0021544 0 ustar 00root root 0000000 0000000 PNG
IHDR 6 tEXtSoftware Adobe ImageReadyqe<