I have declared user_decorator.rb instead of user_helper.rb in the following way
class UserDecorator < Draper::Decorator
delegate_all
def contract_type
contract_types.keys.collect {|k| [k.humanize, k]}
end
def employee_type
employee_types.keys.collect {|k| [k.humanize, k]}
end
def department_role
department_roles.keys.collect {|k| [k.humanize, k]}
end
end
and here are my enums that are declared on the user.rb
enum contract_type: [:probationary, :apprenticeship, :six_months_to_one_year,
:three_years]
enum department_role: [:ceo, :cto, :coo, :manager, :tl, :gl, :developer]
enum employee_type: [:full_time, :part_time, :internship]
I want to call the helper method from the view that is related to registrations controller. It is like as
class RegistrationsController < Devise::RegistrationsController
def new
super
end
def create
super
end
end
But if I call the helper method, like as following from the views/devise/registrations/new.html.erb
<%= f.select :contract_type, contract_type, {prompt: t(".contract_type",
default: "Select contract type")} %>
It don't find the contract_type. Need help about how I can access the helper methods from the view that is declared on the user_decorator.rb
In order to get a decorated user object from Devise's current_user method, you can override it:
class ApplicationController < ActionController::Base
def current_user
UserDecorator.decorate(super) unless super.nil?
end
end
However, since your issue is on the devise/registrations/new.html.erb page, at this point a user will not have logged in and hence won't have a any kind of decorated current_user object.
So, what it would seem you want is to get the set of decorated contract types from your User model into that view, which you could do by creating an instance variable on the controller:
class RegistrationsController < Devise::RegistrationsController
def new
#contract_types = User.contract_types.keys.collect {|k| [k.humanize, k]}
super
end
end
and then use it in the view:
<%= f.select :contract_type,
#contract_types,
{ prompt: t(".contract_type", default: "Select contract type") } %>
If you wanted to do further refactorings, you could perhaps create a scope-like class method on your User model that does the decoration of the contract types:
class User < ActiveRecord::Base
def self.humanized_contract_types
contract_types.keys.collect {|k| [k.humanize, k]}
end
end
So, then your controller code could be shortened to:
class RegistrationsController < Devise::RegistrationsController
def new
#contract_types = User.humanized_contract_types
super
end
end
Or, if you're intent on using the UserDecorator class, you could look into proxying class method calls to it.
You can override your new method to decorate your resource like so :
# registrations_controller.rb
def new
build_resource({})
yield resource if block_given?
respond_with resource
end
protected
def build_resource(hash = nil)
self.resource = resource_class.new_with_session(hash || {}, session).decorate
end
Related
I have the following class
class EvaluateService
def initialize
end
def get_url
end
def self.evaluate_service
#instance ||= new
end
end
class CheckController < ApplicationController
def index
get_url = EvaluateService.get_url
end
end
The problem here is that i know that i can do evaluate_service = EvaluateService.new and use the object evaluate_service.get_url and it will work fine but i also know that some frown upon the idea of initializing the service object this way and rather there is a way of initializing it via a call, send method in the service class.
Just wondering how do i do this?
I think what you're looking for is something like:
class Evaluate
def initialize(foo)
#foo = foo
end
def self.call(foo)
new(foo).call
end
def call
url
end
private
def url
# Implement me
end
end
Now you can do this in your controller:
class CheckController < ApplicationController
def index
#url = Evaluate.call(params)
end
end
The reason some prefer #call as the entry point is that it's polymorphic with lambdas. That is, anywhere you could use a lambda, you can substitute it for an instance of Evaluate, and vice versa.
There are various ways to approach this.
If the methods in EvaluateService don't need state, you could just use class methods, e.g.:
class EvaluateService
def self.get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = EvaluateService.get_url
end
end
In this scenario, EvaluateService should probably be a module.
If you want a single global EvaluateService instance, there's Singleton:
class EvaluateService
include Singleton
def get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = EvaluateService.instance.get_url
end
end
But global objects can be tricky.
Or you could use a helper method in your controller that creates a service instance (as needed) and memoizes it:
class EvaluateService
def get_url
# ...
end
end
class CheckController < ApplicationController
def index
#url = evaluate_service.get_url
end
private
def evaluate_service
#evaluate_service ||= EvaluateService.new
end
end
Maybe even move it up to your ApplicationController.
I'm using Pundit for authorization and I want to make use of its scoping mechanisms for multi-tenancy (driven by hostname).
I've been doing this manually to date by virtue of:
class ApplicationController < ActionController::Base
# Returns a single Client record
def current_client
#current_client ||= Client.by_host(request.host)
end
end
And then in my controllers doing things like:
class PostsController < ApplicationController
def index
#posts = current_client.posts
end
end
Pretty standard fare, really.
I like the simplicity of Pundit's verify_policy_scoped filter for ensuring absolutely every action has been scoped to the correct Client. To me, it really is worthy of a 500 error if scoping has not been officially performed.
Given a Pundit policy scope:
class PostPolicy < ApplicationPolicy
class Scope < Scope
def resolve
# have access to #scope => Post class
# have access to #user => User object or nil
end
end
end
Now, Pundit seems to want me to filter Posts by user, e.g.:
def resolve
scope.where(user_id: user.id)
end
However, in this scenario I actually want to filter by current_client.posts as the default case. I'm not sure how to use Pundit scopes in this situation but my feeling is it needs to look something like:
def resolve
current_client.posts
end
But current_client is naturally not going to be available in the Pundit scope.
One solution could be to pass current_client.posts to policy_scope:
def index
#posts = policy_scope(current_client.posts)
end
But I feel this decentralizes my tenancy scoping destroys the purpose of using Pundit for this task.
Any ideas? Or am I driving Pundit beyond what it was designed for?
The most "Pundit-complient" way to deal with this problem would be to create a scope in your Post model:
Class Post < ActiveRecord::Base
scope :from_user, -> (user) do
user.posts
end
end
Then, you will be able to use it in your policy, where user is filled with the current_user from your controller:
class PostPolicy < ApplicationPolicy
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
scope.from_user(user)
end
end
end
If you are returning an ActiveRecord::Relation from the scope, you can stop reading from here.
If your scope returns an array
The default ApplicationPolicy implement the method show using a where:
source.
So if your scope does not return an AR::Relation but an array, one work-around could be to override this show method:
class PostPolicy < ApplicationPolicy
class Scope
# same content than above
end
def show?
post = scope.find do |post_in_scope|
post_in_scope.id == post.id
end
post.present?
end
end
Whatever your implementation is, you just need to use the PostPolicy from your controller the "Pundit-way":
class PostsController < ApplicationController
def index
#posts = policy_scope(Post)
end
def show
#post = Post.find(params[:id])
authorize #post
end
end
I'm using Devise and Rails 3.2.16. I want to automatically insert who created a record and who updated a record. So I have something like this in models:
before_create :insert_created_by
before_update :insert_updated_by
private
def insert_created_by
self.created_by_id = current_user.id
end
def insert_updated_by
self.updated_by_id = current_user.id
end
Problem is that I get the error undefined local variable or method 'current_user' because current_user is not visible in a callback. How can I automatically insert who created and updated this record?
If there's an easy way to do it in Rails 4.x I'll make the migration.
Editing #HarsHarl's answer would probably have made more sense since this answer is very much similar.
With the Thread.current[:current_user] approach, you would have to make this call to set the User for every request. You've said that you don't like the idea of setting a variable for every single request that is only used so seldom; you could chose to use skip_before_filter to skip setting the User or instead of placing the before_filter in the ApplicationController set it in the controllers where you need the current_user.
A modular approach would be to move the setting of created_by_id and updated_by_id to a concern and include it in models you need to use.
Auditable module:
# app/models/concerns/auditable.rb
module Auditable
extend ActiveSupport::Concern
included do
# Assigns created_by_id and updated_by_id upon included Class initialization
after_initialize :add_created_by_and_updated_by
# Updates updated_by_id for the current instance
after_save :update_updated_by
end
private
def add_created_by_and_updated_by
self.created_by_id ||= User.current.id if User.current
self.updated_by_id ||= User.current.id if User.current
end
# Updates current instance's updated_by_id if current_user is not nil and is not destroyed.
def update_updated_by
self.updated_by_id = User.current.id if User.current and not destroyed?
end
end
User Model:
#app/models/user.rb
class User < ActiveRecord::Base
...
def self.current=(user)
Thread.current[:current_user] = user
end
def self.current
Thread.current[:current_user]
end
...
end
Application Controller:
#app/controllers/application_controller
class ApplicationController < ActionController::Base
...
before_filter :authenticate_user!, :set_current_user
private
def set_current_user
User.current = current_user
end
end
Example Usage: Include auditable module in one of the models:
# app/models/foo.rb
class Foo < ActiveRecord::Base
include Auditable
...
end
Including Auditable concern in Foo model will assign created_by_id and updated_by_id to Foo's instance upon initialization so you have these attributes to use right after initialization, and they are persisted into the foos table on an after_save callback.
another approach is this
class User
class << self
def current_user=(user)
Thread.current[:current_user] = user
end
def current_user
Thread.current[:current_user]
end
end
end
class ApplicationController
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
end
current_user is not accessible from within model files in Rails, only controllers, views and helpers. Although , through class variable you can achieve that but this is not good approach so for that you can create two methods inside his model. When create action call from controller then send current user and field name to that model ex:
Contoller code
def create
your code goes here and after save then write
#model_instance.insert_created_by(current_user)
end
and in model write this method
def self.insert_created_by(user)
update_attributes(created_by_id: user.id)
end
same for other methods
just create an attribute accessor in the model and initialize it when your record is being saved in controller as below
# app/models/foo.rb
class Foo < ActiveRecord::Base
attr_accessor :current_user
before_create :insert_created_by
before_update :insert_updated_by
private
def insert_created_by
self.created_by_id = current_user.id
end
def insert_updated_by
self.updated_by_id = current_user.id
end
end
# app/controllers/foos_controller.rb
class FoosController < ApplicationController
def create
#foo = Foo.new(....)
#foo.current_user = current_user
#foo.save
end
end
I'm allowing my users to have multiple profiles (user has many profiles) and one of them is the default. In my users table I have a default_profile_id.
How do I create a "default_profile" like Devise's current_user which I can use everywhere?
Where should I put this line?
default_profile = Profile.find(current_user.default_profile_id)
Devise's current_user method looks like this:
def current_#{mapping}
#current_#{mapping} ||= warden.authenticate(:scope => :#{mapping})
end
As you can see, the #current_#{mapping} is being memoized. In your case you'd want to use something like this:
def default_profile
#default_profile ||= Profile.find(current_user.default_profile_id)
end
Regarding using it everywhere, I'm going to assume you want to use it both in your controllers and in your views. If that's the case you would declare it in your ApplicationController like so:
class ApplicationController < ActionController::Base
helper_method :default_profile
def default_profile
#default_profile ||= Profile.find(current_user.default_profile_id)
end
end
The helper_method will allow you to access this memoized default_profile in your views. Having this method in the ApplicationController allows you to call it from your other controllers.
You can put this code inside application controller by defining inside a method:
class ApplicationController < ActionController::Base
...
helper_method :default_profile
def default_profile
Profile.find(current_user.default_profile_id)
rescue
nil
end
...
end
And, can access it like current_user in your application. If you call default_profile, it will give you the profile record if available, otherwise nil.
I would add a method profile to user or define a has_one (preferred). Than it is just current_user.profile if you want the default profile:
has_many :profiles
has_one :profile # aka the default profile
I would not implement the shortcut method, but you want:
class ApplicationController < ActionController::Base
def default_profile
current_user.profile
end
helper_method :default_profile
end
At the moment, I am creating some kind of admin panel/backend for my site.
I want to do the following:
Only admins (a user has a user_role(integer) --> 1 = admin, 2 = moderator, 3 = user) can see and access a link for the admin panel.
So I created an admin_controller. In my admin controller I created a new function called is_admin?:
class AdminController < ApplicationController
def admin_panel
end
def is_admin?
current_user.user_role == 1
end
end
my route looks like.
map.admin_panel '/admin-panel', :controller => 'admin', :action => 'admin_panel'
and in my _sidebar.html.erb (partial in applicaton.html.erb) I created the link:
<%= link_to "Admin Panel", :admin_panel unless is_admin? %>
Now I get an error called:
undefined method `is_admin?'
Where is the problem? Please help me solving this problem!
Okay, sorry for this, but it still wont work. Here are my controllers:
application_controller.rb:
class ApplicationController < ActionController::Base
include AuthenticatedSystem
helper :all
protect_from_forgery
helper_method :current_user
def current_user
#current_user ||= User.find_by_id(session[:user])
end
end
users_controller.rb:
class UsersController < ApplicationController
layout 'application'
include AuthenticatedSystem
helper_method :is_admin? #only added this line
def new
end
...
end
user.rb
require 'digest/sha1'
class User < ActiveRecord::Base
# Virtual attribute for the unencrypted password
attr_accessor :password
... #more stuff but nothing for is_admin?
def active?
# the existence of an activation code means they have not activated yet
activation_code.nil?
end
#here is my is_admin? code
def is_admin?
self.user_role == 1
end
...
end
and now my view (_sidebar.html.erb):
<div>
<%= link_to "Admin Panel", :admin_panel unless current_user.is_admin? %>
</div>
That's it. Any ideas?
Btw: now the error changed a bit. Now it is:
undefined method `is_admin?' for nil:NilClass
My Session Create (in sessions_controller.rb):
def create
self.current_user = User.authenticate(params[:login], params[:password])
if logged_in?
if params[:remember_me] == "1"
current_user.remember_me unless current_user.remember_token?
cookies[:auth_token] = { :value => self.current_user.remember_token , :expires => self.current_user.remember_token_expires_at }
end
redirect_back_or_default('/')
flash[:notice] = "Logged in successfully"
else
render :action => 'new'
end
end
The problem is that methods defined in your controllers are not available in your views, unless you do this in the controller:
helper_method :is_admin?
However, in your case I would suggest that you move this method into the user model, as it seems to be more or less part of the business logic of the application.
So, in your user model,
class User < ActiveRecord::Base
def is_admin?
self.user_role == 1
end
end
And then in the view,
<%= link_to "Admin Panel", :admin_panel unless current_user.is_admin? %>
Oh, and btw, make sure that your users cannot change their roles arbitrarily via mass attributes assignment. And also it would be better to define constants for those role integer values. Sorry if this is too obvious :)
use helper_method if you want to use your controller's method in your views
class AdminController < ApplicationController
helper_method :is_admin? #you should include this line so that you can access it in your view.
def admin_panel
end
def is_admin?
current_user.user_role == 1
end
end