Ruby on Rails -Problem using subdirectories - ruby-on-rails

I have tried to set up a separate section of my app using a subdirectory called controlpanel to manage various parts of the site.
I've set up the namespace in my routes.rb
map.namespace :controlpanel do |submap|
submap.resources :pages
# other controllers
end
And placed the controller and views into the relevant subdirectories.
Controlpanel::PagesController
def new
#page = Page.new
end
def create
if #page = Page.create_with_author(current_user, params[:page])
flash[:notice] = 'Page was successfully created.'
redirect_to ([:controlpanel, #page])
else
render :action => 'new'
end
end
Using this mixed in class method
def create_with_author(author, params)
created = new(params)
created.author = author
if created.save
created
end
end
And the view (controlpanel/pages/new.html.erb renders a partial called _form
<%= render :partial => 'form' %>
Which is as follows:
<% semantic_form_for([:controlpanel, #page]) do |form| %>
<% form.inputs do %>
<%= form.input :title %>
<%= form.input :body %>
<% end %>
<%= form.buttons %>
<% end %>
If I fill in the form correctly, it works as expected, redirecting me to the new page, however, if I leave fields blank, violating the validation constraints, I get the following error:
RuntimeError in Controlpanel/pages#create
Showing app/views/controlpanel/pages/_form.html.erb where line #1 raised:
Called id for nil, which would mistakenly be 4 -- if you really wanted the id of nil, use object_id
Can anyone see what is going wrong?
I'm using the formtastic plugin to create the form, but it still happens if I use a regular form.
Any advice greatly appreciated.
Thanks.

Given that the create action is called and new is rendered, Page.create must evaluate to nil.
You probably want to pass params[:page] to create.

Related

undefined method `each' for nil:NilClass while attempting to use a partial

I'm currently having the issue for when I try to use a partial inside of invoices/_form.html.erb, it goes into parts/_index.html.erb and breaks.
Inside of the parts_controller I have:
def _index
#parts = Part.all
end
#unsure if this is needed
Inside of invoices_controller I have:`
def new
#invoice = Invoice.new
#parts = Part.all
end`
Inside of invoices/_form.html.erb I have:
<%= render :partial => "parts/index" , :part => #parts %>
And inside of invoices/new.html.erb I have:
<h1 style="padding-left:120px">New Invoice</h1>
<%= render 'form', invoice: #invoice, part: #parts %>
<%= link_to 'Back', invoices_path, class: "btn btn-default col-md-2" %>
So what this code is attempting to do is display the index page of parts so the user is able to see all current parts they have in stock, and how many of that part is in stock. The parts/index page is the exact same as the the default index page for parts, but it just has a link removed.
The line of code that gives me an issue in parts/index is:
<% #parts.each do |part| %>
And what's confusing me about that is that I should be passing it an object that has data inside of it, since it's declared in both the controller for parts, and the invoice controller. Am I missing something super simple with my syntax, or is what I'm trying to do not the right way to do it? I'm still a noob to rails, so sorry if what I'm trying to get across doesn't make too much sense.
so here is the problem:
<%= render :partial => "parts/index" , :part => #parts %>
you are sending :part to your _index.html.erb partial while using #parts
you need to update your render call to following:
<%= render :partial => "parts/index" , locals: {parts: #parts}%>
and your loop to:
<% parts.each do |part| %>
you provide to _index.html.erb variable part but try to render #parts.
1. you don't need method _index, when your patial _index.html.erb render that not get variable #parts from method _index. I think it's wrong.
2. You need to render in _index.html.erb variable which it's provided from _form
<% part.each do |part_| %>

how does form_for know the difference when submitting :new :edit

I've generated a scaffold, let's call it scaffold test.
Within that scaffold, I've got a _form.html.erb thats being render for action's :new => :create and :edit => :update
Rails does a lot of magic sometimes and I cannot figure out how the form_for knows how to call the proper :action when pressing submit between :new and :edit
Scaffolded Form
<%= form_for(#test) do |f| %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
vs.
Un-scaffolded Form
<% form_for #test :url => {:action => "new"}, :method => "post" do |f| %>
<%= f.submit %>
<% end %>
Edit template
<h1>Editing test</h1>
<%= render 'form' %>
New template
<h1>New test</h1>
<%= render 'form' %>
As you can see theres no difference between the forms
How can both templates render the same form but use different actions?
It checks #test.persisted? If it is persisted then it is an edit form. If it isn't, it is a new form.
It checks if the record is new or not.
#test.new_record? # if true then create action else update action
If the #test instance variable is instantiated via the Test.new class method, then the create method is executed. If #test is an instance of Test that exists in the database, the update method is executed.
In other words:
# app/controllers/tests_controller.rb
def new
#test = Test.new
end
<%= form_for(#test) |do| %> yields a block that is sent to the create controller method.
If, instead:
# app/controllers/tests_controller.rb
def edit
#test = Test.find(params[:id])
end
<%= form_for(#test) |do| %> yields a block that is sent to the update controller method.
UPDATE:
The precise function that Rails uses to recognize whether or not a record is new is the persisted? method.

Repopulating form on failed validation

I have a form for a user to create a question (in additon to user model, there's a question model, with nested answers) on their profile page. It submits from the users profile page /views/users/show.html.erb to the create action of the questions_controller.rb. If it doesn't validate, I think the default for Rails is to render the form(with the invalid information in the form for the user to edit). However, since I'm submitting the form for the question model from the users profile page the prepopulation isn't happening upon failed validation; the user is forced to enter all the information in the form again. Is there a way in this context to get the form on the users show page to fill out with the information that was entered prior to submission?
questions_controller
def create
#question = current_user.questions.build(params[:kestion])
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
###render :action => 'show'
redirect_to current_user
end
end
Update
I've changed the end of the create method too
Redirect ( : back ), :notice => "something went wrong.try again"
But I still can't get the form to populate, and the validation error messages aren't showing either, only the flash notice.
Update
The show method of the users controller creates the new Question (along with the user)
def show
#user = User.find(params[:id])
#question = Question.new
3.times {#question.answers.build}
end
The /views/users/show.html.erb
<%= form_for #question do |f| %>
<% if #question.errors.any? %>
<h2><%= pluralize(#question.errors.count, "error") %> prohibited this question
from being saved: </h2>
<ul>
<% #question.errors.full_messages.each do |msg| %>
<li> <%= msg %></li>
<% end %>
</ul>
<% end %>
<p>
<%= f.label :content, "Question"%>
<%= f.text_area :content, :class => 'span4', :rows => 1 %>
</p>
<p>
<%= f.label :link, "QuoraLink" %>
<%= f.text_field :link, :class => 'span4', :rows => 1 %>
</p>
<%= f.fields_for :answers do |builder| %>
<p>
<%= render 'answer_fields', :f => builder %>
</p>
<% end %>
<p><%= link_to_add_fields "Add Answer", f, :answers %></p>
<p><%= f.submit %></p>
<% end %>
the answer_fields partial rendered from the questions partial
<p class="fields">
<%= f.label :content, "Answer..." %>
<%= f.text_field :content, :class => 'span3', :rows => 1%>
<%= f.label :correctanswer, "Correct" %>
<%= f.check_box :correctanswer, :class => 'span1' %>
<%= link_to_remove_fields "remove answer", f %>
</p>
Currently, in views/users/show.rb you do
#question = Question.new
that creates an empty new question. Then you populate the forms with this empty model.
What you could do instead is:
if session[:question]
#question = #user.questions.new(session[:question])
session[:question] = nil
#question.valid? # run validations to to populate the errors[]
else
#question = Question.new
end
Now all what's left to do is populating session[:question] in your questions_controller before redirecting to :controller=>"users", :action=>"show". Something like:
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
session[:question] = params[:question]
redirect_to current_user
end
You may need to work on serialization/deserialization additionally for populating/using session[:question]. I didn't try to compile, so am not sure.
All this is needed because when you do redirect_to your processing of the user request ends, the user browser gets a redirect status code from your server and goes for another page, sending you a new request (which lands on the path, and eventually controller/action, to which you redirected to). So, as soon as you return from the request processing, all your variables are lost. For the next request you start from scratch.
The render :action => "show" approach (that was in the original scaffold and that you commented out) worked because you didn't return back to user but simply rendered the template with a specific name using the variables you already had in place (including #question, on which 'save' was called and failed, and thus internally validations were called and populated the errors object).
Actually, that reminded me that you may want to use another approach. Instead of passing parameters through session[] and redirecting to UsersController, you may want to populate all required variables and just render the view from that controller. Like below:
if #question.save
redirect_to current_user, :notice => "Successfully created question."
else
#user = current_user
render "users/show"
end
Firstly, the reason that using redirect_to instead of render doesn't repopulate the form is that when you redirect_to, the controller logic for the action is run, whereas using render ignored the controller logic.
So when you render :action => 'show' (the "default" behaviour), it renders show.html.erb, with #question set like this:
#question = current_user.questions.build(params[:kestion])
When you redirect_to current_user, it renders show.html.erb with #question set using the code in your show action:
#question = Question.new
3.times {#question.answers.build}
This is why you get a new (blank) form, instead of a pre-populated one.
Is it really that important that you use redirect_to? If it is, you'll need to get your show method to do the validation. For example, you could rewrite your show method to something like:
def show
#user = User.find(params[:id])
if params.has_key?(:kestion)
#question = #user.questions.build(params[:kestion])
else
#question = Question.new
3.times {#question.answers.build}
end
end
and then make your form point at that page, with something like:
<%= form_for(#question, url: users_path(#question.user) do |f| %>
...
<% end %>
(depending on how your routes are set up and named). Of course, by that point the whole thing become horribly un-RESTful, a bit of a mess, and definitely not the Rails way of doing things. (The other, worse option would be to redirect back and pass the params through a get query.) In my opinion, you lose a lot for a minor gain, and I'm not sure that I'd really recommend it.

How do I use another value of a key in a hash if the first one doesn't exist, like some type of fallback in ruby/rails?

Basically I have a follow button and when click the page refreshes and I show an unfollow button in place. Below is the code I use to render the particular form needed:
follow_forms partial:
<% unless current_user?(#user) %>
<% if current_user.following?(#user) %>
<%= render 'relationships/partials/unfollow' %>
<% else %>
<%= render 'relationships/partials/follow' %>
<% end %>
<% end %>
Any I changed the form to an ajax form because I don't want the page refresh and on success of the form submission I'd like to replace the follow button/form with an unfollow button/form. This isn't straight forward because only 1 form shows at a time so I can't use my jquery selector to find this form anyway.
What I decided to do was create a new action that renders the follow_form partial this way the appropriate form will be available for me to manipulate with my jquery selector.
The new action:
class RelationshipsController < ApplicationController
def get_follow_form
respond_to do |format|
format.html { render :partial => 'relationships/partials/follow_form_ajax' }
end
end
end
The problem now is that I don't have access to the #user instance variable. That doesn't matter to much because I can get the user who was just followed via the jquery success data then pass that as data in the new ajax call to get_follow_form_url and then pass that info into the partial as a local variable.
I still have an issue with the #user instance variable not being available. Which brings me to my question.
How can I make another value be used if the instance variable isn't nil/doesn't exist?
The form for following:
<%= form_for current_user.relationships.build(:followed_id => #user.id), :remote => true do |f| %>
<%= f.hidden_field :followed_id %>
<%= f.submit "Follow", :class => 'followButton' %>
<% end %>
Can I do something like this
:followed_id => #user.id <-if this doesn't exist use this-> user.id
There are other ways around this like creating new partials that are only used for this whole situation or creating some messy if statements but I feel like creating duplicate forms should be my very very very last option.
I look forward to you solutions thanks
Kind regards
There's a very simple way to do this, assuming you have your 'fallback' ID:
:followed_id => #user.present? ? #user.id : fallback_id
Use something like the andand gem or just try and a logic expression:
:followed_id => #user.andand.id || user.id
Even without that you can use identical logic, and certainly don't need multiple partials:
:followed_id => (#user && #user.id) || user.id
But as Frederick says, if you have a replacement value for the object already, couldn't you just set it?

Rails, STI and 'becomes' - f.object.errors not showing in view

My question is: why doesn't .becomes pass errors over to the new object? Isn't this the expected behaviour?
I have the following single table inheritance classes in a rails app:
class Document < ActiveRecord::Base
validates :title, :presence => true
end
class LegalDocument < Document
end
class MarketingDocument < Document
end
I want to use the same controller and set of views to edit both LegalDocuments and MarketingDocuments, so I am using DocumentsController < ApplicationController with the following edit and update actions:
def edit
#document = Document.find(params[:id])
end
def update
#document = Document.find(params[:id])
if #document.update_attributes(params[:document])
redirect_to documents_path, :notice => "#{t(:Document)} was successfully updated."
else
render :action => "edit"
end
end
and the following in my edit view:
<%= form_for #document.becomes(Document) do |f| %>
<% if f.object.errors.present? %>
<div class="error_message">
<h4><%= pluralize(f.object.errors.count, 'error') %> occurred</h4>
</div>
<% end %>
<div>
<%= f.label :title %>
<%= f.text_field :title, :class => "inputText" %>
</div>
<%= f.submit %>
<% end %>
If title is filled in, the documents update correctly.
If title is left blank, I am returned to the edit view BUT the error is not shown.
From debugging, I know it's not showing because f.object.errors is nil. However, from debugging, I also know #document.errors is NOT nil, as expected.
My question is: why doesn't .becomes pass errors over to the new object? Isn't this the expected behaviour?
Yes, I noticed that too.
Just change f.object.errors.present? by #document.errors.any? ( or #document.errors.present?).
If you really want to use f.object.errors.present?, write becomes in the controller (both edit and update actions) instead of in the view:
def edit
#document = Document.find(params[:id]).becomes(Document)
end
def update
#document = Document.find(params[:id]).becomes(Document)
# ....
end
And then in the view:
<%= form_for #document do |f| %>
<% if f.object.errors.present? %>
<p>Errrorsss....</p>
<% end %>
#.....
It happens because the url of the form is build according to #document.becomes(Document) (=> PUT document/:id) but #document is created according to its "true" class (a subclass of Document).
If you have pry (highly recommended), write:
def update
#document = Document.find(params[:id])
binding.pry
# ...
end
And then inspect #document. You will see that #document is an instance of LegalDocument or the other subclass even though you called #document.becomes(Document) in your form.
So in final f.object and #document are not the same.
This explains why you can't see f.object.errors when validation fails.
Edit
The 'best way' to deal with STI and form is NOT to use becomes:
<= form_for #document, url: { controller: 'documents', action: 'update' }, as: :document do |f| %>
<% if #document.errors.any? %>
# or if f.object.errors.any?
# handle validation errors
<% end %>
# your form...
<% end %>
This enables you:
to have only one controller (documents_controller)
to have only one resource (resources :documents)
it keeps trace of your subclasses: a LegalDocument will be store as a LegalDocument. No conversion: You don't have to store its class before the conversion to Document and then reassign it later.
Plus, your subclass is available in your form, so you can (let's imagine) build a select for the type.
your controller looks cleaner: #document = Document.find params[:id] nothing more. Just like a classic resource.
If you want to share this form across different actions(typically edit and new):
<%= form_for #document, url: { controller: 'media_files', action: action }, as: :media_file do |f| %>%>
# edit.html.erb
<%= render 'form', action: 'update' %>
# new.html.erb
<%= render 'form', action: 'create' %>
Pretty much it is a bug and it should work as you initially expected. The following patch to address the issue looks like it was pulled back in October
https://github.com/lazyatom/rails/commit/73cb0f98289923c8fa0287bf1cc8857664078d43

Resources