Rails admin tables with read only access - ruby-on-rails

I am using rails server with rails_admin
I want to add a table to my include_models but I don't want to let write access to one/few of the models:
My admin looks like this:
# frozen_string_literal: true
RailsAdmin.config do |config|
### Popular gems integration
# == Devise ==
config.authenticate_with do
warden.authenticate! scope: :user
end
config.actions do
dashboard # mandatory
index # mandatory
new
export
bulk_delete
show
edit
delete
show_in_app
end
config.included_models = %w[ MyModel1
MyModel2]
end
I would like to add something like
config.read_only_models = %w[MyModel3, MyModel4]
Any suggestion ?

You need to do it on the rails admin model configuration to have access to the current user.
I'm afraid this rules out doing it at the RailsAdmin.config level.
You'll need to do it then field by field.
class MyModel3 < ApplicationRecord
rails_admin do
configure :field do
read_only do
bindings[:view].current_user.admin?
end
end
configure :field2 do
read_only do
bindings[:view].current_user.admin?
end
end
configure :field3 do
read_only do
bindings[:view].current_user.admin?
end
end
# ...
end
end
Not ideal but you'll see that is impossible with the current implementation of the actions by taking a look at the code of the edit action that inherits from base.

I've divided my answer to 2 sections as it may apply to different scenarios:
General case:
General behaviour can be set by adding a readonly? method for the model(s).
If the value is set to true, attempting to update a record will result in an error.
See here for more details.
def readonly?
true
end
ActiveRecord’s underlying persistence will check readonly? before creating or updating any records.
You can also add dynamic content inspection such as:
def readonly?
read_only_list.include? (self.class.name)
end
RailsAdmin specific behaviour (2nd scenario):
If you want to set a specific behaviour for RailsAdmin, you can create a special role and then use
CanCanCan which is an authorization library which restricts what resources a given user is allowed to access.
It can also restrict RailsAdmin & grant access using an Ability class which defines different permissions depending upon the user's role.
See an example how to Use different Ability classes for front-end and admin

Related

Two modules with same method names included in same class

I am working with ruby on rails and I am basically trying to include two modules into the same model/class with both modules having the same method names. An example will be demonstrated below, however my questions are:
Is there a way to include module conditionally? or
Is there a way to invoke based on the specific instance of the class.
An example is a simple complete profile wizard.
How its suppose to work
Case 1: If the user is lets say a Transporter, step_one is completed when the user has a company_name is present.
Case 2: On the otherhand if the user is a Client, step_one is completed when the user has a telephone present.
class User < ApplicationRecord
include ClientWizard
include TransporterWizard
end
module ClientWizard
def step_one_completed?
self.name.present?
end
end
module TransporterWizard
def step_one_completed?
self.company_name.present?
end
end
No, module methods all exist within the class's namespace. Consequently, this doesn't seem like a particularly good use case for modules.
You could give the methods module-specific names (client_wizard_step_one_completed?), but I'd recommend instead defining the wizards as separate classes, and passing the user instance as a parameter.
class User < ApplicationRecord
def client_wizard
ClientWizard.new(self)
end
end
class ClientWizard
def initialize(user)
#user = user
end
def step_one_completed?
#user.name.present?
end
end

Breadcrumbs list in ActiveAdmin shows wrong name when using friendly_id

I have a model named Company that has code. The column is used for friendly_id.
class Company < ActiveRecord::Base
extend FriendlyId
friendly_id :code, use: :slugged
end
ActiveAdmin doesn't recognize friendly_id, so that I had to override find_resource method like this:
ActiveAdmin.register Company do
controller do
def find_resource
scoped_collection.friendly.find(params[:id])
end
end
end
With this code I can edit the model attributes by ActiveAdmin, but breadcrumbs list in edit page shows wrong company's name. (That is using id, instead of code)
Where and how can I configure to use ActiveAdmin and friendly_id at the sametime?
Thanks for #mark-merrit, by this code breadcrumbs shows proper company name.
app/admin/companies.rb
ActiveAdmin.register Post do
breadcrumb do
links = [link_to('Admin', admin_root_path), link_to('Companies', admin_companies_path)]
if %(show edit).include?(params['action'])
links << link_to(company.name, admin_company_path)
end
links
end
end
Maybe there is a better implementation for parents path in the breadcrumbs. Let me know if you know about it.
From ActiveAdmin source code, which can be found in lib/active_admin/dsl.rb
# Rewrite breadcrumb links.
# Block will be executed inside controller.
# Block must return an array if you want to rewrite breadcrumb links.
#
# Example:
# ActiveAdmin.register Post do
# breadcrumb do
# [
# link_to('my piece', '/my/link/to/piece')
# ]
# end
# end
#
def breadcrumb(&block)
config.breadcrumb = block
end
Since it is executed in the controller, you can use your custom find_resource method to configure it to your liking!
Does prepending :code to config.display_name in initializers/active_admin.rb work for you?

Multi theme support for Rails app

What will be a better approach to display labels in a web app based on a user.
For ex: We have User groups A and B. And labels and headers differ between groups. There will be no changes in the layout and only text differs.
I was looking at Rails themes. However looks like it works well for assets and themes.
Looking for suggestions here. App is on Rails 4.
Probably you may use decorator pattern with gem draper
Implementation will look something like this:
# app/decorators/group_decorator.rb
class GroupDecorator < Draper::Decorator
def name
end
end
# app/decorators/group_one_decorator.rb
class GroupOneDecorator < GroupDecorator
def name
'group one specific message'
end
end
# app/decorators/group_one_decorator.rb
class GroupTwoDecorator < GroupDecorator
def name
'group two specific message'
end
end
Then wherever you can just call decorate on group
user.group.decorate.name
or
GroupOneDecorator.new(user.group).name

Rails Devise attr_accessible problem

Im trying to add devise authorization to my rails 3 app.
Its all going well except Im also trying to follow this tutorial to dynamically set attr_accessible for role_ids only for admin users (I dont want regular users changing their role, but an admin should be able to do so)... the problem is, the railscast tutorial approach assumes I have access to change the controller behavior when in fact devise is handling all that under the hood.
Please Help
You can subclass the Devise controllers, you just have to generate the views and move them to the correct place. Check out "Configuring views" and "Configuring controllers" in the Devise readme.
I ended up adding role_ids to attr_accessible, then subclassing the RegistrationsController and adding a before_filter to remove that param for non-admins.
class Users::RegistrationsController < Devise::RegistrationsController
before_filter :remove_admin_params, :only => [:create, :update]
protected
# disable setting the role_ids value unless an admin is doing the edit.
def remove_admin_params
params[:user].delete(:role_ids) unless current_user.try(:admin?)
end
end
Just make sure to add the registration views to /app/views/users/registrations/.
The best way I found to handle this is from RailsCast 237. It is more verbose than Arrel's answer, but it does not force you to add role (or other fields) to attr_accessible.
Add the following method in an initializer:
class ActiveRecord::Base
attr_accessible
attr_accessor :accessible
private
def mass_assignment_authorizer(role = :default)
if accessible == :all
self.class.protected_attributes # hack
else
# super returns a whitelist object
super + (accessible || [])
end
end
end
Then in your controller, you can do:
user.accessible = :role if can? :set_role, resource
This call, unfortunately, has to be made after the user (or whatever) object has been instantiated. That means that you would have to subclass the controller, and call this after the resource instantiation in update and create.
This is for Rails 3.2. In earlier versions I believe the method mass_assignment_authorizer does not take a parameter. The attr_accessible with no values sets a fail-safe application wide denial for mass assignment. This can also be done in the application.rb file with
config.active_record.whitelist_attributes = true

How to overwrite/extend existing spree classes in app/models?

I want to extend Class Role such that I can add more roles to the
roles table in Spree. My application would have different prices based
on roles.
By default roles have: ("admin" and "user") in it. I want to add more
types to the table.
Q1: Can I just extend the Role class in one of my extensions?
Q2: How can I implement (actually extend on app/models/Variant.rb) the
prices based on different roles such that it just grabs price from one
place? So that I dont have to change code in *_html.erb files where
its using price.
If I can get this to work this would be a cool extension to have on
github.
Thanks
To extend classes in Spree, you can use Modules or class_eval. Spree extensions tend to use class_eval. Here's an example for extending User and Variant in a custom extension.
class CustomRoleExtension < Spree::Extension
# main extension method
def activate
# extend User
User.class_eval do
def business?
self.roles.include?("business")
end
def sponsor?
self.roles.include?("sponsor")
end
def developer?
self.roles.include?("developer")
end
end
# extend Variant
Variant.class_eval do
def price_for(role)
# ...
end
end
end
end
To add more roles, I just added a defaults/roles.yml to my extension, with custom yaml blocks:
coach_role:
id: 3
name: coach
trainer_role:
id: 4
name: trainer
graduate_role:
id: 5
name: graduate
Then when you run rake db:bootstrap, it will add all those roles to the database.
Let me know if that works.

Resources