Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

like image 458
Zack Xu Avatar asked Nov 22 '12 18:11

Zack Xu


People also ask

Is it possible to implement serialization in activemodel?

ActiveModel::Serializers::JSON module automatically includes the ActiveModel::Serialization module, so there is no need to explicitly include ActiveModel::Serialization. A minimal implementation including JSON would be: Which would provide you with:

What is update_attributes in Rails 3?

This method used to be called update_attributes in Rails 3. It changes the attributes of the model, checks the validations, and updates the record in the database if it validates. Note that just like update_attribute this method also saves other changed attributes to the database.

How do I change an attribute without validating the model?

def name= (new_name) write_attribute ( :name, new_name.upcase) # This is equivalent: # self [:name] = new_name.upcase end This method will change the attribute in the model and pass it straight to the database, without running any validations. Any other changed attributes are also saved to the database.


2 Answers

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]
like image 188
jdoe Avatar answered Oct 08 '22 14:10

jdoe


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.

like image 33
Zack Xu Avatar answered Oct 08 '22 13:10

Zack Xu