Validating forms with variables - ruby-on-rails

In most of the examples I see, I notice that in the controller, the new method simply has:
#object = Object.new
In the create method, you'll see something like:
#object = Object.find(params[:object])
if #object.save
flash[:success] = "This object has been added."
redirect_to objects_path
else
render :action => 'new'
end
This works fine, but the only thing I run into (pretty common), is when the validations fail using some built in helper methods:
validates_presence_of :value
the "render :action => 'new' will be called. So then rails bypasses the controller and goes straight to the action for new, which attempts to render the form.
That bypass of the controller kills me, because sometimes I'm loading values into the form using values I've defined in my controller.
I'll end up getting errors because of nil values, because rails bypassed the controller and the values were never set when loading "render :action => 'new'".
My question: What's the best way to redirect a form (or best practice in general) when validating a form that has variables assigned in it that is defined the in controller? I want to avoid those nil value errors, so I'm thinking I'm just doing it wrong to begin with.

You could move your custom bit of code that loads your various values into a before_filter, a bit like:
before_filter :get_values, :only => [:new, :create]
def new
# your code here
end
def create
#object = Object.new params[:object
if #object.save
flash[:success] = "This object has been added."
redirect_to objects_path
else
render :action => 'new'
end
end
private
def get_values
# your code here
end

One way: render hidden fields for all those variables, so your object will already have them in create action.
Another way: create a before_filter for those two actions.
Personally I'd go with the second option.

Related

Conditional routing for nested resource in Rails controller #edit action, depending on where request came from

I have a Foo resource that has_many Bars. I'm using nested resources for a limited number of actions, but otherwise prefer to keep my routing for bars shallow. There are two ways to navigate to the edit view for the Bar object - either from the nested path that includes foo, or from the shallower bar path that isn't nested inside foo. For example, a user might click the edit button from the page at /foos/[:foo_id]/bar/[:bar_id]; or from /bars/[:bar_id].
In the first case, I want the controller to redirect the user back to the parent foo page: /foos/[:foo_id] after the record is updated. In the second case, I want it to redirect to the index view for bars: /bars. I believe I need some sort of conditional in the #edit action in the bars controller that will tell Rails where to go after #update executes.
# config/routes.rb
resources :foos do
resources :bars, only: [:new, :edit]
end
resources :bars
# bin/rake routes:
foo_bars POST /foos/:foo_id/bars(.:format) bars#create
new_foo_bar GET /foos/:foo_id/bars/new(.:format) bars#new
edit_foo_bar GET /foos/:foo_id/bars/:id/edit(.:format) bars#edit
bars GET /bars(.:format) bars#index
POST /bars(.:format) bars#create
new_bar GET /bars/new(.:format) bars#new
edit_bar GET /bars/:id/edit(.:format) bars#edit
bar GET /bars/:id(.:format) bars#show
PATCH /bars/:id(.:format) bars#update
PUT /bars/:id(.:format) bars#update
DELETE /bars/:id(.:format) bars#destroy
The controller for bars:
# app/controllers/bar_controller.rb
def edit
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
#bar = bar.find(params[:id])
#foo = #bar.foo
respond_to do |format|
if #bar.update_attributes(bar_params)
format.html { redirect_to #foo, notice: "bar successfully updated" }
else
format.html { render action: "edit" }
end
end
end
I'm trying to change the redirect_to #foo line in the #update action so there is conditional logic that switches out #foo for #bars depending on where the #edit action was initiated. I've tried something like the following to test whether params[:foo] is present when the #edit action is called, setting an instance variable for the redirect.
def edit
if params[:foo]
#redirect_page = #foo
else
#redirect_page = #bars
end
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
# code omitted...
format.html { redirect_to #redirect_page, notice: "bar successfully updated" }
# code omitted...
end
This doesn't work. Rails states cannot redirect to nil!. I've also tried something using a test based on URI(request.referer).path in the #edit action, without success.
I'm still not entirely clear how the Rails magic happens in the controller. I believe the #edit action is the proper place to define the conditional for the redirect (or through a method called in the #edit action), as that's where the controller will "see" the incoming request and know where it came from. But I can't quite figure out to capture that information, and pass it along to #update. Appreciate any guidance.
In your edit forms, add a hidden_field_tag:
<%= hidden_field_tag "route", request.env['PATH_INFO'] %>
Then in your controller, you can have an if statement and use a redirect_to based on what the params[:route] is.
I figured it out. The params[:route] method using request.env['PATH_INFO] wasn't working for me, because the 'PATH_INFO' variable in the form was providing the path handed off to the bars#update action, instead of the path where the bars#edit action was initiated.
After clicking "Edit" from the parent foo page at /foos/[:id] the params hash is:
>> params
=> {"controller"=>"bars", "action"=>"edit", "foo_id"=>"3786", "id"=>"16"}
There is no value for params[:route] when the form is first accessed - the hidden field is only added to the params hash after clicking "Update" in the edit form:
>> params[:route]
=> "/foos/3786/bars/16/edit"
This could work, but would require building logic to parse the route in order to redirect to /foos/[:foo_id]
It turned out to be simpler to use the Rails flash method to store the path for redirecting back to the source page. I did this by calling a custom method set_redirect_path in the BarsController, and calling it in bars#edit. This sets a value for the source in the flash, which is available in bars#update. Maybe there's a better/more conventional way to achieve this, but this seems to be a clean and simple way to do what I want.
# app/controllers/bars_controller.rb
def edit
set_redirect_path
#bar = bar.find(params[:id])
#foo = #bar.foo
end
def update
#bar = bar.find(params[:id])
#foo = #bar.foo
respond_to do |format|
if #bar.update_attributes(bar_params)
format.html { redirect_to flash[:source], notice: "bar successfully updated" }
format.xml { head :ok }
else
format.html { render action: "edit" }
format.xml { render xml: #bar.errors, status: :unprocessable_entity }
end
end
end
private
def set_redirect_path
flash[:source] = URI(request.referer).path
end
One advantage of this approach is I can now get rid of conditional logic in the shared partial app/views/bars/_list.html.haml that was required to determine whether clicking the "Edit" button should route to edit_foo_bar_path or to edit_bar_path (i.e. the former is chosen if #foo exists). Consequently, I can delete :edit for the nested resource :bars. Since the flash captures the incoming source of the request and stores it for reference in the #update action, all edit requests can use the same edit_bar_path, regardless of where they originate from. After update Rails redirects the user to the point where they initiated the #edit action.

Saving to database issue

I am building a database and I am getting an issue with my create action. My code for the create action is:
def create
#skills = Skill.new(params[:skill])
if #skills.save
redirect_to :action => 'index'
else
#skills = Skill.find(:all)
render :action => 'new'
end
end
and my error message is this:
ActiveModel::ForbiddenAttributesError in SkillsController#create
ActiveModel::ForbiddenAttributesError
I assume there is a problem and I am not saving all the needed params but I am not sure. Thanks anyone who knows what might be going wrong and I will keep messing around with it myself. Thanks again.
In your controller add something like this:
private
def skill_params
params.require(:skill).permit(:attribute_1, :attribute_2, :attribute_3)
end
then change create to:
def create
#skills = Skill.new(skill_params)
if #skills.save
redirect_to :action => 'index'
else
#skills = Skill.find(:all)
render :action => 'new'
end
end
This is specific to Rails 4: this version of ruby on rails fordids direct usage of the params to instanciate a new Model object.
It makes your application more safe by protecting it against some vulnerabilities: for example, imagine if someone requests your application to create a new user and passes admin: true as parameter. The old way may create an admin user. On the contrary, the Rails 4 way force you to filter the parameters.

Excluding nested form fields in controller action with Hash#exclude before mass_assignment error is generated?

I currently have a create action in my sales controller that looks like this:
def create
#sale = Sale.new(params[:sale].except(:vehicles_attributes))
if #sale.save
redirect_to #sale, :notice => "Successfully created sale."
else
render :action => 'new'
end
end
The intention is to exclude a couple of attributes that are used only to populate linked selects, and should not be submitted (there are no columns for them).
With the controller code above, I am finding that the parameters still includes "sale"=>{"vehicles_attributes"=>{"0"=>{"make"=>"","model"=>""}}} so it seems that I have missed something in the controller code.
EDIT: After some more digging around, I have found that the mass_assignment exception is firing before my except code gets a chance to remove the params that shouldn't be sent by the form, so I am back to square one.
How can I ensure that I remove the fields that shouldn't be sent by the form before I get the mass_assignment error?
As far as I know the mass_assignment error should occur during the new call, so your way should work. Although I never used the except method. Have you tried using the reject! method?
def create
params[:sale].reject! { |k, v| k == :vehicles_attributes }
#sale = Sale.new(params[:sale])
if #sale.save
redirect_to #sale, :notice => "Successfully created sale."
else
render :action => 'new'
end
end
If you need to keep the :vehicles_attributes you can also use the reject method (without the bang) which gives you a copy instead of removing it from the original hash.

ActiveAdmin - how to render default template in customized action

We are using ActiveAdmin in our Rails3 application for the default models. Now we needed to overwrite the show action. The OrderProcess model is a transient (tableless) model, which means that all fields are aggregated from other data. We use an internal module that provides the necessary methods to mock the MetaSearch methods ActiveAdmin is depending on. The following is how we overwrite the show action:
ActiveAdmin.register OrderProcess do
member_action :show, :method => :get do
#order_process = OrderProcess.all_orders_for_deal(params['id'])
end
end
That gives us an error complaining about a missing template "Missing template admin/order_processes/show with ..."
We also tried to call
render renderer_for(:show)
but that produced an error about a missing method model_name which may be due to our model being tableless and the regarding module.
How can we use ActiveAdmins built in rendering methods to display our model? Any help is appreciated.
Just ran into this... Grant's comment is correct, active_admin_template doesn't exist any more (I'm on 1.0.0-pre2).
I ended up going with:
render :action => :edit, :layout => false
which seems to work, although you will have to supply a label for the header, which displays as "translation missing: en.active_admin.[your_action]_model"
The solution mentioned at this other stackoverflow post worked:
render active_admin_template('edit.html.arb'), :layout => false
I had a similar issue where I needed to override the default active admin controller behavior for the update action. I got it to work like this:
controller do
def update
#model = Model.find(params[:id])
# do stuff
if #model.save
redirect_to admin_model_path(#model)
else
render :edit
end
end
end
The key was just render :edit which will render the default edit page already defined by active admin.
The other solution using
render active_admin_template('edit.html.arb'), :layout => false
did not work for me or any other combination of render renderer_for(:edit).
I have the same problem :(
I'm trying to override an update action and trying to render the 'edit action'
member_action :update, :method => :post do
if params[:user][:password].blank?
[:password, :password_confirmation, :current_password].collect{|p| params[:user].delete(p) }
end
#user = User.find(params[:id])
respond_to do |format|
if #user.update_attributes(params[:user])
format.html { redirect_to([:admin, #user]) }
else
format.html { render renderer_for(:edit) }
end
end
end
The activeadmin docs are very light on the specifics of how to override a standard controller action, which is frustrating given how opaque the source code is. Many of the internals in the gem seem to have changed a ton with version 1.0, which renders a lot of older Stack Overflow answers unusable.
Anyway, here's how I was above to override the #create action in my activeadmin controller (on Rails 4.2.x):
controller do
def create
#user = User.create_from_admin(permitted_params[:user])
if #user.persisted?
redirect_to resource_path(#user), notice: I18n.t("admin.user.create.notice")
else
render :action => :new
end
end
end
It's worth noting that activeadmin expects, if your model is User, for the create action to have a populated model instance as #user before it can render action => :new.
I wrote the internals of my custom create method as a class method on my model so I could unit-test it and bury as little code as possible in my activeadmin code.
For context, I needed to override this action because I'm using Devise and I wanted to let my admins create user accounts with a temporary password and a custom welcome email rather than the built-in :confirmable email for self-created accounts.
Here's that User class method:
def self.create_from_admin(params)
generated_password = Devise.friendly_token.first(8)
#user = User.new(params)
#user.password = generated_password
#user.skip_confirmation!
if #user.save
# Code to send custom email with temp password
end
#user
end

Using parameters in my controller

So I'm using the excellent Ancestry gem But while the documentation seems very complete I don't understand how to pass the parameter of my element which I want to be the parent of my newly created element. Firstly, do I want to do it in the new or create action... allow me to explain. For example: (with some actions removed for brevity)
class PeopleController < ApplicationController
#...
def new
#person = Person.new
end
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = "Registration Successful."
redirect_to root_url
else
render :action => 'new'
end
end
end
So namely I don't know where to create the ancestry, the docs say:
...You can use the parent attribute to organise your records into a tree. If you have the id of the record you want to use as a parent and don’t want to fetch it, you can also use parent_id. Like any virtual model attributes, parent and parent_id can be set using parent= and parent_id= on a record or by including them in the hash passed to new, create, create!, update_attributes and update_attributes!. For example:
TreeNode.create! :name => 'Stinky', :parent => TreeNode.create!(:name => 'Squeeky')
I want to know what my controller show look like to allow me to set the parent of the #person when I create them.
So otherwise I'm stuck, I don't know what else to do here... but anyhow, I do know that this gem is similar to the more popular acts_as_tree, any help is super appreciated!
Updated
I think I almost have it but when I try this for my create action
def create
#parent = Recipe.find(params[:parent])
#recipe = Recipe.new(params[:recipe], :parent => #parent.id) do |recipe|
recipe.user_id = current_user.id
end
if #recipe.save
current_user.has_role!(:owner, #recipe)
redirect_to #recipe
else
render :action => 'new'
end
end
I get:
Couldn't find Recipe without an ID
Updated
My view has a link to the new action that looks like this <%= link_to "fork this recipe", {:controller => "recipes", :action => "new", :parent => #recipe} %>
That seems to look fine to me, also the url reads fine when you get to the form, recipes/new?parent=112, but I still get that error, there has to be a way for that parameter to be passed as the parent of the newly created object.
If it works like acts_as_tree then I'll assume that you can do something like this:
#parent.children.create(attributes)
Which will create a new child object with the parent set, regardless of what the attributes say.
According to the docs that you pasted you can do:
#...
#user = User.new(params[:user])
#user.parent_id = #parent_user.id
#user.save
#...
You can also include it in the params hash for the user -- your form submission would need to have params[:user][:parent_id]:
#user = User.create(params[:user])

Resources