Jon Snow
Jon Snow

Reputation: 11882

How to show a serialized Array attribute for a Rails ActiveRecord Model in a form?

We're using the "serialize" feature of ActiveRecord in Rails like this:

class User < ActiveRecord::Base
    serialize :favorite_colors, Array
    ....
end

So we can have

u = User.last
u.favorite_colors = [ 'blue', 'red', 'grey' ]
u.save!

So basically ActiveRecord is serializing the array above and stores it in one database field called favorite_colors.

My question is: How do you allow a user to enter his favorite colors in a form? Do you use a series of textfields? And once they're entered, how do you show them in a form for him to edit?

This is a question related to Rails Form Helpers for serialized array attribute.

Thanks

Upvotes: 10

Views: 9235

Answers (3)

Jon Snow
Jon Snow

Reputation: 11882

I have solved this problem by 'flattening' the array in the view and reconstituting the array in the controller.

Some changes are needed in the model too, see below.

class User < ActiveRecord::Base

    serialize :favorite_colors, Array

    def self.create_virtual_attributes (*args)
        args.each do |method_name|
            10.times do |key|
                define_method "#{method_name}_#{key}" do
                end
                define_method "#{method_name}_#{key}=" do
                end
            end
        end
    end

    create_virtual_attributes :favorite_colors
end

If you don't define methods like the above, Rails would complain about the form element's names in the view, such as "favorite_colors_0" (see below).

In the view, I dynamically create 10 text fields, favorite_colors_0, favorite_colors_1, etc.

<% 10.times do |key| %>
    <%= form.label :favorite_color %>
    <%= form.text_field "favorite_colors_#{key}", :value => @user.favorite_colors[key] %>
<% end %>

In the controller, I have to merge the favorite_colors_* text fields into an array BEFORE calling save or update_attributes:

unless params[:user].select{|k,v| k =~ /^favorite_colors_/}.empty?
    params[:user][:favorite_colors] = params[:user].select{|k,v| k =~ /^favorite_colors_/}.values.reject{|v| v.empty?}
    params[:user].reject! {|k,v| k=~ /^favorite_colors_/}
end

One thing I'm doing is to hard-code 10, which limits how many elements you can have in the favorite_colors array. In the form, it also outputs 10 text fields. We can change 10 to 100 easily. But we will still have a limit. Your suggestion on how to remove this limit is welcome.

Hope you find this post useful.

Upvotes: 3

jdoe
jdoe

Reputation: 15771

If you want multi-select HTML field, try:

= form_for @user do |f|
  = f.select :favorite_colors, %w[full colors list], {}, :multiple => true

If you're using simple_form gem, you can present the options as check boxes easily:

= simple_form_for @user do |f|
  = f.input :favorite_colors, as: :check_boxes, collection: %w[full colors list]

Upvotes: 4

awenkhh
awenkhh

Reputation: 6111

To allow access to AR attributes, you have to grant them like this:

class User < ActiveRecord::Base
  serialize :favorite_colors, Array
  attr_accessible :favorite_colors
  ....
end

Upvotes: 2

Related Questions