-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Nested inputs for key value hash attributes
This details how to create for a inputs for a Postgres JSON or JSONB column. JSON and JSONB support was added in Rails 4.2+.
Lets say you have a Post
model with a JSON or JSONB column custom_fields
.
Rails accepts attributes for JSON columns as a hash:
{
post: {
title: "Nested inputs for key/value hash attributes."
custom_fields: {
hash_tags: "#rails,#simple_form,#postgres"
}
}
}
If this was a nested model we could have added out inputs by:
<%= simple_form_for(@post) do |f|
f.input :title
f.simple_fields_for(@post.custom_fields) do |cf|
cf :hash_tags
end
end %>
But this fails since our lowly hash does not respond to model_name
which is part of the
ActiveModel api.
cf :hash_tags
will also not work since our hash does not respond to hash_tags
.
The solution is to dig out the old proxy or delegator pattern in the form of a decorator:
class CustomFieldsDecorator
# @see http://api.rubyonrails.org/classes/ActiveModel/Naming.html
MODEL_NAME = ActiveModel::Name.new(self.class, nil, 'custom_fields')
def model_name
MODEL_NAME
end
def initialize(hash)
@object = hash.symbolize_keys
end
# Delegates to the wrapped object
def method_missing(method, *args, &block)
if @object.key? method
@object[method]
elsif @object.respond_to? method
@object.send(method, *args, &block)
end
end
def has_attribute? attr
@object.key? attr
end
end
Since our Decorator delegates to the hash we can call enumerable methods.
<% custom_fields = CustomFieldsDecorator.new(@post.custom_fields) %>
<%= simple_form_for(@post) do |f|
f.input :title
f.simple_fields_for(custom_fields) do |field|
custom_fields.each do |key, value|
field.input key
end
end
end %>
Solution 2: This solution, as proposed, works when you have a limited and known JSON objects. Create a Serializer like this:
# app/serializers/hash_serializer.rb
class HashSerializer
def self.dump(hash)
hash.to_json
end
def self.load(hash)
(hash || {}).with_indifferent_access
end
end
In order to access JSON object as symbols and add the following to the model:
class Post < ActiveRecord::Base
serialize :preferences, HashSerializer
store_accessor :preferences, :blog, :github, :twitter
end
It is now possible to directly access preferences
objects like blog
or twitter
: post.blog
, post.twitter
, etc.
PS: make sure you have active_model_serializers
gem
Solution 3:
It is something that sits between #1 and #2. Basically, it uses OpenStruct in the view and works well with known keys.
<%= simple_form_for(@post) do |f|
f.input :title
f.simple_fields_for :custom_fields, OpenStruct.new(@post.custom_fields) do |field|
field.input :twitter
field.input :github
end
end %>
This page was created by the OSS community and might be outdated or incomplete. Feel free to improve or update this content according to the latest versions of SimpleForm and Rails to help the next developer who visits this wiki after you.
Keep in mind to maintain the guides as simple as possible and to avoid additional dependencies that might be specific to your application or workflow (such as Haml, RSpec, Guard and similars).