RailsCasts - Ruby on Rails Screencasts

RailsCasts Pro episodes are now free!

Learn more or hide this

Virtual Attributes (revised)

#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.
Click to Play Video ▶
Tweet
  • Download:
  • source code Project Files in Zip (66.3 KB)
  • mp4 Full Size H.264 Video (25.9 MB)
  • m4v Smaller H.264 Video (13.2 MB)
  • webm Full Size VP8 Video (16.3 MB)
  • ogv Full Size Theora Video (31.3 MB)
Browse_code Browse Source Code

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
loading

AltStyle によって変換されたページ (->オリジナル) /