ActiveAdmin - how to render default template in customized action - ruby-on-rails

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

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.

Rails Redirect_path after save based on view

I currently have two views (new.html.erb and retirement_accounts_new.html.erb) in the Accounts both using the same create and update methods.
Here's how they're defined in the controller:
# GET /accounts/new
def new
#account = current_user.accounts.build
end
# GET /retirement/accounts/new
def retirement_accounts_new
#account = current_user.accounts.build
end
And here's the same create method they share:
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to accounts_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
Is there a way to make that redirect_to accounts_path conditional based on which view is rendering the form?
I would like retirement_accounts_new on save/update to redirect_to retirement_accounts
It sounds like this might be a design issue. Are Accounts and RetirementAccounts significantly different? Will they share much of the same logic, but not all? If so, I think I would avoid using conditional logic in the controller and solve it using inheritance.
The idea here is that retirement_accounts would be considered a new resource in your routes file:
resources :retirement_accounts
Then you manually create a new controller for it (skip the rails generate... command). Save this file as app/controllers/retirement_accounts_controller.rb:
class RetirementAccountsController < AccountsController
end
Notice how it inherits from AccountsController instead of ApplicationController. Even in this empty state, RetirementAccountsController shares all of the logic of AccountsController, including the new and create methods, plus all of the view files to which they refer. To make the necessary modifications for the retirement accounts, you simply need to override the appropriate actions and views.
You can delete your retirement_accounts_new action, since it is identical to the new action. Move the view for retirement_accounts_new to app/views/retirement_accounts/new.html.erb, so that template will be rendered when new is called on the RetirementAccountsController.
As for the conditional create method, you can make a private method on both controllers that will determine where the post-create redirect should point:
class AccountsController < ApplicationController
# ...
def create
#account = current_user.accounts.build(account_params)
if #account.save
redirect_to post_create_redirect_path, notice: 'Account was successfully created.'
else
render action: 'new'
end
end
private
def post_create_redirect_path
accounts_path
end
end
class RetirementAccountsController < AccountsController
private
def post_create_redirect_path
retirement_accounts_path
end
end
If RetirementAccount < Account as a single table inheritance model then the thing you are asking would happen by default,
plan B would be to use explicit url_for in the redirect such as:
redirect_to url_for(controller: params[:controller], action: :show, id: #account.id), notice: 'Account was successfully created.'
Looking at the api doc this should work too:
redirect_to :action => "show", :id => #account.id,notice: 'Account was successfully created.'
Check out http://apidock.com/rails/ActionController/Base/redirect_to - there's probably an answer for you there somewhere :)
PS I have assumed that the the retirement account and account actions are in different controllers. If they're not in different controllers and not different model classes then you can put a hidden tag in the new form - but this is bad&ugly
Best solution is probably STI model and 2 separate resources for the 2 classes and everything will work out of the box. If this isn't an option, at least separate the controllers and make things clean that way, it's much better to reuse views then to reuse controllers

Devise - Authenticate user (after validations) on a create action

Using Devise, I know how to protect controller actions from non-signed-in users through:
before_filter :authenticate_user!
In order to illustrate what I am trying to achieve, please see an example:
I have the following controller: (a project belongs to a user)
projects_controller.rb
def create
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
What I am looking for is a way that users can interact more with the website before having to sign up/sign in. Something like:
after_validation :authenticate_user!
if the user is not signed in, and redirect him after success (sign up/sign in) to the "project" show page.
Things I thought:
1.) Change the controller in order to accept a project object without user_id, ask for authentication if the user is not signed in, then update attributes with the user_id
I try to do it like this first and it results to a very ugly code. (Moreover authenticate_user! doesn't redirect to the #project which lead to more customization)
2.) Create a wizard with nested_attributes (project form and nested new registration form and session form)
3.) Something better? (a custom method?)
It seems authologic manages this more easily. I'm not sure it is a reason to switch so I would like to have your idea/answer on this. Thanks!
EDIT
references: Peter Ehrlich answer comment
CONTROLLER WITH VALIDATIONS LOGIC
projects_controller.rb
def create
unless current_user
#project = Project.new(params[:project]) # create a project variable used only for testing validation (this variable will change in resume project method just before being saved)
if #project.valid? # test if validations pass
session['new_project'] = params[:project]
redirect_to '/users/sign_up'
else
render :action => 'new'
end
else
#project = current_user.projects.new(params[:project])
if #project.save
redirect_to #project
else
render :action => 'new'
end
end
end
def resume_project
#project = current_user.projects.new(session.delete('new_project')) # changes the #project variable
#project.save
redirect_to #project
end
routes
get "/resume_project", :controller => 'projects', :action => 'resume_project'
application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
def after_sign_in_path_for(resource)
return '/resume_project' if session['new_project'].present?
super
end
Something like this should work:
def create
unless current_user
session['new_project'] = params[:project]
redirect_to '/register'
return
end
# and on to normal stuff
# in your devise controller
def after_sign_in_path
return '/resume_project' if session['new_project'].present?
super
end
# back in projects_controller now
def resume_project
#project.create(session.delete('new_project'))
# you know the drill from here
# I'd also put in a check to make an error if the session is not set- in case they reload or some such
Keep in mind that session is a cookie in the browser, and thus has a size limit (4kb). If you're posting images or other media, you'll have to store them temporarily server-side.
Another option would be to create a userless project, and use a similar technique to allow them to claim it as their own. This would be nice if you wanted unclaimed projects displayed to all to be available as a flow.
I haven't tested it out, but it should be possible to store the action the user was going to, I.e. create, with the params hash that was submitted and redirect to it upon successful login. It would then handle the error cases as normal.
Have you tried that?

Validating forms with variables

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.

How do I use redirect_to if I've got multiple controllers in different subdirectories?

I've created a small application to learn RoR. (Book database) It consists of a read-only area and a read-write admin area.
After I've got the admin functionality working first, I've moved the controller into a subdirectory and created the read-only controller.
Now when I'm updating a book in the admin area, the redirect_to function redirects to the read-only area.
What am I missing?
Here's the code I'm using:
class Admin::BooksController < ApplicationController
<snip>
def update
#book = Book.find params[:id]
respond_to do |format|
if #book.update_attributes params[:book]
flash[:notice] = "Book updated"
format.html { redirect_to #book }
format.xml { head :ok }
else
<snip>
end
end
end
<snip>
end
This update itself works but it redirects me to /books/1, but I'd want it to redirect to /admin/books/1. I could just hardcode the correct path, but I guess that's not very good style.
What would be the proper way?
PS: Please comment if you need further information.
You are telling it to redirect to book because you are using rails' built in magical recognition of what it should do with the #book object (which is build a url to show the book using the book controller.
format.html { redirect_to #book }
If you want it to go elsewhere you need to be explicit about where you want it to go using a hash for url_for
format.html { redirect_to :controller => 'admin/book', :action => 'show', :id => #book }
or use the paths like klew points out.
so for more detail -
redirect_to (#book) or
redirect_to book_path(#book)
are both shortcuts for this:
redirect_to :controller => book, :action => 'show', :id => #book.id
Rails creates for you url helpers based on your routes.rb. If you have namespace then you can use this:
admin_book_path(#book) # admin/books/2
admin_books_path # admin/books
edit_admin_book_path(#book) # admin/books/2/edit
and so on.
The other way is to use resource_controller it creates for you controller automaticaly and provides some ways to modify it if it's needed. It also gives you some useful url helpers
collection_path # admin/books
object_path # admin/books/2
When you use above helpers in views, than it generates url with namespace if you are in one, or without namespace otherwise.
resource_controller isn't perfect, but in most cases it works good and saves a lot of work.
You can also pass an array to redirect where the first element is a symbol representing the namespace, and the second the element the object.
redirect_to [:admin_book, #book]
You can also use this for form_for, link_to and any other helpers that require a path.

Resources