#16 Virtual Attributes (revised)
Jul 20, 2012 | 11 minutes | Active Record, Forms
Virtual attributes are a clean way to add form fields that do not map directly to the database. Here I show how to handle validations, associations, and more.
- Download:
- source codeProject Files in Zip (66.3 KB)
- mp4Full Size H.264 Video (25.9 MB)
- m4vSmaller H.264 Video (13.2 MB)
- webmFull Size VP8 Video (16.3 MB)
- ogvFull Size Theora Video (31.3 MB)
Resources
views/products/_form.html.erb
<div class="field"> <%= f.label :name %><br /> <%= f.text_field :name %> </div> <div class="field"> <%= f.label :price_in_dollars %><br /> <%= f.text_field :price_in_dollars %> </div> <div class="field"> <%= f.label :released_at_text, "Release Date & Time" %><br /> <%= f.text_field :released_at_text %> </div> <div class="field"> <%= f.label :category_id %><br /> <%= f.collection_select :category_id, Category.order(:name), :id, :name %> or create one: <%= f.text_field :new_category %> </div> <div class="field"> <%= f.label :tag_names, "Tags (space separated)" %><br /> <%= f.text_field :tag_names %> </div>
models/product.rb
class Product < ActiveRecord::Base attr_accessible :name, :price_in_dollars, :released_at_text, :category_id, :new_category, :tag_names belongs_to :category has_many :taggings has_many :tags, through: :taggings attr_writer :released_at_text, :tag_names attr_accessor :new_category validate :check_released_at_text before_save :save_released_at_text before_save :create_category before_save :save_tag_names def price_in_dollars price_in_cents.to_d/100 if price_in_cents end def price_in_dollars=(dollars) self.price_in_cents = dollars.to_d*100 if dollars.present? end def released_at_text @released_at_text || released_at.try(:strftime, "%Y-%m-%d %H:%M:%S") end def save_released_at_text self.released_at = Time.zone.parse(@released_at_text) if @released_at_text.present? end def check_released_at_text if @released_at_text.present? && Time.zone.parse(@released_at_text).nil? errors.add :released_at_text, "cannot be parsed" end rescue ArgumentError errors.add :released_at_text, "is out of range" end def create_category self.category = Category.create!(name: new_category) if new_category.present? end def tag_names @tag_names || tags.pluck(:name).join(" ") end def save_tag_names if @tag_names self.tags = @tag_names.split.map { |name| Tag.where(name: name).first_or_create! } end end end