New acts like create - ruby-on-rails

In my create action, method new acts like create.
def create
#page = Page.new(params[:page].merge(:user_id => current_user.id ))
if #page.save
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end
ActiveRecord creates new object in database while I'm using new with params. Page.new works fine in new action in my controller. What can be the reason? There is no overridden method new and no callbacks (before_save, before_create etc) in my model. Any help would be appreciated.
UPDATE - code from debugger
.../app/controllers/pages_controller.rb:48
#page = Page.new(params[:page].merge(:user_id => current_user.id ))
(rdb:25) #page
nil
(rdb:25) n
.../app/controllers/pages_controller.rb:49
if #page.save
(rdb:25) #page
#<Page id: 80 ... >
(rdb:25) Page.last
#<Page id: 80 ... >
(rdb:25) #page.save
false

Check my inline comments..
def create
#page = Page.new(params[:page].merge(:user_id => current_user.id )) # you trigger new thats fine..
if #page.save # Notice here.. This line is triggering query on database.
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end

Reason (method in model which can change status in workflow):
def status=(state_name)
states = [self.current_state.to_sym]
possible_states.each {|t| states<< t[1]}
unless state_name.blank?
if states.include? state_name
process_event! state_name
end
end
end
Ugly fix
def create
#page = Page.new
if #page.update_attributes(params[:page].merge(:user_id => current_user.id )) && #page.save
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end
Mistake was quite silly and I'm not proud of my solution. Anyway, thanks for help:)

With an ActiveRecord class, create = new + save
https://github.com/rails/rails/blob/7edade337e968fb028b2b6abfa579120eb424039/activerecord/lib/active_record/persistence.rb#L40

Your controller code is correct. This is how a 'create' controller method should work. The problem is not there.
Are you certain you're having two models created?
The .new method you're calling with the attributes creates an activerecord object in memory that's unsaved. The .save method saves it. At the end (assuming the data is valid) you should have a single object in memory.
If you have two objects created, then there is a problem. If you have only one, then it's as it should be.
Are you having a second object created by this controller method?
The process should be:
# when GET /student/new is called, this returns an empty object to display in the form
# for the user to see.
def new
#page = Page.new
end
# When POST /page is called, the form params are passed in here.
def create
# First, generate a new page object with the params passed in.
#page = Page.new(params[:page].merge(:user_id => current_user.id ))
# Now try save the object to persist it in the database.
if #page.save
flash[:notice] = t("success")
redirect_to pages_path
else
render :new
end
end

Related

Rails4: losing form validation errors while using `redirect_to` in controller

I've been trying to fix this for a while but haven't gotten anywhere yet. Would appreciate if someone could let me know how how this can be done, or if there is any way i can use the render method to do this instead (currently preserves errors but redirects to wrong path as mentioned below...)
I have a custom route for form which I am trying to redirect back when there are validation errors:
get "clubs/sign_up/:plan_id", to: "clubs#new", as: :new_membership
below is what I have so far in my controller along along with some comments regarding other steps I have tried
clubs_controller.rb
def create
#membership = Membership.new(membership_params)
if #membership.save
redirect_to root_path
else
flash[:error] = "Please check form errors:"
redirect_to new_membership_path(session[:membership_plan_id]) #errors lost
# render action: 'new', plan_id: 'silver' # <<<Preserves errors but breaks path, renders: localhost:3000/clubs instead of .../clubs/sign_up/:plan_id
# session[:membership_errors] = #membership.errors #<<< Doesn't wotk either, getting a cookie overflow error when trying to pass errors to #new
return
end
end
def new
session[:membership_plan_id] = params[:plan_id]
#membership = Membership.new
end
Assuming plan_I'd is part of your model..
Change your render line to:
render :new
Change the rest to:
def new
session[:membership_plan_id] = params[:plan_id]
#membership = Membership.new plan_id: params[:plan_id]
end
def create
#membership = Membership.new(membership_params)
if #membership.save
redirect_to root_path
else
flash[:error] = #membership.errors.full_messages.to_sentence
render :new
end
end
And add a hidden field for plan_id in your form. The reason render goes wrong is that it does not have the param available, trying to add it to the render operation does not work hence your issue
You need to render instead of redirecting. Use the ||= operator to help here.. It's all just ruby, so something like
(..snip..)
else
flash[:error] = "Problem with form"
new
end
end
def new
session[:membership_plan_id] = params[:plan_id]
#membership ||= Membership.new
render 'new'
end

Instantiate instance variable in helper method from controller

I have a helper which instantiates a model and renders a form. This form should be available to any view in the application
# support_form_helper
def support_form
#support_stats = SupportForm::Stat.find(get_stats_id)
#enquiry = SupportForm::Enquiry.new(stats_id: #support_stats.id)
render partial: 'support_form/enquiries/form'
end
And its rendered in the view:
# some_view.html.erb
<%= support_form %>
This is fine until I want to submit the form and validate it in the controller.
# enquiries_controller.rb
def create
#enquiry = SupportForm::Enquiry.new(params[:support_form_enquiry])
topic = #enquiry.topic
#stat = SupportForm::Stat.find(#enquiry.stats_id)
#stat.stats[topic] = #stat.stats[topic].to_i.next
respond_to do |format|
if #enquiry.valid? && #stat.save
format.html { redirect_to(root_path) }
else
format.html { redirect_to(:back) }
end
end
end
This is where I can't render the previous view with the errors attached to the invalid object. The helper gets invoked again and initializes a new #enquiries object, without the errors obviously.
How can I render the form in many views across an application and still return to the view with the object together with errors when it is invalid?
I found an answer which answers my question but its a bad idea:
Render the action that initiated update
def create
#enquiry = SupportForm::Enquiry.new(params[:support_form_enquiry])
topic = #enquiry.topic
#stat = SupportForm::Stat.find(#enquiry.stats_id)
#stat.stats[topic] = #stat.stats[topic].to_i.next
if #enquiry.valid? && #stat.save
redirect_to(root_path)
else
render Rails.application.routes.recognize_path(request.referer).values.join("/")
end
end
The problem is that there will likely be instance variables in the view that submitted the form and I would have to be able to instantiate all the instance variable in the application then.....not possible.
Currently I'm considering putting the errors in the flash hash... not something I want to do. With the original object returned i can repopulate the fields with the users input.
When you use redirect_to, rails will kick off a whole new controller & view sequence. Use
render "path/to/template/from/view/folder"`
instead.
A typical create action using this pattern would look like (for a 'post' object in this case):
def create
#post = Post.new(params[:post])
#created = #post.save
respond_to do |format|
if #created
flash[:notice] = 'Post was successfully created.'
format.html { redirect_to post_path(#post) }
format.js
else
format.html { render :action => :new }
format.js
end
end
end
Notice how if it's successfully created we do a full redirect to the "show" page for the post, but if it's not successful we just do a render.
You should probably modify your support_form helper so that it only creates a new #enquiry if it hasn't been created already:
def support_form
#support_stats = SupportForm::Stat.find(get_stats_id)
#enquiry ||= SupportForm::Enquiry.new(stats_id: #support_stats.id)
render partial: 'support_form/enquiries/form'
end
||= is shorthand for "equals itself or". If it hasn't been defined (or is nil or false) then it will fail the first part of the or and pass through to the second, where the object is created.
In your form partial, also, you should make sure you're using form_for, which will submit to the create or update action depending on whether the object has been saved already.

Refactoring related nested if

I'm trying to create 2 related models inside my create method, where the 2nd model is created using model1.model2s.build. The model*_params is just Rails 4 strong parameters.
So I have this set of code in my create method:
def create
#model1 = current_user.model1.build(model1_params)
if #model1.save
#model2 = #model1.model2s.build(model2_params)
if #model2.save
redirect_to model1_path(#model1)
else
render 'new'
end
else
render 'new'
end
end
As you can see there's an ugly nested if in the method, and it's not DRY as I'm forced to repeat render 'new' in order to capture save failures. This has been the only way I can get model2 to save, because it requires a relation to model1, and model1 must save first, in order for the id of model1 to be propagated to the build method.
My question therefore, is how can I refactor this set of code so that it doesn't require a nested if?
def create
#model1 = current_user.model1.build(model1_params)
return render("new") unless #model1.save
#model2 = #model1.model2s.build(model2_params)
return render("new") unless #model2.save
redirect_to model1_path(#model1)
end
You can easily make it a single if/else:
if #model1.save && #model1.model2s.build(model2_params).save
redirect_to #model1
else
render 'new'
end
Alternatively, exceptions:
begin
#model1 = current_user.model1.build(model1_params)
#model1.save!
#model2 = #model1.model2s.build(model2_params)
#model2.save!
redirect_to #model1
rescue ActiveRecord::RecordInvalid => e
render 'new'
end

Rails render issue

Ok, another annoying problem.
I have a GuestsController that with an index action like this:
def index
#booking = Booking.find(session[:booking_id]) #i have also hard coded values to make sure the session isn't the issue
#guest = Guest.find(session[:guest_id])
end
and a personal action (to perform updates) as follows:
def personal
#guest = Guest.find(session[:guest_id])
if #guest.update(post_params)
redirect_to :controller => 'guests', :action => 'cards'
else
render 'index'
end
end
My index.html.erb view uses the #booking variable:
<%= #booking.friendly_id %> #this is one example
and also contains the form to submit the "name" field to the personal action. It updates fine if the data is valid but the #booking variable doesn't exist if it's invalid???
I need to show validation errors so I can't just use redirect_to.
The error I get is: NoMethodError in Guests#personal and undefined method `friendly_id' for nil:NilClass
Any ideas?
Just initialize the object in else part
else
#booking = Booking.find(session[:booking_id])
render 'index'
end
How about moving #booking and #guest definitions to before_filter?
before_filter do
#booking = Booking.find(session[:booking_id]) #i have also hard coded values to make sure the session isn't the issue
#guest = Guest.find(session[:guest_id])
end
There needs to be something to handle when #booking is nil - which can happen if I'm reading this right.
def index
if Booking.find(session[:booking_id])?
#booking = Booking.find(session[:booking_id])
else
#booking = Booking.build #or whatever you want here
end
#guest = Guest.find(session[:guest_id])
end

Updating a model into another one in Rails

I have a model called Details that contains a method that updates some attributes with calculations by using another model's (Summary) attributes. I call that method into a before_saveto make Details' attributes update automatically when I modify an entry. I would like to make my Details' attributs update when I modify my entries in the Summary model. What I'm searching is something that could be used like that :
def update
#summary = Summary.find(params[:id])
if #summary.update_attributes(params[:summary])
(Details.update_all_attributes)
flash[:notice] = "Updated!"
redirect_to edit_summary_path(#summary)
else
render :action => 'edit'
end
end
See the line : Details.update_all_attributes. All my Details' attributs would be saved and recalculated because of the before_save I have put. How can I do that ? Thanks.
Edit :
I just found the answer. I did a loop that saves all my entries.
def update
#summary = Summary.find(params[:id])
if #summary.update_attributes(params[:summary])
#details = Details.all
#details.each do |detail|
detail.save!
end
flash[:notice] = "Updated!"
redirect_to edit_summary_path(#summary)
else
render :action => 'edit'
end
end
Hope I will help some of you !
Fat models, skinny controllers:
Model
class Summary < ActiveRecord::Base
after_update :recalculate_details
private
def recalculate_details
Detail.all.each {|d| d.save! }
end
end
Controller
def update
#summary = Summary.find(params[:id])
if #summary.update_attributes(params[:summary])
flash[:notice] = "Updated!"
redirect_to edit_summary_path(#summary)
else
render :action => 'edit'
end
end

Resources