Rails ActiveAdmin modify resource object - ruby-on-rails

I've currently got a user object but to avoid redundancy, I'd like to wrap it into a presenter object called MerchantUser/ProviderUser. However, with ActiveAdmin, I'm a little confused on how to do this. I've tried using before_create to change the user into the corresponding presenters but in index...do, I'm still seeing that user.class is equal to User and not the wrapper classes that I've defined.
I've looked into scoping_collection but unfortunately that only works on collections and not individual objects?
ActiveAdmin.register User, as: "Companies" do # rubocop:disable Metrics/BlockLength
before_create do |user|
if user.merchant?
user = MerchantUser.new(user)
else
user = ProviderUser.new(user)
end
end
actions :all, except: [:destroy]
permit_params :name, :email, contract_attributes: [:id, :flat_rate, :percentage]
filter :role, as: :select
index do # rubocop:disable Metrics/BlockLength
column :name do |user|
user.name <---I want it so I can just do this without the if/else blocks like below.
end
column :role
column :contact_phone
column :email
column :website do |user|
if user.merchant?
user.company.website
else
user.provider.website
end
end
column :flat_rate do |user|
money_without_cents_and_with_symbol(user.contract.flat_rate)
end
column :percentage do |user|
number_to_percentage(user.contract.percentage, precision: 0)
end
actions
end

Have you looked into Active Admin's support for decorators? This page is quite comprehensive. The best way to implement them depends on how your decorator/presenter object is implemented.
Link summary: use decorate_with or look into using this gem for PORO support

Are you sure you want/need a presenter here? You can register the same Rails model multiple times as ActiveAdmin resources with different names and customizations (filters, index page, forms, etc). You can also use Rails STI or just subclass Rails models, perhaps with different Rails default_scope and then register the subclasses.

Related

DRY-ing up ActiveAdmin

I've got multiple resources in my ActiveAdmin installation that share quite a lot of the same traits, like:
The same or similar scopes
Equal or similar controller methods (action_methods, for example)
Similar attributes (with code blocks) in the show action
Similar attributes (with code blocks) in the edit action
What is the best way to avoid duplicating this functionality across the different resources?
I have set up decorators to avoid duplicating functionality in the index view, but I'm not sure if (and how?) this could be used in the other cases.
You can also extend your module. For example:
module AccountManageable
def has_manageable_account
permit_params :name, :email, :role, :avatar
filter :name, as: :string
filter :email, as: :string
# ... other DSL methods
end
end
and then in your admin
ActiveAdmin.register Admin do
extend AccountManageable
has_manageable_account
end
You need to extend the DSL with monkey patch:
module ActiveAdmin
# This is the class where all the register blocks are evaluated.
class ResourceDSL < DSL
def your_custom_method attr
#common code
end
end
end
Now you can use your_custom_method in your registered resource file.
https://github.com/activeadmin/activeadmin/blob/master/lib/active_admin/resource_dsl.rb

Rails 4 | Assign Role to User in Form using Bitmask | RoleModel

I got stuck on the following issue - I suspect there is a simple solution to it, but I just can't figure it out.
I am using Role Model Gem with Rails 4.
All works well, and the assigned roles are stored perfectly well in the roles_mask attribute (integer) of the user model as internal bitmask.
Now, I would like that Admins can assign as well as remove Roles from users via a FORM (view). I am not a Rails Ninja, so there might be a trick to do this.
According to the Doc, I can do the following:
# role assignment
>> u.roles = [:admin] # ['admin'] works as well
=> [:admin]
# adding roles (remove via delete or re-assign)
>> u.roles << :manager
=> [:admin, :manager]
So that is understood.
And my approach was to query for all valid roles in the form:
# get all valid roles that have been declared
>> User.valid_roles
=> [:admin, :manager, :author]
Then list them as checkbox. Once the form gets submitted I assign / remove roles.
The question:
Is that the right approach, does this even work, and if so how?
Since I had the problem not once I figured out a way - here is the solution:
More details here.
Many roles per user
I assume in this answer that you have roles in form of symbols (below I modified this solution in a way that it works with symbols) - so to work with Pundit in case you are using it.
# in models/user.rb
ROLES = [:admin, :manager, :general, :custom_role, :another_custome_role, :banned]
It is possible to assign multiple roles to a user and store it into a single integer column using a bitmask. First add a roles_mask integer column to your users table.
rails generate migration add_roles_mask_to_users roles_mask:integer
rake db:migrate
Next you'll need to add the following code to the User model for getting and setting the list of roles a user belongs to. This will perform the necessary bitwise operations to translate an array of roles into the integer field.
# in models/user.rb
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
def roles
ROLES.reject do |r|
((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
end
end
Here a tiny modification to make it work with Rolify & Pundit
def roles=(roles)
self.roles_mask = (roles.map { |x| x.to_sym } & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
If you're using devise without strong parameters, don't forget to add attr_accessible :roles to you user model.
If you're using devise with strong_parameters, either as a gem in a Rails 3 app, or as is built-in in Rails 4, dont forget to add the roles to the permitted list in the controller
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, roles: [])}
end
end
See the section on strong parameters in the Devise documentation.
You can use checkboxes in the view for setting these roles.
<% for role in User::ROLES %>
<%= check_box_tag "user[roles][#{role}]", role, #user.roles.include?(role), {:name => "user[roles][]"}%>
<%= label_tag "user_roles_#{role}", role.humanize %><br />
<% end %>
<%= hidden_field_tag "user[roles][]", "" %>
Finally, you can then add a convenient way to check the user's roles in the Ability class.
# in models/user.rb
def is?(role)
roles.include?(role.to_s)
end
# in models/ability.rb
can :manage, :all if user.is? :admin
See Custom Actions for a way to restrict which users can assign roles to other users.
This functionality has also been extracted into a little gem called role_model (code & howto).
If you do not like this bitmask solution, see Separate Role Model for an alternative way to handle this.

Rails + Active Admin: Display name

I am using Active Admin with Ruby on Rails and I am having an issue with the way that some models are shown in the panel.
Taking the class User as an example, if I do not define any method to display it friendly, I see #<User:00000006b47868>. So Active Admin suggests implementing a method to specify, for each class, how to show it.
According to the documentation (http://activeadmin.info/docs/3-index-pages/index-as-table.html), Active Admin looks for one of these methods to guess what to display, in the following order:
:display_name, :full_name, :name, :username, :login, :title, :email, :to_s
So having this method within the User class would solve the problem:
def display_name
return self.id.to_s + '-' + self.full_name
end
However, before using Active Admin, I was already using the method display_name with other purposes (for example, in views) in order to show the user name in a friendly way, and I do not want to show the same in Active Admin panel.
I cannot change the name of the method because I use display_name in a lot of files along the project, and changing it would probably introduce bugs in the application.
An ideal solution for this case would be to have something like an active_admin_name method that is used by Active Admin to show models in its panel. So the question is:
Is there any way to have a method that is called by Active Admin instead of display_name? For example, to result in the following order:
:active_admin_name, :display_name, :full_name, :name, :username, :login, :title, :email, :to_s
I have searched in Active Admin documentation and in config/initializers/active_admin.rb, but I could not find a way to do it.
Thanks!
Try
ActiveAdmin.setup do |config|
config.display_name_methods = [:active_admin_name, :display_name ...]
end
You can find this setting in lib/active_admin/application.rb
You could also use :title key to define it for each page, like this:
show(title: 'Something') do |record|
...
end
# or with a Proc
show(title: ->(record) { record.another_method_to_display }) do |record|
...
end

How to create an object of a STI subclass using ActiveAdmin

Given the following setup(which is not working currently)
class Employee < ActiveRecord::Base
end
class Manager < Employee
end
ActiveAdmin.register Employee do
form do |f|
f.input :name
f.input :joining_date
f.input :salary
f.input :type, as: select, collection: Employee.descendants.map(&:name)
end
end
I would like to have a single "new" form for all employees and be able to select the STI type of the employee in the form.
I am able to see the select box for "type" as intended but when I hit the "Create" button, I get the following error:
ActiveModel::MassAssignmentSecurity::Error in Admin::EmployeesController#create
Can't mass-assign protected attributes: type
Now, I am aware of the way protected attributes work in Rails and I have a couple of workarounds such as defining Employee.attributes_protected_by_default but that is lowering the security and too hack-y.
I want to be able to do this using some feature in ActiveAdmin but I can't find one. I do not want to have to create a custom controller action as the example I showed is highly simplified and contrived.
I wish that somehow the controller generated by ActiveAdmin would identify type and do Manager.create instead of Employee.create
Does anyone know a workaround?
You can customize the controller yourself. Read ActiveAdmin Doc on Customizing Controllers. Here is a quick example:
controller do
alias_method :create_user, :create
def create
# do what you need to here
# then call create_user alias
# which calls the original create
create_user
# or do the create yourself and don't
# call create_user
end
end
Newer versions of the inherited_resources gem have a BaseHelpers module. You can override its methods to change how the model is altered, while still maintaining all of the surrounding controller code. It's a little cleaner than alias_method, and they have hooks for all the standard REST actions:
controller do
# overrides InheritedResources::BaseHelpers#create_resource
def create_resource(object)
object.do_some_cool_stuff_and_save
end
# overrides InheritedResources::BaseHelpers#destroy_resource
def destroy_resource(object)
object.soft_delete
end
end

Rails 3.1 attr_accessible verification receives an array of roles

I would like to use rails new dynamic attr_accessible feature. However each of my user has many roles (i am using declarative authorization). So i have the following in my model:
class Student < ActiveRecord::Base
attr_accessible :first_name, :as=> :admin
end
and i pass this in my controller:
#student.update_attributes(params[:student], :as => user_roles)
user_roles is an array of symbols:
user_roles = [:admin, :employee]
I would like my model to check if one of the symbols in the array matches with the declared attr_accessible. Therefore I avoid any duplication.
For example, given that user_roles =[:admin, :employee]. This works:
#student.update_attributes(params[:student], :as => user_roles.first)
but it is useless if I can only verify one role or symbol because all my users have many roles.
Any help would be greatly appreciated
***************UPDATE************************
You can download an example app here:
https://github.com/jalagrange/roles_test_app
There are 2 examples in this app: Students in which y cannot update any attributes, despite the fact that 'user_roles = [:admin, :student]'; And People in which I can change only the first name because i am using "user_roles.first" in the controller update action. Hope this helps. Im sure somebody else must have this issue.
You can monkey-patch ActiveModel's mass assignment module as follows:
# in config/initializers/mass_assignment_security.rb
module ActiveModel::MassAssignmentSecurity::ClassMethods
def accessible_attributes(roles = :default)
whitelist = ActiveModel::MassAssignmentSecurity::WhiteList.new
Array.wrap(roles).inject(whitelist) do |allowed_attrs, role|
allowed_attrs + accessible_attributes_configs[role].to_a
end
end
end
That way, you can pass an array as the :as option to update_attributes
Note that this probably breaks if accessible_attrs_configs contains a BlackList (from using attr_protected)

Resources