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
endThen define association using has_one_nested macro:
class User < ActiveRecord::Base
include NestedRecord::Macro
has_one_nested :profile
endOr you can include the Macro globally:
class ApplicationRecord < ActiveRecord::Base
include NestedRecord::Macro
end
class User < ApplicationRecord
has_one_nested :profile
endDefine 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
endYou can go deeper and define models on the next nesting level:
class Profile::Contacts < NestedRecord::Base
attribute :email, :string
attribute :phone, :string
endYou 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 = 'john@doe.com'
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: 'john@doe.com',
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
endDon'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
endRecord 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
endThen 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 = trueDevelopment
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.