I am experiencing problems with undefined method `to_key' for, on a form for polymorphic upload.
This is the form partial:
<%= form_for [#parent, Upload], :html => { :multipart => true } do |f| %>
<div class="field">
<%= f.label :document %><br />
<%= f.file_field :document %>
</div>
<div class="actions">
<%= f.submit "Upload"%>
</div>
<% end %>
This is the controller:
class UploadsController < ApplicationController
before_filter :find_parent
respond_to :html, :js
def index
#uploads = #parent.uploads.all unless #uploads.blank?
respond_with([#parent, #uploads])
end
def new
#upload = #parent.uploads.new unless #uploads.blank?
end
def show
#upload = #parent.upload.find(params[:upload_id])
end
def create
# Associate the correct MIME type for the file since Flash will change it
if params[:Filedata]
#upload.document = params[:Filedata]
#upload.content_type = MIME::Types.type_for(#upload.original_filename).to_s
#upload = #parent.uploads.build(params[:upload])
if #upload.save
flash[:notice] = "suceessfully saved upload"
redirect_to [#parent, :uploads]
else
render :action => 'new'
end
end
end
def edit
#upload = Upload.where(params[:id])
end
private
def find_parent
classes ||= []
params.each do |name ,value|
if name =~ /(.*?)_id/
#parent = classes << $1.pluralize.classify.constantize.find(value)
end
end
return unless classes.blank?
end
end
If i change
<%= form_for [#parent, Upload], :html => { :multipart => true } do |f| %>
to
<%= form_for [parent, Upload], :html => { :multipart => true } do |f| %>
I get a new error: undefined local variable or method `parent' for #<#:0x21a30e0>
This is the error trace:
ActionView::Template::Error (undefined method `to_key' for #<Class:0x2205e88>):
1: <%= render :partial => "uploads/uploadify" %>
2:
3: <%= form_for [#parent, Upload], :html => { :multipart => true } do |f| %>
4:
5:
6: <div class="field">
The "uploads/uploadify" partial is in this gist: https://gist.github.com/911635
Any pointers will be helpful. Thanks
From what I can see, your form_for should be something along the lines of
<%= form_for [#parent, #upload], :html => { :multipart => true } do |f| %>
as I'm assuming your upload object is nested within another object, similar to the following:
resources :posts do
resources :uploads
end
What form_for does when passed an array like this is construct the relevant path based on the class of the given objects and whether they are new records.
In your case, you create a new upload object in the new action of your controller, so form_for will inspect the array, get the class and id of #parent, then get the class and id of #upload. However, because #upload has no id, it will POST to /parent_class/parent_id/upload instead of PUTting to parent_class/parent_id/upload/upload_id.
Let me know if that doesn't work and we'll figure it out further :)
-- EDIT - after comments --
This means that one of #parent or #upload is nil. To check, you can put the following in your view
<%= debug #parent %>
and the same for #upload and see which is nil. However, I'm guessing that #upload is nil, because of this line in your controller:
# UploadsController#new
#upload = #parent.uploads.new unless #uploads.blank?
specifically the unless #uploads.blank? part. Unless you initialize it in the ApplicationController, #uploads is always nil, which means #uploads.blank? will always be true, which in turn means #upload will never be initialized. Change the line to read
#upload = #parent.uploads.new
and the problem will hopefully be resolved. The same is true of the other methods where you have used unless #uploads.blank?.
On a semi-related note, in UploadsController#find_parent, you have this line
classes ||= []
because the variable is local to the find_parent method, you can be assured that it is not initialized, and should rather write classes = [].
Also, you have this line of code
return unless classes.blank?
right before the end of the method. Did you add that in so that you return from the method once #parent has been initialized? If so, that line should be inside the each block.
Further, since classes isn't used outside of the method, why define it at all? The code could read as follows and still have the same behaviour
def find_parent
params.each do |name ,value|
#parent = $1.pluralize.classify.constantize.find(value) if name =~ /(.*?)_id/
return if #parent
end
end
Amongst other things, you'll see that this does a few things:
Avoids initializing a variable that is not needed.
Inlines the if statement, which helps readability for single line conditionals
Changes use of unless variable.blank to if variable. Unless your variable is a boolean, this accomplishes the same thing, but reduces the cognitive load, as the former is essentially a double negative which your brain has to parse.
-- EDIT - from email exchange about the issue --
You are correct - if #parent will return true if parent is initialized. As I mentioned on SO however, the exception to this is if #parent is initialized and set to false. Essentially what it means is that in Ruby, all values except nil and false are considered true. When an instance variable has not been initialized, it's default value is nil, which is why that line of code works. Does that make sense?
In terms of setting #parent in each action that renders form in the UsersController, which of these is the correct way to do this on the index action. I have tried all 3 but got errors
Remember that both #parent and #upload must be instances of ActiveRecord (AR) objects. In the first case, you set #parent to User.all, which is an array of AR objects, which will not work. Also, you try to call #parent.uploads before #parent is initialized, which will give a no method error. However, even if you were to swap the two lines around, you are calling #parent.uploads when parent is an array. Remember that the uploads method is defined on individual AR objects, and not on an array of them. Since all three of your implementations of index do similar things, the above caveats apply to all of them in various forms.
users_controller.rb
def index
#upload = #parent.uploads
#parent = #user = User.all
end
or
def index
# #user = #parent.user.all
#parent = #user = User.all
end
or
def index
#parent = #upload = #parent.uploads
#users = User.all
end
I'll quickly walk you through the changes I made. Before I start, I should explain that this
<%= render "partial_name", :variable1 => a_variable, :variable2 => another_variable %>
is equivalent to doing this
<%= render :partial => "partial_name", :locals => {:variable1 => a_variable, :variable2 => another_variable} %>
and is just a shorter (and somewhat cleaner) way of rendering. Likewise, in a controller, you can do
render "new"
instead of
render :action => "new"
You can read more about this at http://guides.rubyonrails.org/layouts_and_rendering.html Now on to the code.
#app/views/users/_form.html.erb
<%= render :partial => "uploads/uploadify" %>
<%= form_for [parent, upload], :html => { :multipart => true } do |f| %>
<div class="field">
<%= f.label :document %><br />
<%= f.file_field :document %>
</div>
<div class="actions">
<%= f.submit "Upload"%>
</div>
<%end%>
On the uploads form, you'll see that I changed #parent and #upload to parent and upload. This means you need to pass the variables in when you render the form instead of the form looking for instance variable set by the controller. You'll see that this allows us to do the following:
#app/views/users/index.html.erb
<h1>Users</h1>
<table>
<% #users.each do |user| %>
<tr>
<td><%= link_to user.email %></td>
<td><%= render 'uploads/form', :parent => user, :upload => user.uploads.new %></td>
</tr>
<% end %>
</table>
Add an upload form for each user in UsersController#index. You'll notice that because we now explicitly pass in parent and upload, we can have multiple upload forms on the same page. This is a much cleaner and more extensible approach to embedding partials, as it becomes immediately obvious what parent and upload are being set to. With the instance variable approach, people unfamiliar with the code base might struggle to determine where #parent and #upload are being set, etc.
#app/views/users/show.html.erb
<div>
<% #user.email %>
<h3 id="photos_count"><%= pluralize(#user.uploads.size, "Photo")%></h3>
<div id="uploads">
<%= image_tag #user.upload.document.url(:small)%>
<em>on <%= #user.upload.created_at.strftime('%b %d, %Y at %H:%M') %></em>
</div>
<h3>Upload a Photo</h3>
<%= render "upload/form", :parent => #user, :upload => user.uploads.new %>
</div>
This is similar to the changes above, where we pass in the parent and upload objects.
#config/routes.rb
Uploader::Application.routes.draw do
resources :users do
resources :uploads
end
devise_for :users
resources :posts do
resources :uploads
end
root :to => 'users#index'
end
You'll see that I removed uploads as a top level resources in the routes. This is because uploads requires a parent of some sort, and so cannot be top level.
#app/views/uploads/new.html.erb
<%= render 'form', :parent => #parent, :upload => #upload %>
I made the same changes as above, passing parent and upload through explicitly. You'll obviously need to do this wherever you render the form.
#app/controllers/users_controller.rb
class UsersController < ApplicationController
respond_to :html, :js
def index
#users = User.all
end
def show
#user = User.find(params[:id])
end
def new
#user = User.new
end
def create
#user = User.new(params[:user])
if #user.save
redirect_to users_path
else
render :action => 'new'
end
end
def update
#user = User.find_by_id(params[:id])
#user.update_attributes(params[:user])
respond_with(#user)
end
def destroy
#user = User.find_by_id(params[:id])
#user.destroy
respond_with(#user)
end
end
I've removed any mention of #parent from the user controller, as we pass it through explicitly.
Hopefully that all makes sense. You can extrapolate from these examples and pass through the parent and upload object wherever you want to render an upload form.
[#parent, Upload] => [#parent, :upload]
<%= form_for [#parent, :upload], :html => { :multipart => true } do |f| %>
UPD
You should change places :upload and #parent
<%= form_for [:upload, #parent], :html => { :multipart => true } do |f| %>
Related
So I have a model called City and it has_many :places and it accepts_nested_attributes_for :places. Each Place belongs_to :category. When I render a form for a City I have f.fields_for :places do |place| and I do it like this:
<% f.fields_for :places do |place| %>
<%= render "place_fields", :f => place
<% end %>
My _place_fields.html.erb contains the folowing:
<div class="place_header"><%= f.object.category.name %></div>
<div><%= f.label :name %>: <%=f.text_field :name %> </div>
<div><%= f.text_area :description %> </div>
But the problem apears when I try to add a new place. First of all I want to bring up a simple select form to select a category for the new place, and then render that same partial based on the category_id.
I do that inside the same action:
def add_place
if params[:category_id]
#place = Place.new(:category_id => params[:category_id])
respond_to do |format|
format.html { return nil }
format.js {
#here comes the render
}
end
else
render_message :header => "Choose category", :partial => "category_select", :over => 10
end
end
But if I try to do $("#places_tab").append("<%= escape_javascript(render :partial => "place_fields", :f => #place %>"); it gives an error, wich is expected.
Once again: I need to render the fields for that new Place and just don't know how to do that.
UPDATE
Received some advice on passing the original City Formbuilder to the action and rendering that Place right from that builder, but don't have any idea of how to do that.
The problem is that you are passing an instance of the Place model (#place) as the form builder instead of the form builder itself.
I have an rails 3 application where there are multiple registrations (diagnosis, patient, laboratory test, service, client, user, supplier). Initially these will be populated by seeding the database. The requirement is for the description codes to be mixed case (capitalised first word) when either
1. specified by the application (some configuration setting - yet to be determined)
2. specified by data entry user
At present I have a model, view & controller for Diagnosis which contains two fields:
1. code (always to be capitalised)
2. description (First word capitalised based on check_box_tag value)
Presently I am using a before_save callback in the model to implement the conversion, but I cannot get it to only work when the check_box_tag is not selected i.e. its ignoring the check_box_tag.
I have tried changing the check_box_tag to a check_box adding an attr_assessor to the model (but not the sqlite3 db as it is not required to be stored).
This didn't work either.
How do I accomplish this? How do I override the option to use a checkbox from an internal application configuration file which results in either the checkbox being 'unavailable' or not visible if the application configuration specifies not user selectable?
Model (diagnosis.rb)
require 'DescriptionHelper'
class Diagnosis < ActiveRecord::Base
attr_accessible :code, description
string_correct_case = DescriptionHelper.new([:code, :description])
validates :code, :presence => true, :length => { :minimum => 4, :maximum => 4 }
validates :description, :presence => true
before_save string_correct_case
end
Callback in DescriptionHelper.rb
class DescriptionHelper
def initialize(attribute)
#attrs_to_manage = attribute
end
def before_save(record)
#attrs_to_manage.each do |attribute|
record.send("#{attribute}=", capitaliseWords(record.send("#{attribute}")))
end
end
private
def capitaliseWords(value)
value = value.mb_chars.downcase.to_s.gsub(/\b\w/) { |first| first.upcase }
end
end
Controller (diagnoses_controller.rb)
class DiagnosesController < ApplicationController
def new
#diagnosis = Diagnosis.new
end
def create
#diagnosis = Diagnosis.new(params[:diagnosis])
if #diagnosis.save
flash[:notice] = "Diagnosis created with params [#{#diagnosis.attributes.inspect}" #for debugging, once fixed will be just 'Diagnosis created.'
redirect_to #diagnosis
else
flash[:alert] = "Diagnosis not created."
render :action => "new"
end
end
.. other controller actions - edit, show, destroy
end
View (_form.html.erb)
<%= form_for(#daignosis) do |f| %>
<div class="field">
<%= f.label :code %>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= check_box_tag("diagnosis_desc_dont_convert", 1, false) %><%= f.label "Leave as entered" %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
When this runs currently the check_box_tag is ignored.
When adding in the model an attar_assessor :description_correctcase and changing the view to use f.check_box 'description_correctcase' this is still ignored.
How does one get this to work?
Thanks in advance from a rails aspiring developer.
Finally got a solution to the problem, after reading and re-reading various SO solutions to component parts of my question. I'm not sure its correct in terms of rails, but it works.
If you can offer me a better solution I would certainly learn from this.
Here is my solution.
Model (diagnosis.rb)
require 'DescriptionHelper'
class Diagnosis < ActiveRecord::Base
attr_accessor :do_not_correctcase
attr_accessible :code, :description, :do_not_correctcase
before_save DescriptionHelper.new([:code, :description]), :if =>
lambda { |d| d.do_not_correctcase.to_s == '0' }
validates :code, :presence => true, :length => { :minimum => 4, :maximum => 4 }
validates :description, :presence => true
end
This I referenced from the following SO solution - https://stackoverflow.com/a/6388691/1108010
Controller (diagnoses_controller.rb)
class DiagnosesController < ApplicationController
def new
#diagnosis = Diagnosis.new
end
def create
#diagnosis = Diagnosis.new(params[:diagnosis])
#diagnosis.do_not_correctcase = params[:diagnosis][:do_not_correctcase]
logger.debug "New diagnoses: #{#diagnosis.attributes.inspect}"
logger.debug "Diagnosis should be valid: #{#diagnosis.valid?}"
logger.debug "code has value #{params[:code]}"
if #diagnosis.save
flash[:notice] = "Diagnosis created with params [#{#diagnosis.attributes.inspect}" #for debugging
redirect_to #diagnosis
else
flash[:alert] = "Diagnosis not created."
render :action => "new"
end
end
.. other controller actions - edit, show, destroy
end
I also changed the view to replace the check_box_tag with a check_box.
View (_form.html.erb)
<%= form_for(#daignosis) do |f| %>
<div class="field">
<%= f.label :code %>
<%= f.text_field :code %>
</div>
<div class="field">
<%= f.label :description %>
<%= f.text_field :description %>
</div>
<div class="field">
<%= f.check_box 'do_not_correctcase' %><%= f.label "Leave as entered" %><br />
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
So despite getting this to work I'm not clear on are the following:
When inspecting the attributes with "#{#diagnosis.attributes.inspect}".
I assume that the reason the attr_accessor variable is not included in the New diagnosis output is that it is not part of the database table and therefore Active Reocrd does not instanciate it as part of the new record with #diagnosis.new
Could someone be kind enough to confirm that.
Why does the log have no value for logger.debug "code has value #{params[:code]}"? What causes the params[:code] to be null in the logger output?
Logfile contained the following entry:
Started POST "/diagnoses" for 127.0.0.1 at 2012-03-05 09:36:38 +0000
Processing by DiagnosesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"RW/mzkhavGeaIW0hVLn0ortTnbCDlrX+FfzH4neLLsA=", "diagnosis"=>{"code"=>"tt02", "description"=>"description for tt02", "do_not_correctcase"=>"1"}, "commit"=>"Create Diagnosis"}
New diagnosis: {"code"=>"tt02", "created_at"=>nil, "description"=>"description for tt02", "updated_at"=>nil}
Diagnosis should be valid: true
code has value
I would dearly like to know what is the correct way to do all this, as I feel this is not very DRY or clean.
I am trying to pass along a local variable to a partial using link_to. I have tried many different things, but it doesn't seem to work. The js file loads the partial fine, it just doesn't have the locals. This is what I have, thanks for any direction!
_health.html.erb (this is a partial on index.html of Contacts model)
<% #comments = Comment.find_all_by_api(#api) %>
<%= link_to 'Read Comments', comments_path(:comments => #comments), :action => 'comments', :remote => true %>
comments.js.erb
$("#comments").html("<%= escape_javascript(render(:partial => 'comment', :locals => {:comments => :comments})) %>");
comment.html.erb
<% unless #comments.blank? %>
<% #comments.each do |c| %>
<%= c %><br />
<% end %>
<% end %>
contacts_controller.rb
def comments
respond_to do | format |
format.js {render :layout => false}
end
end
The partial does not know about the comments because you never set them. The comments action in the controller needs to look like this:
def comments
#comments = Comment.find(params[:id])
respond_to do | format |
(replace params[:id] with the appropriate parameter from your route)
You're doing an AJAX request and since http is stateless, the comments action does not know anything about any previous requests - which means that the comments from _health.html.erb have ceased to exist for the comments action in the controller.
I have a project that contains projects that have todos that have tasks. When I try to create a new task, I get this error when I submit:
No route matches [POST] "/projects/1/todos/19/tasks/new"
Here is my form:
<%= form_for [#todo, #todo.tasks.new], :url => new_project_todo_task_path(#project, #todo) do |f| %>
<div class="field">
<%= f.label :description, "Description" %><br />
<%= f.text_area :description %>
</div>
<div class="actions">
<%= f.submit %> or <%= link_to "Cancel", "#", :id => "cancel_new_task_link" %>
</div>
<% end %>
Here is my controller:
class TasksController < ApplicationController
before_filter :authenticated?
before_filter :get_project_and_todo
respond_to :html, :xml, :json, :js
def new
#task = #todo.tasks.new
end
def create
#task = #todo.tasks.new(params[:task])
if #task.save
respond_with #todo, :location => project_todo_path(#project, #todo)
else
render "new"
end
end
private
def get_project_and_todo
#project = Project.find(params[:project_id])
#todo = #project.todos.find(params[:todo_id])
end
end
Here are my routes:
resources :projects do
resources :todos do
resources :tasks
end
end
Thanks
Your URL should not be new_project_todo_task_path(#project, #todo). You don't need to specify the URL here as Rails will imply it from the parameters passed in to form_for.
If the final object is a new object and not persisted in the database then it will make a POST request to, in this case, /projects/:project_id/todos. You're declaring in your example that you want to make a POST request to /projects/:project_id/todos/new, for which there is no POST route and that is why it's failing.
I've installed devise for my rails app, i can go to the sign in page or the sign up page. But I want them both on a welcome page...
So I've made a welcome_page_controller.rb with the following function:
class WelcomePageController < ApplicationController
def index
render :template => '/devise/sessions/new'
render :template => '/devise/registration/new'
end
end
But when i go to the welcome page i get this error:
NameError in Welcome_page#index
Showing /Users/tboeree/Dropbox/rails_projects/rebasev4/app/views/devise/sessions/new.html.erb where line #5 raised:
undefined local variable or method `resource' for #<#<Class:0x104931c>:0x102749c>
Extracted source (around line #5):
2: <% #header_title = "Login" %>
3:
4:
5: <%= form_for(resource, :as => resource_name, :url => session_path(resource_name)) do |f| %>
6: <p><%= f.label :email %><br />
7: <%= f.email_field :email %></p>
8:
Does anybody knows a solution for this problem? Thanks in advance!
Does it have to do with the fact that it is missing the resource function? in the welcome_page controller? It's probably somewhere in the devise controller...?
Regards,
Thijs
Here's how I managed to did it.
I've put a sign up form in my home#index
My files:
view/home/index.html.erb
<%= render :file => 'registrations/new' %>
helper/home_helper.rb
module HomeHelper
def resource_name
:user
end
def resource
#resource = session[:subscription] || User.new
end
def devise_mapping
#devise_mapping ||= Devise.mappings[:user]
end
def devise_error_messages!
return "" if resource.errors.empty?
messages = resource.errors.full_messages.map { |msg| content_tag(:li, msg) }.join
sentence = I18n.t("errors.messages.not_saved",
:count => resource.errors.count,
:resource => resource_name)
html = <<-HTML
<div id="error_explanation">
<h2>#{sentence}</h2>
<ul>#{messages}</ul>
</div>
HTML
html.html_safe
end
end
You need that part because Devise works with something called resource and it should be defined so you can call your registration#new anywhere.
Like that, you should be able to register. However, I needed to display errors on the same page. Here's what I added:
layout/home.html.erb (the layout used by index view)
<% flash.each do |name, msg| %>
# New code (allow for flash elements to be arrays)
<% if msg.class == Array %>
<% msg.each do |message| %>
<%= content_tag :div, message, :id => "flash_#{name}" %>
<% end %>
<% else %>
# old code
<%= content_tag :div, msg, :id => "flash_#{name}" %>
<% end %> #don't forget the extra end
<% end %>
I found this code here
And here's something I created: I saved my resource object if invalid in a session so that the user hasn't to fill every field again. I guess a better solution exists but it works and it's enough for me ;)
controller/registration_controller.rb
def create
build_resource
if resource.save
if resource.active_for_authentication?
# We delete the session created by an incomplete subscription if it exists.
if !session[:subscription].nil?
session[:subscription] = nil
end
set_flash_message :notice, :signed_up if is_navigational_format?
sign_in(resource_name, resource)
respond_with resource, :location => redirect_location(resource_name, resource)
else
set_flash_message :notice, :inactive_signed_up, :reason => resource.inactive_message.to_s if is_navigational_format?
expire_session_data_after_sign_in!
respond_with resource, :location => after_inactive_sign_up_path_for(resource)
end
else
clean_up_passwords(resource)
# Solution for displaying Devise errors on the homepage found on:
# https://stackoverflow.com/questions/4101641/rails-devise-handling-devise-error-messages
flash[:notice] = flash[:notice].to_a.concat resource.errors.full_messages
# We store the invalid object in session so the user hasn't to fill every fields again.
# The session is deleted if the subscription becomes valid.
session[:subscription] = resource
redirect_to root_path #Set the path you want here
end
end
I think I didn't forget any code. Feel free to use whatever you need.
Also, you can add your sign in form in the same page (something like that:)
<%= form_for("user", :url => user_session_path) do |f| %>
<%= f.text_field :email %>
<%= f.password_field :password %>
<%= f.submit 'Sign in' %>
<%= f.check_box :remember_me %>
<%= f.label :remember_me %>
<%= link_to "Forgot your password?", new_password_path('user') %>
<% end %>
Cheers !