Don't know why I can't seem to figure this out since it seems like it should be so simple, but basically, I'm trying to create a link to an action (I want "Publish" to appear next to show, edit, delete) for each of a resource in Active Admin.
I used the code they suggest on their wiki:
action_item do
link_to "button label", action_path(post)
end
Problem is, I get an error because rails doesn't know what "post" is. It's nil. The version of the Wiki on Github has the same code, except they use "resource" instead of post. I wasn't sure if that was them implying that I would use my own resource name there, or if you're supposed to actually use the variable "resource". I tried the latter case and got a "Couldn't find without an ID" error.
So the question is, where do I set the variable name? What are they using as their iterator?
I used to use this:
action_item only: :show do |resource|
link_to('New Post', new_resource_path(resource))
end
UPDATE
action_item only: :show do
link_to('New Post', new_resource_path)
end
Thanks Alter Lagos
I have accomplished this with a very similar piece of code, see:
Inside my: app/admin/posts.rb
member_action :publish, method: 'get' do
post = Post.find(params[:id])
post.publish!
redirect_to admin_post_path(post), notice: 'Post published!'
end
In my case, I want the link buttons available only in the show action.
action_item :only => :show do
if post.status == 'pending'
link_to 'Publish', publish_admin_post_path(post)
elsif post.status == 'published'
link_to 'Expire', expire_admin_post_path(post)
else
end
end
Hope this helps you!
In ActiveAdmin you have to use resource to reference an object that you're working with.
When you use resource in an action like index, you will probably get an error as ActiveAdmin is not working with one. To prevent this, specify the actions you want the button to appear in.
To specify an action, give the argument only with an array of the actions you want the button to appear in. For example:
action_item :only => [:show, :edit] do
...
end
Related
I am new in Ruby and Rails and little bit confused about rendering and adding routes for a new template.
I have following link_to tag
<td colspan="3">
<%= link_to 'Show Current State', simulation, :action => :current_state, :class => 'btn btn-primary'%>
</td>
Where simulation is the name of controller and action is name of the method in SimulationController.
I added this in my routes.rb
resources :simulations, except: [:edit]
resources :simulations do
collection do
get 'current_state'
post 'current_state'
end
end
In my SimulationController class I added a new method i.e.
def current_state
byebug
end
My problem? routes is not re-directing to current_state method. Instead, it is redirecting to http://localhost:3000/simulations/{someID}
This redirection is calling show action.
def show
...
end
How can I make this work out and make <%= #simulation.dat %> line accessible in new.html.erb. Location of new.html.erb is in following path
views/simulations/index.html.js
views/similations/show.html.js
views/simulations/new.html.erb
This could be a basic question but I am new to rails 4. Thanks in advance.
Edit-1
Def of get_state method in controller
def get_state
#simulation = current_user.simulations.find(params[:id])
return not_found if #simulation.nil?
.....
/// How to send `#simulation` into `state.html.erb` formally as `new.html.erb`
end
You have too many misses in your code.
First, You don't need 2 resources :simulations, just merge them into one:
resources :simulations, except: :edit do
member do
get 'current_state', action: 'get_state'
post 'current_state', action: 'change_state'
end
end
Note that the original collection block is changed to a member block.
The difference between a collection block and a member block is that you need to provide an resource id for each routes in the member block, while no resource id is required for those in the collection block.
Also note that I added action: 'xxx' in each route, so you have to add these 2 actions in your SimulationsController, one for GET requests, and the other for POST requests.
UPDATE
In both of these actions, add render 'new' at the end.
END OF UPDATE
Run rake routes in your console (or bundle exec rake routes if you have multiple versions of rails installed), and you will see all the routes along with there url helper methods listed, like this:
Prefix Verb URI Pattern Controller#Action
current_state_simulations GET /simulations/:id/current_state simulations#get_state
current_state_simulations POST /simulations/:id/current_state simulations#change_state
...
According to the Prefix column, the link in the view should be
<%= link_to 'Show Current State', current_state_simulations_path(simulation), :class => 'btn btn-primary'%>
Or in short
<%= link_to 'Show Current State', [:current_state, simulation], :class => 'btn btn-primary'%>
UPDATE FOR Edit-1
Don't return in actions, because return doesn't stop rendering.
Instead, use raise ActionController::RoutingError.new('Not Found') to redirect users to the 404 page.
You can define an instance method in ApplicationController:
class ApplicationController < ActionController::Base
private
def not_found!
raise ActionController::RoutingError.new('Not Found')
end
end
And modify your SimulationsController:
def get_state
#simulation = current_user.simulations.find(params[:id])
not_found! unless #simulation
# ...
render 'new'
end
Best Practice
For dynamic page web applications, don't render views for non-GET requests!
Why? Because if a user POSTs some data to your web app, and then refreshes his/her browser, that request gets POSTed again, and your database got tainted. Same for PATCH, PUT and DELETE requests.
You can redirect the user to a GET path if the non-GET request succeeds, or to a 400 page if the non-GET request fails.
I have been trying to find a clean way to determine wether an action (in a rails controller) applies on members or on collection.
For example when you declare in routes.rb
resources :projects
You get the following methods defined "on collections":
index
create
new
and the following are defined on "on members":
update
show
delete
I am trying to find a way to leverage this great pattern in views for example:
<% if #controller.action.applies_on_members? %>
<%= link_to :destroy, :method => "delete", :confirm => "RU Sure" %>
<%= link_to :show, "show" %>
<% end %>
Similarly this could be useful in before_filters
before_filter :find_project, :only_on_members => true
Currently I have to do the following:
before_filter :find_project, :except => [:new, :create, :index, :export_all, :destroy_all, :archive_all]
This is quite annoying and my idea is that all such actions share a common behavior: they are defined on collections.
Does anyone have an idea of how to achieve this in clean way ?
NB: The reason I ask this question is because I am looking for something scalable in terms of development, such that the behavior of before_filters or some partials is automatically inherited by new custom actions.
Who never had to write down something like
<% unless ["new", "index", "some_other_action1", "some_other_action2", "some_other_action3", "some_other_action4"].include? #controller.action_name %>
<%= link_to "Destroy", :project, :method => :delete %>
<% end %>
Almost a year after posting this question, I know have found a reasonable solution for this question.
If we look at what on => :member and on => :collection really do, the difference is basically that on => :member generates a path including the :id param, whereas the on => :collection version does not.
A simple solution would then to test whether params[:id] is blank or not in a custom method. It would look like this (say in application_controller.rb):
class ApplicationController < ActionController::Base
def on_member?
!params[:id].blank?
end
helper_method :on_member? #method will be accessible in views
end
As described in this post for example, we can do something like this in a before_filter:
before_filter :find_project, :if => :on_member?
In views, this allows me to check the kind of method like so:
<% if on_member? %>
<%= link_to :destroy, :method => "delete", :confirm => "RU Sure" %>
<%= link_to :show, "show" %>
<% end %>
First of all, the pattern on operating on collections and their entries is part of REST which is one of the pillars of Rails. What you've identified is essentially what REST calls collections and entries. In theory, you can apply the most common HTTB verbs on either the collection, or an entry within that collection, where: GET; retrieve, PUT; replace, POST; create and DELETE; destroy. Although it's a little unclear what POST would do on a single entry.
In practise, and in Rails, however, you usually use those you've identified:
Collections
Create entry (POST)
View entries (GET)
Entry
Update entry (PUT)
Show entry (GET)
Delete entry (DELETE)
These are the RESTful routes Rails adds. However, as you know Rails also boxes two other actions to implement a user page which can provide the necessary data to these endpoints. (new, edit),
It's rare I see people coming down to these definitions. As I see it there's at least two ways to go about this problem if you really want to solve it (see bottom of the post):
You could monkey-patch "_normalize_callback_options" to get the exact syntax you proposed. This is relatively straight-forward, but if upstream changes anything about the structure, you have to change your patch, thus it's not very sustainable. Therefore I would not recommend that approach, no matter how tasteful the syntastic sugar looks. Of course you could also try to commit this upstream, but it's unlikely to be accepted due to the reasons mentioned below. :-)
Instead I'd put your definitions in an initializer: config/initializer/rest.rb
ENTRY_ACTIONS = [:edit, :new, :show, :delete, :update]
COLLECTION_ACTIONS = [:create, :index]
Then use them in your filters like:
before_filter :find_project, only: ENTRY_ACTIONS
For access in your views, add an entry_action? and collection_action? method to your application helper:
module ApplicationHelper
def entry_action?
ENTRY_ACTIONS.include?(controller.action_name.to_sym)
end
def collection_action?
COLLECTION_ACTIONS.include?(controller.action_name.to_sym)
end
end
Then in your views:
<% if entry_action? %>
# code
<% end %>
I wouldn't extract this to constants myself, but just write the array directly since it's much more clear in my opinion to read when I come back later. Furthermore, most people would have to think twice every time they encounter these methods which is far from optimal.
You're taking a very common problem that's already solved in Rails for us and pretty much turning it upside down. Rather than asking the controller about its state all over your application, you should tell your views and controllers to do what it needs to do however it knows how to do it.
Since you already know for sure that the index, create and new actions apply to "collections" and that update, show and delete apply to "members", you should define each respective controller actions and views to work with such forms of resources.
Going back to the examples you've given, the links should be defined in a partial and have the appropriate view template render the partial.
Your before_filter should be written as before_filter :find_project, :only => [:update, :show, :delete].
Just the thought of seeing calls to #controller.action.applies_on_members? all over the place in your application raises a flag in my mind that you need to rethink your design. Again, you're trying to solve a problem that Rails already takes care of for you.
Let me preface this by saying, i'm pretty new to rails and programming.
I'm trying to make some links to toggle a boolean attribute on and off. I've essentially succeeded in doing it on a non-nested resource by doing the following:
Route:
resources :my_resource do
get 'toggle_attribute', :on => :member
end
Controller:
def toggle_attribute
#resource = Resource.find(params[:id])
#resource.toggle!(:attribute)
end
View:
<%= link_to "Toggle Resource", toggle_attribute_resource_path(#resource), :remote => true %>
First, like I said above, this works on my non-nested route, however no matter what solution I try to add to the controller I can't get my link to flash a message or re-direct to anything when clicked, you click the button and nothing happens, you have to manually refresh to see the change.
Second, I can't figure out how to get this same sort of thing to work on a route that is nested like so:
Route:
resources :resource_1 do
resources :resource_2
end
Can anyone give me some tips?
Thanks a ton in advance. This stuff has been driving me batty.
By using remote => true, you are telling it to make an ajax call. This means that you need to also add a toggle_attribute.js.erb file in your views folder and in that file use javascript to replace the link element or text with what you want.
Also make sure to respond to js by setting respond_to :html, :js at the top of your controller.
repond_to :html, :js
def toggle_attribute
#resource = Resource.find(params[:id])
#resource.toggle!(:attribute)
end
toggle_attribute.js.erb :
$('#toggler').html("my new html here");
in view:
<%= link_to "Toggle Resource", toggle_attribute_resource_path(#resource), :remote => true, :id => "toggler"%>
Update:
For your nested route try this:
resources :resource_1 do
resources :resource_2 do
member do
get :toggle_attribute
end
end
end
your path would be something like:
toggle_attribute_resource_1_resource_2_path(#resource, #resource2)
I want to hide the edit path if the object to edit has a certain status.
How can I do that?
I finally did it. I needed two things:
Redirect when access directly and hide buttons to the edit page.
To redirect when the user try to access directly to the edit page I use a before_filter:
before_filter :some_method, :only => [:edit, :update]
def some_method
redirect_to action: :show if status == something
end
To hide the buttons I do it like this:
ActiveAdmin.register Model do
config.clear_action_items!
action_item :only => [:show] , :if => proc { instance.status == something } do
link_to 'Edit', edit_model_path(instance)
end
end
If you are talking about hiding the edit link that is shown by default (along with the view and delete links) in the index action, you can customize the index view as follows:
ActiveAdmin.register Model do
index do
column :actions do |object|
raw( %(#{link_to "View", [:admin, object]}
#{link_to "Delete", [:admin, object], method: :delete}
#{(link_to"Edit", [:edit, :admin, object]) if object.status? }) )
end
end
end
Because the content of the column will be only what is returned by the column block, you need to return all three (or two) links at once as a string. Here raw is used so that the actual links will be displayed and not the html for the links.
This can be achieve using the following:
ActiveAdmin.register Object do
index do
column :name
actions defaults: true do |object|
link_to 'Archive', archive_admin_post_path(post) if object.status?
end
end
end
Note that using defaults: true will append your custom action to active admin default actions.
You could create a before_filter in your controller that only applies to edit action. It could check the status, and allow it to run or redirect_to depending on the return of the method.
Something like this in your applications controller:
def some_method
if foo.bar == true
redirect_to foos_path
end
end
Then in the beginning of your controller of question
before_filter :some_method, :only => :edit
A fully customizable solution would be to use an authorization adapter, either a custom one or a library such as pundit or cancan: https://activeadmin.info/13-authorization-adapter.html
My use case was around restricting actions based on the context (e.g. the user editing). I solved it locally like this:
controller do
def action_methods
if condition?
super
else
super - ['edit', 'update']
end
end
end
if u want to hide the "edit" link (in active_admin views) for object if the object holds some specific value, u can override the default view for the method and add condition before the link is displayed.
In Ruby on Rails, is it possible to change a default action for a RESTful resource, so than when someone, for example, goes to /books it gets :new instead of the listing (I don't care if that means not being able to show the listing anymore)?
I'd point out that if you are pointing /books to /books/new, you are going to be confusing anyone who is expecting REST. If you aren't working alone, or if you are and have other come on board later, or if you expect to expose an API to the outside, the REST convention is that /books takes you to a listing, /books/new is where you create a new record.
Not sure why would you do such a thing, but just add this
map.connect "/books", :controller => "books", :action => "new", :conditions => { :method => :get}
to your config/routes.rb before the
map.resources :books
and it should work.
Yes. You should be able to replace your index method in your controller...
def index
#resource = Resource.new
# have your index template with they proper form
end
In the same vein, you can just do
def index
show
end
def index
redirect_to new_book_path
end
I think would be the simplest way.