ActiveAdmin action items depending on status of data - ruby-on-rails

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.

Related

Rails: How to review content before submit/save?

I know it is simple but I can't get my head around a solution.
It is a job board site. Lets say it's functionality similar to this site. When a user fill all required information and click "To next step" or "Preview", another page loads with all filled data. That page is similar to the final page when data is saved.
When user on preview page, it can go forward and submit the page (in this case it will be saved to DB). Or, click back to Edit the job.
I tried the following::
Within _form.html.erb I added a preview button
<%= f.submit "Preview", :name => 'preview' %>
Within JobControllers I altered create method
def create
if params[:preview]
#job = Job.new(jobs_params)
render 'jobs/preview'
else
#job.save
end
end
Created a Preview view /jobs/preview.html.erb
Now I have 2 problems.
1- Within my preview page, I have an edit button like so: <%= link_to "Edit Job", edit_job_path(#job) %>. But I have an error because I can't find #job. Error says: No route matches {:action=>"edit", :controller=>"jobs", :id=>nil} missing required keys: [:id]
SOLUTION Changed like to <%= link_to 'Back to edit', 'javascript:history.go(-1);' %>
2- How I would submit and add to my DB all information on preview page?
Thank you.
Once I've given a similar task. What I've done is to save records, but not to publish. In my index (resource listing) action of relevant controller, I only fetch published records. Also show action prechecks if that record's published attribute is set to true.
What was my model/controllers looked like before
#model
class Book < ActiveRecord::Base
...
scope :active, -> { where(published: true).some_other_queries }
self.active?
(published && some_other_requirements)
end
...
end
#controller
def index
#books = Book.active
...
end
def show
if #book.active?
render 'show'
...
else
...
end
end
First added a secret key for previews.
#model
def secret
#some custom random key generation
# e.g. Digest::MD5.hexdigest("#{id}_#{ENV['RAILS_SECRET']}")
end
Then added preview action to controller
def preview
# i don't check if the record is active.
# also added a security layer, to prevent irrelevant guys to view
# that record
if #book.secret == params[:secret]
render 'show'
else
...
end
end
In dashboard
...
= link_to "Preview", preview_book_path(book, secret: book.secret)
...
then added a member route
#routes
resources :books do
get :preview, on: :member
end
When I have to do something like this what I normally do is create a review table in my app. This table looks just like the table that is going to saving to.
When they press the "Approved" or "Save" button just populate the new table with the proper data.
I like to create a routes to handle this
resources :something do
match 'move_to_something_else' => 'somethings#move_to_something_else', as: :move_to_something_else, via: :all
end
Now on the controller we can do the following:
def move_to_something_else
#something = Something.find(params[:id])
#something_else = SomethingElse.new
#something_else.name = #something.name
....
#something_else.save
redirect_to something_else_path(#something_else)
end
Alternative you could add a state to your table with the default value of 'draft'
# config/routes.rb
resources :something do
match 'published' => 'somethings#published', as: :published, via: :all
end
# Controller
def published
#something = Something.find(params[:id])
#something.state = 'published'
#something.save
redirect_to something_path(#something)
end

Delete session variable with link_to

I'm wondering what's the best way to implement a link or a button that deletes a session object ( session[:object] ). I'm storing some data in the browser and users must be able to delete these whenever they want. Any idea ?
I would create a route called clear_[whatever], and bind it to a custom action in your controller.
your route would look like this:
get '/clear-[whatever]', to: '[your controller]#clear_[whatever]', via: [:destroy], as: :clear_[whatever]
your button would then be:
<%= link_to clear_[whatever]_path ... %>
and your controller action should do:
def clear_[whatever]
#clear = session[:whatever] = nil
if #clear ...
...
else
...
end
You could make a controller (or, alternatively, a non-REST method in your object's existing controller):
Controller
class WidgetSessionsController < ApplicationController
# "Delete" widget session data
def destroy
# Remove the widget from the session
#_current_user = session[:widget] = nil
end
end
routes.rb
# obviously you could add create and update here
resources :widget_sessions [only: destroy]
View
<%= link_to 'Click here to delete your widget session!', widget_session_path(), method: :delete %>

Why does the params "id" key change from params[:id] into params[:model_id]?

When I go to the characters controller, show action, all the normal params[:id] is as how it should be according to REST.
In the show view, I render a partial. In that partial, I have a link that goes to the vote_socionics action. This action is defined under a socionics_votes module, which gets included by the characters controller. (I have it set up this way because I have other controllers that also include this module).
My problem is that when I click on this link, and it goes to the set_votable private method within the socionics_votes_module.rb file, the params[:id] is no longer present. Using pry, I found that it actually turns into params[:character_id]
Questions:
1) Why does this happen (is it because it goes to a "different" controller, even if it's a module?)
2) How do I work around this? I would think that it would be more elegant to have it be params[:id], instead of having to do an if-else to account for both keys.
characters_controller.rb
class CharactersController < ApplicationController
include SocionicsVotesModule
def show
#character = Character.find(params[:id])
end
characters/show.html.haml
= render partial: 'votes/vote_socionics',
locals: { votable: #votable, votable_name: #votable_name, socionics: #socionics }
_vote_socionics.html.haml
= link_to content_tag(:div,"a"), send("#{votable_name}_vote_socionics_path", votable, vote_type: "#{s.type_two_im_raw}"),
id: "vote-#{s.type_two_im_raw}",
class: "#{current_user.voted_on?(votable) ? 'voted' : 'not-voted'}",
method: :post,
data: { id: "#{s.type_two_im_raw}" }
socionics_votes_module.rb
module SocionicsVotesController
extend ActiveSupport::Concern
included do
before_action :set_votable
end
private
def set_votable
votable_constant = controller_name.singularize.camelize.constantize
#votable = votable_constant.find(params[:id]) # This is where it fails, since there is no params[:id], and rather, params[:character_id]
end
def set_votable_name
#votable_name = controller_name.singularize.downcase
end
routes.rb
concern :socionics_votes do
post 'vote_socionics'
end
resources :characters, concerns: :socionics_votes
resources :celebrities, concerns: :socionics_votes
resources :users, concerns: :socionics_votes
The URL of the link in the partial when hovered over.
localhost..../characters/4-cc/vote_socionics?vote_type=neti
Something like .find(params[:id] || params[:"#{#votable_name}_id"]) didn't work, and seems silly.
You need to add the vote_socionics route as a member of the resource:
concern :socionics_votes do
member do
post 'vote_socionics'
end
end
This way the id parameter gets set correctly

Allowing admins to add users with Devise

I'm trying to make it so only admins can add uses with devise. I've gotten it mostly working however now when I'm logged in as an admin and submit the sign up form it kicks me back with the error: You are already signed in.
I've tried to follow the instructions here: http://wiki.summercode.com/rails_authentication_with_devise_and_cancan but it doesn't seem to mention this situation.
Do I need to do further overriding in the editors_controller to allow this?
Here are my routes ("editors" is the name of my user model):
devise_for :admins, :skip => [:registrations]
as :admin do
get 'admin/editors' => 'editors#index', as: :admin_editors
get 'admin/editors/new' => 'editors#new', as: :new_editor
delete 'admin/editors/:id' => 'editors#destroy', as: :destroy_editor
end
devise_for :editors, :skip => [:registrations], :controllers => { :registrations => "editors" }
and my editors_controller in "app/controllers/"
class EditorsController < Devise::RegistrationsController
before_filter :check_permissions, :only => [:new, :create, :cancel]
skip_before_filter :require_no_authentication
def dashboard
render "editors/dashboard.html.haml"
end
def index
#editors = Editor.all
respond_to do |format|
format.html
end
end
private
def check_permissions
authorize! :create, resource
end
end
EDIT
I noticed this Processing by Devise::RegistrationsController#create as HTML in the logs when I submit the form. I had suspected that perhaps the skip_before_filter :require_no_authentication wasn't being called, but assumed that because the EditorsController was inheriting from RegistrationController that before filter would work properly. Is that not the case?
You'll want to implement your own create method on EditorsController instead of inheriting that action from Devise::RegistrationsController. As you're seeing, the method in Devise::RegistrationsController will first check to see if you're already logged in and kick you back if you are. If you're not logged in it will create a User account and then log you in as that user.
You're trying to get around that problem with skip_before_filter :require_no_authentication, but it's likely that your form is POSTing to /editors instead of /admin/editors. So, you'll need to add a route that allows you to get to create on the EditorsController :
as :admin do
post 'admin/editors' => 'editors#create'
# your other :admin routes here
end
Then you'd want to implement a scaled down version of create. You probably want something kind of like this :
class EditorsController < Devise::RegistrationsController
def create
build_resource(sign_up_params)
if resource.save
redirect_to admin_editors_path
else
clean_up_passwords resource
respond_with resource
end
end
# your other methods here
end
You'll also want to make sure that the admin/editors/new template is pointing the form to the correct route ('admin/editors').
None of the googleable solutions worked when I tried them. This works
What I did was create a new action in the controller and a new route for it, and connect the links on my views that normally connect to create to now call my route and action.
But that wasn't enough. Because Devise is listening and will grab any add you try to do and validate it through it's own code. So instead I just add the new user record with a sql insert.
Add this route
post 'savenew', to: 'users#savenew'
Add this action to the user controller:
def savenew
rawsql = "insert into users (email, created_at,updated_at) values ('#{user_params[:email]}',now(), now())"
sql = rawsql
ActiveRecord::Base.connection.execute(sql)
redirect_to action: 'index''
end
View: new.html.erb
change the form_for so that submit will go to the new route and action, not the default Rails one.
<%= form_for User, :url => {:action => "savenew"} do |f| %>
Using Rails 4.2.6 here (my model is User instead of Editor). The following solution bypasses (I think) any devise actions that may interfere with new User creation by the admin:
Add this action to the Users controller:
def savenew
User.create_new_user(user_params)
redirect_to action: 'index'
end
Add this private method to the Users controller if it does not exist:
private
def user_params
params.require(:user).permit(:email, :password,
:password_confirmation)
end
Add this to config/routes.rb:
match '/savenew', to: 'users#savenew', via: :post
Add this class method to the User model:
def self.create_new_user(params)
#user = User.create!(params)
end
I don't have a separate Admin class in my application. Instead, I defined an admin attribute for Users and check for it with a :validate_admin before_action filter in the UsersController.
I wanted to be able to create a new user from the :index view, so I added a button:
<%= button_to 'New User', '/new_user', class: 'btn btn-primary',
method: :get %>
You might have to tweak the above solution if you have any after_create actions in the User model (e.g. sending a welcome email).

Active Admin Custom Action Item Resource Name

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

Resources