I have a dashboard_controller that I am using to manage users. Here is that controller:
class DashboardController < ApplicationController
before_action :authenticate_user!
def index
if current_user.admin?
#users = current_user.get_organization_users
else
flash[:notice] = "Unauthorized Page View"
redirect_to(tasks_url)
end
end
Note I am using #users = current_user.get_organization_users. Here is the get_organization_users method in my user model...
def get_organization_users
self.organization.users
end
How would I replace this with a scope? I tried...
scope :organization_users, -> { self.organization.users }
...but no worky. Any help appreciated.
A scope is used to add a class method to your model. But you're trying to call the method on an instance. So, in this case, the instance method makes sense.
However, if you want to create a scope, pass in the user_id as a parameter to the scope.
scope :organization_users(user_id), -> { find(user_id).organization.users }
Internally, Active record converts scopes into class methods.
That would mean that you can't replace the instance method get_organization_users with a scope and expect to call it on current_user, an instance of the class.
What you could do is create a scope and pass an argument (most probably the user id) to it, then call that scope directly on the user class.
I could give an example if you wish, but I think this approach is much longer than the desired one.
I am not sure about your question. You want to fetch all users that belongs to the organization associated with the current user, right? In such case you should just call current_user.organization.users.
Scope is used for filtering records in the current model and not for getting objects that are in the relation. You can read about it in the official documentation: http://guides.rubyonrails.org/active_record_querying.html#scopes.
Related
I am created new rails application and I want to restrict user actions based on only one condition like record can be editable by owner(created_by) and sub-owner(Added by owner). I have models like App, User and controller like AppController. In AppController I have more than one actions like index, create, show, update, delete. I have one policy like AppPolicy. Here I need to create only one method to verify all actions but by default each action requires another method like action_name? in policy class.
Example
Existing code:
class AppPolicy < ApplicationPolicy
class Scope < Scope
def resolve
scope
end
end
def action1?
record.users.include? (user)
end
def action2?
record.users.include? (user)
end
def action3?
record.users.include? (user)
end
end
From above code we can see a same condition reside in all methods. I need to use only one method to verify action1, action2, action3. I don't know this is possible or not in Pundit.
I know this is an old question but I just had the same problem.
I can think about 2 solutions:
solution 1
When you know all the actions that could be called.
You can use define_method, like this
[:action1?, :action2?].each do |m|
define_method(m) { record.users.include? (user) }
end
solution 2
When you don't know all the actions. (this could be dangerous)
You can use a combination of method_missing and respond_to_missing. The latter is needed since pundit will call internally respond_to before calling the corresponding method of the policy.
Example:
def method_missing(m, *args, &block)
record.users.include? (user)
end
def respond_to_missing?(method_name, include_private = false)
true #Here it would be better to add some conditions
end
You can use cancan (or cancancan) gem rubygems link
You can create the ability configuration file with
rails g cancan:ability
The authorize! method in your controller will raise an exception if the user is not able to perform the given action, so call it on before_action callback.
Documentation here
I have a user model in my application. Now I want to replace some user model coding into 2 categories likely employ.rb and customer.rb under a module users, to avoid more number of codes in a single model. I want to access a method send_mail in customer.rb after a user created.
user.rb
after_create:send_msg_on_order
def send_msg_on_order
Users::Customer.send_mail
end
users/customer.rb
def send_mail
Mailer.send_mail_to_customer.deliver
end
And I am getting undefined method `send_mail' for Users::Customer:Module error.
You have defined send_mail method as instance method but calling it as a class method. Either make it a class method or create an instance of Customer model and call it.
Making the method a class method:
def self.send_mail
Mailer.send_mail_to_customer.deliver
end
If you wish to keep it an instance method, then call it like this:
after_create:send_msg_on_order
def send_msg_on_order
Users::Customer.new.send_mail
end
HTH
You can also call like this
def send_msg_on_order
Customer.send_mail
end
In my application controller I have the following code:
helper_method :current_user
def current_user
#current_user ||= User.find(session[:user]) if session[:user]
end
I would like to use the "current_user" method in a model. According to the rails API the helper method can be accessed at "AbstractController::Helpers::ClassMethods".
See link:
http://api.rubyonrails.org/classes/AbstractController/Helpers/ClassMethods.html
When I add this to my model I get a method not found error:
include AbstractController::Helpers::ClassMethods
Am I missing something in how to include these helper methods?
Thanks!
Short answer: you can't and you shouldn't.
The model doesn't have (and should not have) any visibility of the view and the controller. The clear separation is one of the key principle of the MVC pattern.
If you want a method in your model to have access to the current user, then pass the user when invoking the method.
For instance, assuming you want to pass the user on the Post creation, define a custom method
class Post
def do_something_with_user(user)
# ...
end
end
and call it from the controller
def action
Post.find(...).do_something_with_user(current_user)
end
There are possible workarounds, such as storing the current user into the current thread or in a global variable, but this is gonna break the rules (and you should not break the rules).
I'm have some difficulties here, I am unable to successfully call a method which belongs to a ProjectPage model in the ProjectPage controller.
I have in my ProjectPage controller:
def index
#searches = Project.published.financed
#project_pages = form_search(params)
end
And in my ProjectPage model:
def form_search(searches)
searches = searches.where('amount > ?', params[:price_min]) if check_params(params[:price_min])
#project_pages = ProjectPage.where(:project_id => searches.pluck(:'projects.id'))
end
However, I am unable to successfully call the form_search method.
To complete davidb's answer, two things you're doing wrong are:
1) you're calling a model's function from a controller, when the model function is only defined in the model itself. So you do need to call
Project.form_search
and define the function with
def self.form_search
2) you're calling params from the model. In the MVC architecture, the model doesn't know anything about the request, so params is not defined there. Instead, you'll need to pass the variable to your function like you're already doing...
Three thing:
1.) When you want to create a class wide method thats not limited to an object of the class you need to define it like
def self.method_name
..
end
and not
def method_name
...
end
2.) This can be done using a scope with lambda these are really nice features. Like This in the model add:
scope :form_search, lambda{|q| where("amount > ?", q) }
Will enable you to call
Project.form_search(params[:price_min])
The secound step would be to add a scope to the ProjectPage model so everything is at the place it belongs to!
3.) When you call a Class method in the Controller you need to specifiy the Model like this:
Class.class_method
Declare like this in model
def self.form_search(searches)
searches = searches.where('amount > ?', params[:price_min]) if check_params(params[:price_min])
#project_pages = ProjectPage.where(:project_id => searches.pluck(:'projects.id'))
end
and call from controller
#project_pages = ProjectPage.form_search(params)
I used a global variable in my app for passing information before. But I got a problem and thanks everyone here suggested me to store those data in session with database.
I tried, but I found that I can't access the session variable in Model. I googled and knew this is the Model normal behavior, RoR won't pass the session variable to Model.
So, I would like to use that session variable in validation and also the controller....
how to pass the value of the
session variable into Models? or
is there any other method for my
use case? I need a variable storing
a value, which is required in all
MVCs, and should be independent
between different concurrent users.
Thanks everyone. :)
If I understand you correctly, a session variable changes the way you validate the model. I believe the correct solution for this is the following:
class Blog < ActiveRecord::Base
attr_accessor :validate_title
validate_presence_of :title, :if => :validate_title
end
class BlogsController < ApplicationController
def new
#blog = Blog.new
#blog.validate_title = session[:validate_title]
end
end
The code has not been testet, but that's the idea. The if argument can be the name of a method and you can do whatever you want in there. You can have various validation modes if you want. For example:
class Blog < ActiveRecord::Base
attr_accessor :validation_mode
validate_presence_of :title, :if => :validate_title
def validate_title
validation_mode == "full" or validation_mode == "only_title"
end
end
class BlogsController < ApplicationController
def new
#blog = Blog.new
#blog.validate_mode = session[:validate_mode]
end
end
For more information, read the guide on validation.
Do you need the session variable as part of your model, or just as a flag to determine what to execute?
If the former, you don't need to know where did the parameters originate, just pass the variable as an argument for some call in your method. For example:
#user = User.new(params[:user].merge(:some_attribute => session[:some_key])
and just add the validation as usual in the model.
If you need that to control some flow of the execution, as you mention that should be different for different users, you may need an instance variable in your controller. Something like
class SomeController
before_filter :set_some_session_variable
def set_some_session_variable
#some_variable = session[:some_key]
end
end
You could use session[:some_key] directly in your view, but is better to set it in an instance variable instead.
Best way of doing this is to Create a method with an argument and pass session as an argument.
Fro example
class User < ActiveRecord::Base
def self.some_method(some_arg)
User.find(some_arg)
end
end
from controller
User.some_method(session[:user_id])
Models are an interface to the database. They can be used without a session (e.g. from IRB). It would be a violation of MVC to allow the models to talk to the session. Can you expand your question with a bit more info about what you're trying to do? There is most probably a better way to do it.