0.0
No commit activity in last 3 years
No release in over 3 years
This gem extends ActiveRecord to add additional functionality to JSONB - use PostgreSQL JSONB data for associations - thread-safe single-key updates to JSONB columns using `jsonb_set` - extended `table#references` for easy migrations and indexes - virtual JSONB foreign keys using check constraints (NOTE: real foreign key constraints are not possible with PostgreSQL JSONB) Inspired by activerecord-jsonb-associations, but for use in Rails 6+ and ruby 2.7+ and with some unnecessary options and features (HABTM) removed and some additional features added
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
 Dependencies

Development

~> 3.9

Runtime

~> 0.1, >= 0.1.0
~> 1.1, >= 1.1.2
~> 2, >= 2.2.2
~> 6.1.4, >= 6.1.4.1
 Project Readme

associate_jsonb

Gem Version

Easy PostgreSQL JSONB extensions

including:

  • Basic ActiveRecord Associations using PostgreSQL JSONB columns, with built-in accessors and column indexes
  • Thread-Safe JSONB updates (well, as safe as they can be) using a custom nested version of jsonb_set (jsonb_nested_set)

Requirements:

  • PostgreSQL (>= 12)
  • Rails 6.0.3.2

Installation

Add this line to your application's Gemfile:

gem 'associate_jsonb'

And then execute:

$ bundle install

Usage

Jsonb Associations

One-to-One and One-to-Many associations

To set up your jsonb column, you can use the built in add_reference/table.references function. This will only add a new store column if it doesn't already exist

rails g migration add_foreign_key_store_to_my_table
class AddForeignKeyStoreToMyTable < ActiveRecord::Migration[6.0]
  def change
    add_reference :my_table, :user, store: :extra # => store created
    add_reference :my_table, :label, store: :extra, null: false # => store already exists, NOT NULL check constraint added to `store->'label_id'`
    # NOTE: you can also use a `change_table(:my_table) block`
  end
end

and

class CreateMyTable < ActiveRecord::Migration[6.0]
  def change
    create_table(:my_table) do |t|
      t.references :user, store: :extra
      t.references :label, store: :extra, null: false
    end
  end
end

If you add the jsonb_foreign_key function to your database, you can also create a foreign_key check constraint by using the same built-in :foreign_key option used in normal reference definitions.

NOTE: true foreign key references are not possible with jsonb attributes. This will instead create a CHECK constraint that looks for the referenced column using an EXISTS statement

rails g migration add_jsonb_foreign_key_function
class AddJsonbForeignKeyFunction < ActiveRecord::Migration[6.0]
  def up
    add_jsonb_foreign_key_function
  end
end
class CreateMyTable < ActiveRecord::Migration[6.0]
  def change
    create_table(:my_table) do |t|
      t.references :user, store: :extra, foreign_key: true, null: false
    end
  end
end
class CreateMyTable < ActiveRecord::Migration[6.0]
  def change
    create_table(:my_table) do |t|
      t.references :person, store: :extra, foreign_key: { to_table: :users }, null: false
    end
  end
end

You can store all foreign keys of your model in one JSONB column, without having to create multiple columns:

class Profile < ActiveRecord::Base
  # Setting additional :store option on :belongs_to association
  # enables saving of foreign ids in :extra JSONB column
  belongs_to :user, store: :extra
end

class SocialProfile < ActiveRecord::Base
  belongs_to :user, store: :extra
end

class User < ActiveRecord::Base
  # Parent model association needs to specify :foreign_store
  # for associations with JSONB storage
  has_one :profile, foreign_store: :extra
  has_many :social_profiles, foreign_store: :extra
end

Many-to-Many associations

Due to the ease of getting out-of-sync, and the complexity needed to build it, HABTM relation functionality has not been implemented through JSONB

jsonb_set based hash updates

When enabled, only keys present in the updated hash and with values changed in memory will be updated. To completely delete a key/value pair from an enabled attribute, set the key's value to nil.

e.g.

# given: instance#data == { "key_1"=>1,
#                           "key_2"=>2,
#                           "key_3"=> { "key_4"=>7,
#                                       "key_5"=>8,
#                                       "key_6"=>9 } }

instance.update({ key_1: "asdf", a: 1, key_2: nil, key_3: { key_5: nil }})

# instance#data => { "key_1"=>"asdf",
#                    "a"=>"asdf",
#                    "key_3"=> { "key_4"=>7,
#                                "key_6"=>9 } }

enabling/adding attribute types

first, create the sql function

rails g migration add_jsonb_nested_set_function
class AddJsonbNestedSetFunction < ActiveRecord::Migration[6.0]
  def up
    add_jsonb_nested_set_function
  end
end

then in an initializer, enable key based updates:

# config/initializers/associate_jsonb.rb
AssociateJsonb.enable_jsonb_set
  • Key based updates rely on inheritance for allowed attribute types. Any attributes that respond true to attr_type.is_a?(GivenClass) for any enabled type classes will use jsonb_nested_set

  • To add classes to the enabled list, pass them as arguments to AssociateJsonb.add_hash_type(*klasses). Any arguments passed to AssociateJsonb.enable_jsonb_set are forwarded to AssociateJsonb.add_hash_type

  • By default, calling AssociateJsonb.enable_jsonb_set(*klasses) without arguments, and no classes previously added, adds ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Jsonb to the allowed classes list

disabling/removing attribute types

  • by default jsonb_nested_set updates are disabled.

  • if you've enabled them and need to disable, use: AssociateJsonb.disable_jsonb_set

  • To remove a class from the allowed list while leaving nested set updates enabled, use AssociateJsonb.remove_hash_type(*klasses). Any arguments passed to AssociateJsonb.disable_jsonb_set are forwarded to AssociateJsonb.remove_hash_type

Automatically delete nil value hash keys

When jsonb_set updates are disabled, jsonb columns are replaced with the current document (i.e. default rails behavior)

You are also given the option to automatically clear nil/null values from the hash automatically when jsonb_set is disabled

in an initializer:

# config/initializers/associate_jsonb.rb
AssociateJsonb.jsonb_delete_nil = true

Rules for classes to which this applies are the same as for jsonb_nested_set; add and remove classes through AssociateJsonb.(add|remove)_hash_type(*klasses)

Developing

To setup development environment, run:

$ bin/setup

To run specs:

$ bundle exec rspec

License

The gem is available as open source under the terms of the MIT License.