activeadmin override index action - ruby-on-rails

my problem is related to customization in ActiveAdmin.
First of all I can't get how to override index action. Everything looks simple according to the documentation but very few things work as expected. Eventually I came up with these two alternatives. First one is a blogpost which presents the following solution (which appear to work).
scope_to do
Class.new do
def self.projects
Project.where(:id => 1)
end
end
end
While this one, which is the solution for issue#511 does not work. Can anyone tell why??
scope_to :current_project
controller do
private
def current_project
Project.where(:id => 1)
end
end
What's your experience? How do you achieve index action customization?
I'm an experienced web developer but I'm new to Ruby world in general.
Do you think it's a good idea to use ActiveAdmin for a production project? What's you pick when it comes to Admin interface?
I've read about Rails Admin but looks like it's not easy to customize.
My biggest concern at the moment is about active admin not easy to customize to achieve UI or behavior which are very different from the ones that it offers by default.
What do you think?

If you want to customize controller see https://github.com/josevalim/inherited_resources. For example:
controller do
def index
# something
index! do |format|
format.html { redirect_to some_url }
end
end
protected
def collection
#projects ||= end_of_association_chain.paginate(:page => params[:page])
end
end

Related

Rails need advice on controller practices

I try to follow RESTfull approach in controllers, but sometimes I need action which won't fit into default ones. E.g. I have an index action rendering all articles which is
def index
#articles = Article.all
end
but what if I also want search them? should I create separate actions for it, or should I bloat existing controller, like:
def index
if params[:search]
#articles = Article.where(id: params[:search_id])
else
#articles = Article.all
end
end
What is the right way?
You should use same action and create a index action only. And search logic goes to Article model.
You should follow this
I would keep it in index. If you want to keep it clean and standardise it you could change the controller code thus:
def index
#articles = Article.search(params[:search])
end
and then add a class method in Article
class << self
def search(options={})
if options.blank?
return self.all
else
..other search logic
end
end
end
Note that the example you gave for a search doesn't really make sense as it's specifying the id of the article. If you already know the id then that's not really a search: you might as well just go to the show page for the article.

rails 4 user authentication

I'm using devise with my rails 4 app to handle the authentication, and no problems there.
However, I want to make sure a logged in user can only view / edit (via the show and update actions) the items that his user owns (that are linked to his user_id).
I think I could hack something to make this all work by checking the current_user.id, but many users in Stackoverflow and other places say to use cancan-- however it appears cancan is dead and gone, and there's a replacement called cancancan, which may be ok, but I don't know.
Is there a standard way to do this in Rails 4, or is the best route to still use a third party gem like cancancan? Is there a better gem?
I've been using Pundit instead of Cancan for the last few projects I've done. It is lightweight, flexible and easy to use. Here's the link: https://github.com/elabs/pundit
In regards to your question, you will create policies for each model. For each action you define a method. It's super simple and explained on the link I've attached. Here as an example you have update in your model (models/post.rb):
def update
#post = Post.find(params[:id])
authorize #post
if #post.update(post_params)
redirect_to #post
else
render :edit
end
end
Call authorize to define permissions.
In your policies/post.rb:
class PostPolicy < Struct.new(:user, :post)
def update?
user.admin? or not post.published?
end
end
That returns true or false. In your case if you want to check if the user is a owner you can place the following if statement:
if user.admin? || user.owner_of?(post)
You get the idea. You can also define scopes, etc.
I don't think there's a standard, per se, but rather it's based on what you need. For Rails 4, cancancan brings a lot to the table and is built off of a gem that has been used regularly by the Rails community.
The only other alternatives I'm familiar with are protector and pundit - maybe check those out.
However, if cancancan and protector don't fit your needs, you could always roll your own authorization solution, but to me, why reinvent the wheel if cancancan will satisfy your needs.
I'd recommend Action Access, it's much simpler and straightforward. It boils down to this:
class ArticlesController < ApplicationController
let :user, :all
let :guest, [:show, :index]
# ...
def edit
not_authorized! unless #article.user == current_user
# ...
end
# ...
end
First of this will automatically lock the controller, allowing only users to access every action, guests can only show or index articles. Then not_authorized! will reject and redirect with an alert any user other than the owner of the article.
What's good about this is that it makes controllers to be self contained, everything related to the controller is within the controller. This also makes it very modular and avoids leaving forgotten trash anywhere else when you refactor.
It's completely independent of the authentication system (so no problem with Devise) but it bundles a set of handy model additions that allow to do things like:
<% if current_user.can? :edit, :article %>
<%= link_to 'Edit article', edit_article_path(#article) %>
<% end %>
Here :article refers to ArticlesController, so the link will only be displayed if the current user is authorized to access the edit action in ArticlesController. It supports namespaces too.
You can lock controllers by default, customize the redirection path and the alert message, etc. Checkout the documentation for more.

How can I customize the way Rails 4 render() finds files?

I'm serving a versioned web service from Rails.
I would very much like to be able to call render like normal:
render 'index'
And have it correctly serve the requested version from the following:
index.v1.json.jbuilder
index.v2.json.jbuilder
index.v3.json.jbuilder
Assuming that I already know the requested version within the context of the controller action execution, how do I get render() to leverage it?
I have used the versioncake gem
You should definitely check this out. File name will be very close to what you have:
index.v1.json.jbuilder
would be
index.json.v1.jbuilder
Sounds like a builder design pattern might work here. Have a view builder object that returns the desired behavior.
module ViewBuiler
def build(name, api_version)
View.new(name, api_version).build
end
class View < Struct(:name, :api_version)
def build
[name, api_version, 'json', 'jbuilder'].join('.')
end
end
end
and in your controller you could just do something like:
ApplicationController
include ViewBuilder
end
MyController < ApplicationController
def index
...
# you can pass either strings or symbols to build and it will
# return 'index.v1.json.jbuilder'
render build(:index, params[:api_version])
end
end
And disclaimer, this is just an idea and not something I've implemented. I like the approach for 2 reason. The Controller actions remain skinny and don't have logic in it. A builder like this seems pretty easy to test. And it keeps the thing that might change (views etc) isolated into something that can change frequently as long as it retains it's interface that the Controllers will work with.
This seems like a candidate for Variants. This is new to 4.1 though.
class MyController < ActionController::Base
before_action :set_variant
def my_action
.....
respond_to do |format|
format.json do |json|
json.v1 do
# render whatever you need here
end
end
end
end
protected
def set_variant
request.variant = :v1 if request.params[:version] == "v1"
....
end
end

Rails: Verify correct user across multiple controllers

I have several controllers that require a correct user for their edit/update/delete actions. What is the Rails-way to accomplish the following:
Currently, in each controller I have the following code:
class FooController < ApplicationController
before_filter :correct_user, :only => [:edit, :update, :destroy]
# normal controller code
private
def correct_user
#foo = Foo.find params[:id]
redirect_to some_path unless current_user == #foo.user
end
end
I have similar code in 3 controllers. I started to bring it out to a helper like this:
module ApplicationHelper
def correct_user( object, path )
if object.respond_to? :user
redirect_to path unless object.user == current_user
end
end
But I'm wondering if this is a good way to do it. What's the accepted way to solve this?
Thank you
EDIT
The correct user check here is because I want to make sure it's only the author who can make edits/deltes to each of the objects.
To clarify, the objects would be things like Questions and Posts. I don't want to use something like CanCan as it's overkill for something simple like this.
I really like using RyanB's CanCan, which allows you to both restrict access to actions based on the user, and centralize such authorization into basically a single file.
CanCan on GitHub: https://github.com/ryanb/cancan
Screencast explaining how to setup/use it: http://railscasts.com/episodes/192-authorization-with-cancan
EDIT
No problem. I hear you on CanCan - it takes a little while to get up and running on it, but it's designed to do exactly what you're asking - per object authorization.
Alternative:
Another way to do this is move your authoriship/current_user check to the ApplicationController class, from which all of your other Controllers inherit (so they will get that code through inheritance - and you don't need to write the same code in multiple Controllers), and it would look something like...
class ApplicationController < ActionController::Base
...
helper_method :correct_user
private
def correct_user( object, path )
redirect_to path unless object.user == current_user
end
end
You should do the following :
def edit
#foo = current_user.foos.find(params[:id])
end
This way, only if the current user is the owner of the Foo he will be able to see it.

Using Devise, how do I make it so that a user can only edit/view items belonging to them

I've been struggling with this for a while now, so I thought I'd ask the experts.
I am trying to make it so that Users can only edit/view Items that they have created using Devise.
I have Users set up and working well. Items are being created with a user associated with them and I can verify this via rails console.
def create
#item = Item.new(params[:item])
#item.user = current_user
end
What I am trying to do now is to make it so that once logged in, users can only see the items that they have created, and no others.
In my Items controller have tried replacing:
def index
#items = Items.all
end
with
def index
#items = current_user.Items.find(params[:id])
end
but this doens't seem to work for me and I get
undefined method `Items' for #<User:0x007fdf3ea847e0>
Can anyone offer any advice as to what to try next?
Thanks so much.
Maybe I`m old school but I would not use current_user to find records, only to verify permissions. I would use the primary key relationships directly (they don't change):
#items = Item.find(:all, :conditions => { :user_id => current_user[:id] }
or
#items = Item.find_all_by_user_id current_user[:id]
As for setting permissions, devise actually doesn`t let you do that BUT there is the excellent supplement called Cancan, you should definitely look into it. With Cancan, you will have an ability.rb class that will define your permissions. What you are looking for then becomes:
class Ability
can [:read, :show, :edit, :update, :delete, :destroy], Item do |item|
item.user_id == user.id
end
# or
can :manage, Item do |item|
item.user_id == user.id
end
end
reading the Cancan docs would clarify the code above.
What you're trying to do is really close…
current_user is an "instance" of the User class.
What you want to do is use the association from the user instance, which is a special method applied to every user—"items". (If the Item class also has a belongs_to :user it'll have a method called user as well)
You want current_user.items.find(params[:id])
Also, when you create it, you could also use current_user.items.create(params[:item])
If I'm understanding your question, I think you might want to check out an authorization library - like CanCan to do this.
https://github.com/ryanb/cancan
It works pretty slick to handle permission type things like this. Many people use this library in conjunction with Devise.

Resources