Skip to content

marshall-lee/nested_record

Repository files navigation

NestedRecord

This gem is for mapping of json fields on ActiveModel objects!

Installation

Add this line to your application's Gemfile:

gem 'nested_record'

And then execute:

$ bundle

Or install it yourself as:

$ gem install nested_record

Usage

Use nested_record to define nested associations on ActiveRecord models via JSON attributes.

First add json column into your database:

change_table :users do |t|
  t.json :profile
end

Then define association using has_one_nested macro:

class User < ActiveRecord::Base
  include NestedRecord::Macro

  has_one_nested :profile
end

Or you can include the Macro globally:

class ApplicationRecord < ActiveRecord::Base
  include NestedRecord::Macro
end

class User < ApplicationRecord
  has_one_nested :profile
end

Define nested record attributes using ActiveModel::Attributes API (since Rails 5.2):

class Profile < NestedRecord::Base
  attribute :age,    :integer
  attribute :active, :boolean
  has_one_nested :contacts
end

You can go deeper and define models on the next nesting level:

class Profile::Contacts < NestedRecord::Base
  attribute :email, :string
  attribute :phone, :string
end

You can store a collection of objects with has_many_nested:

class Profile::Contacts < NestedRecord::Base
  attribute :email, :string
  attribute :phone, :string
  has_many_nested :socials
end

class Profile::Social < NestedRecord::Base
  attribute :name
  attribute :url
end

user.profile.age = 39
user.profile.contacts.email = '[email protected]'
user.profile.contacts.socials[0].name # => 'facebook'

You can assign attributes in the way like accepts_nested_attributes_for macros provides for AR models:

user.profile_attributes = {
  age: 39,
  contacts_attributes: {
    email: '[email protected]',
    socials_attributes: [
      { name: 'facebook', url: 'facebook.example.com/johndoe' },
      { name: 'twitter', url: 'twitter.example.com/johndoe' }
    ]
  }
}

Advanced usage

nested_record can do many different things for you!

Validations

Every NestedRecord::Base descendant in fact is an ActiveModel::Model so standard validations are also supported.

class Profile < NestedRecord::Base
  attribute :age,    :integer
  attribute :active, :boolean

  validates :age, presence: true
end

Don't bother with defining record micro-classes

If you want so, you can rewrite the above example this way:

class User < ApplicationRecord
  has_one_nested :profile do
    attribute :age, :integer
    attribute :active, :boolean
    has_one_nested :contacts do
      attribute :email, :string
      attribute :phone, :string
      has_many_nested :socials do
        attribute :name
        attribute :url
      end
    end
  end
end

Record classes then available under local types namespace module e.g. User::LocalTypes::Profile.

Concerns

Common attributes, validations and other settings can be DRY-ed to modules called concerns.

module TitleAndDescription
  extend NestedRecord::Concern
  
  attribute :title
  attribute :description
  
  validates :title, presence: true
end

class Article < NestedRecord::Base
  has_one_nested :foo do
    include TitleAndDescription
  end
end

:class_name option

By default, class name of nested record is automatically inferred from the association name but of course it's all customizable. There's a :class_name option for this!

Depending on what form do you use — has_* :foo or has_* :foo do ... end, the :class_name option means different things.

:class_name option when referring an external model

In a non-&block form, the :class_name behaves similar to the option with same name in ActiveRecord's has_one/has_many associations.

class User < ApplicationRecord
  has_one_nested :profile, class_name: 'SomeNamespace::Profile'
end

class SomeNamespace::Profile < NestedRecord::Base
  attribute :age, :integer
  attribute :active, :boolean
end

:class_name option when using with an embedded local types

When record definition is embedded, :class_name option denotes the name of the class in local types namespace module under which it's defined.

class User < ApplicationRecord
  has_one_nested :profile, class_name: 'ProfileRecord' do
    attribute :age, :integer
    attribute :active, :boolean
  end
end

Then the profile model is available under User::LocalTypes::ProfileRecord name.

You can also disable the const naming at all, passing class_name: false. In this case, the local type is anonymous so no constant in local types namespace is set.

class_name: true (the default) means infer the class name from association name e.g. User::LocalTypes::Profile constant is set by default.

nested_accessors

This is the store_accessor on steroids! Unlike store_accessor it's support nesting, type coercions and all other things this library can do. Think of it as a has_one_nested association with accessors lifted up one level.

class User < ApplicationRecord
  nested_accessors from: :profile do
    attribute :age, :integer
    attribute :active, :integer
  end
end

user = User.new
user.age = 33
user.active = true

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake spec to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/marshall-lee/nested_record.

License

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

About

Map JSON fields on ActiveModel objects

Resources

License

Stars

Watchers

Forks

Packages

No packages published