My goal is to enable a user to be able to submit multiple NewsImages from a parent Blog form.
My Blog model looks like this:
# == Schema Information
# Schema version: 20091006171847
#
# Table name: blogs
#
# id :integer(4) not null, primary key
# title :string(255)
# body :text
# profile_id :integer(4)
# created_at :datetime
# updated_at :datetime
#
class Blog < ActiveRecord::Base
has_many :comments, :as => :commentable, :order => "created_at asc"
has_many :news_images, :dependent => :destroy
belongs_to :profile
accepts_nested_attributes_for :news_images
validates_presence_of :title, :body
attr_immutable :id, :profile_id
def after_create
feed_item = FeedItem.create(:item => self)
([profile] + profile.friends + profile.followers).each{ |p| p.feed_items << feed_item }
end
def to_param
"#{self.id}-#{title.to_safe_uri}"
end
end
My NewsImage model looks like this:
class NewsImage < ActiveRecord::Base
belongs_to :blog
validates_each :blog do |news_image, attr, value|
news_item.errors.add attr, "You are only limited to 5 images." if news_item.blog.news_items.size >= 5
end
has_attached_file :image, :styles => { :original => "1920x1080>", :square => "158x158#", :thumb => "386x155#", :slide_thumb => "165x67#"},
:url => "/system/:attachment/:id/:style/:basename.:extension",
:path => ":rails_root/public/system/:attachment/:id/:style/:basename.:extension"
def validate
dimensions = Paperclip::Geometry.from_file(self.image.queued_for_write[:original])
self.errors.add(:image, "Please upload an image that is at least 476 pixels wide") if dimensions.width < 476
self.errors.add(:image, "Please upload an image that is at least 319 pixels high") if dimensions.height < 319
end
end
My Blog controller looks like this:
class BlogsController < ApplicationController
skip_filter :login_required, :only => [:index, :show]
prepend_before_filter :get_profile
before_filter :setup
def index
if @p && @p == @profile && @p.blogs.empty?
flash[:notice] = 'You have not create any blog posts. Try creating one now.'
redirect_to new_profile_blog_path(@p) and return
end
respond_to do |wants|
wants.html {render}
wants.rss {render :layout=>false}
end
end
def create
@blog = @p.blogs.build params[:blog]
respond_to do |wants|
if @blog.save
wants.html do
flash[:notice] = 'New blog post created.'
redirect_to profile_blogs_path(@p)
end
else
wants.html do
flash.now[:error] = 'Failed to create a new blog post.'
render :action => :new
end
end
end
end
def show
render
end
def edit
render
end
def update
respond_to do |wants|
if @blog.update_attributes(params[:blog])
wants.html do
flash[:notice]='Blog post updated.'
redirect_to profile_blogs_path(@p)
end
else
wants.html do
flash.now[:error]='Failed to update the blog post.'
render :action => :edit
end
end
end
end
def destroy
@blog.destroy
respond_to do |wants|
wants.html do
flash[:notice]='Blog post deleted.'
redirect_to profile_blogs_path(@p)
end
end
end
protected
def get_profile
@profile = Profile[params[:profile_id]]
end
def setup
@user = @profile.user
@blogs = @profile.blogs.paginate(:page => @page, :per_page => @per_page)
if params[:id]
@blog = Blog[params[:id]]
else
@blog = Blog.new
3.times {@blog.news_images.build }
end
end
def allow_to
super :owner, :all => true
super :all, :only => [:index, :show]
end
end
My form currently is this:
<%
#locals
blog ||= @blog
%>
<div id="blog">
<% less_form_for [@profile, blog], :html => {:multipart => true} do |f| %>
<%= f.text_field :title %>
<%= f.text_area :body %>
<% f.fields_for :news_images do |builder| %>
<%= builder.label :caption %>
<%= builder.text_field :caption %>
<%= builder.label :image %>
<%= builder.file_field :image %>
<% end %>
To include a youtube video use: [youtube: address_of_video]
<div class="row button">
<%= f.submit 'Save', :class=>'button' %>
</div>
<% end %>
</div>
I keep getting the following error:
ActionView::TemplateError (`@blog[news_images_attributes][0]' is not allowed as an instance variable name) on line #12 of app/views/blogs/_form.html.erb:
If I remove:
<%= builder.text_field :caption %>
the error goes away. In fact if i change the text field to a file field the error goes away.
I'm really confused here as I'm not sure why one form helper works fine and the other doesn't.
Thank you very much for looking =)
Update:
This is my less_form_builder.rb file:
class LessFormBuilder < ActionView::Helpers::FormBuilder
include ActionView::Helpers::ActiveRecordHelper
def method_missing *args
options = args.extract_options!
label = get_label '', options
front(label) + super(*args) + back(label)
end
def wrap method, options = {}
s = front(method, options)
s += yield if block_given?
s += back(method, options)
end
# Generates a label
#
# If +options+ includes :for,
# that is used as the :for of the label. Otherwise,
# "#{this form's object name}_#{method}" is used.
#
# If +options+ includes :label,
# that value is used for the text of the label. Otherwise,
# "#{method titleized}: " is used.
def label method, options = {}
text = options.delete(:label) || "#{method.to_s.titleize}: "
if options[:for]
"<label for='#{options.delete(:for)}'>#{text}</label>"
else
#need to use InstanceTag to build the correct ID for :for
ActionView::Helpers::InstanceTag.new(@object_name, method, self, @object).to_label_tag(text, options)
end
end
def select method, options = {}
front(method, options) + super + back(method, options)
end
def text_field method, options = {}
front(method, options) + super + back(method, options)
end
def password_field method, options = {}
front(method, options) + super + back(method, options)
end
def text_area method, options = {}
front(method, options) + super + back(method, options)
end
def check_box method, options = {}
front(method, options) + super + back(method, options)
end
def calendar_field method, options = {}
expired = options.delete(:expired) || false
if not expired; options.merge!(:class => 'calendar'); else; options.merge!(:disabled => true); end
text_field method, options
end
def front method = '', options = {}
"<div class='row clear'>#{label(method, options)}"
end
def back method = '', options = {}
"#{error_messages_on( object_name, method ) unless method.blank?}
<div class='clear'></div>
</div>"
end
end