attributes and constructors in rails - ruby-on-rails

I'm new to rails and don't even know if this is the correct way of solving my situation.
I have a "Club" ActiveRecords model which has a "has_many" association to a "Member" model. I want the logged in "Club" to only be able to administrate it's own "Member" so in the beginning of each action in the "Member" model I did something similar to the following:
def index
#members = Club.find(session[:club_id]).members
to access the right members. This did not however turn out very DRY as I did the same in every action. So I thought of using something equivalent to what would be called a constructor in other languages. The initialize method as I've understood it. This was however not working, this told me why, and proposed an alternative. The after_initialize.
def after_initialize
#club = Club.find(session[:club_id])
end
def index
#members = #club.members
....
does not seem to work anyway. Any pointers to why?
You have a nil object when you didn't expect it!
The error occurred while evaluating nil.members
Makes me think that the #club var isn't set at all.
Also, is this solution really a good one? This makes it hard to implement any kind of "super admin" who can manage the members in all of the clubs. Any ideas on where I am missing something?

You can use a before_filter.
Define the filter in your ApplicationController (so that you can access it from any controller).
class ApplicationController < ActionController::Base
# ..
protected
def load_members
#members = if session[:club_id]
Club.find(session[:club_id]).members
else
[]
end
end
end
Then, load the filter before any action where you need it.
For example
class ClubController < ApplicationController
before_filter :load_members, :only => %w( index )
def index
# here #members is set
end
end
Otherwise, use lazy loading. You can use the same load_members and call it whenever you need it.
class ClubController < ApplicationController
def index
# do something with members
load_members.each { ... }
end
end
Of course, you can customize load_member to raise an exception, redirect the client if #members.empty? or do whatever you want.

You want to use a before_filter for this.
class MembersController < ApplicationController
before_filter :find_club
def index
#members = #club.members
end
private
def find_club
#club = Club.find(session[:club_id])
end
end

I'm a fan of a plugin called Rolerequirement. It allows you to make custom roles and apply them by controller: http://code.google.com/p/rolerequirement/

Related

How can I create an instance variable to all instances of the class in ruby, using metaprogramming?

I'm new to ruby/rails world, and I'm facing and issue that appears to be something trivial, but it isn't being that easy for me.
What I want to do, is to write a simple helper method, to help keep the controllers clean. This method will receive a symbol/string, and create an instance variable with the camelized received string, which will evaluate to a constant/class.
My attempts resume themselves to this:
class ApplicationController < ActionController::Base
...
protected
def self.service(name)
instance_eval do
instance_variable_set("##{name.to_s.pluralize}", name.to_s.camelize.constantize)
end
end
And, in my controller:
class UserController < ApplicationController
service :user
But when I do this, it create the instance variables in UserController, not UserController.new. So, I know what is wrong, but I couldn't find how can I do this right.
Can anyone help me with that?
The trouble is that this method is in the class scope:
def self.service(name)
instance_eval do
instance_variable_set("##{name.to_s.pluralize}", name.to_s.camelize.constantize)
end
end
This means it'll be setting class level instance variables - or class variables (kinda).
If you want to follow this approach, you'll need to store these all in an array or similar at the class level, which can be read out and turned into instance variables when the class is instantiated.
However, I don't like to mess with the initialize method on controllers. It's not the way to do things, so could change the class behaviour, break things or become fragile when changing versions.
In your ApplicationController, something like:
class << self
def service(name)
#services ||= []
return #services += name.map(&:to_sym) if name.is_a?(Array)
#services << name.to_sym
end
def service_names
#services
end
end
before_action :services
def services
self.class.service_names.each do |name|
instance_eval do
instance_variable_set("##{name.to_s.pluralize}", name.to_s.camelize.constantize)
end
end
end
This is untested.

How do I initialise objects in a partial view for use by multiple controllers?

I hope this is something obvious that I've just consistently overlooked and the community can set me on the right path.
I have a news article controller, but I want to be able to use a "common" ticker list on different views. How do I initialise this "#article_list" if I'm using the partial in a few controllers? Apparently it is of the opinion that using a helper is not the solution, since helpers are just for view logic. So where do I put this initialiser that would be available to every controller as required? I shouldn't put them in application controller should I?
You can use before_filter method, i.e. something like this:
class ApplicationController < ActionController::Base
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
UPDATE:
Indeed, there will be problem with a fat ApplicationController. To avoid it it's possible to use module (almost #carolclarinet describe it below):
module ArticleList
def set_article_list
#article_list = ArticleList.all # or any onther selection
end
end
class NewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :action1
def action1
end
end
class AnotherNewsArticleController < ApplicationController
include ArticleList
before_filter :set_article_list, only: :another_action1
def another_action1
end
end
And
You can create, essentially, a query object that is only responsible for returning what you need for #article_list, for example, building off of Psylone's answer:
class ArticleList
def ticker_articles
ArticleList.all # or any onther selection
end
end
This class could go in lib, app/models, app/query_objects, app/models/query_objects, wherever it makes sense for you. This is a bit outside The Rails Way so there's no convention about where these types of objects should live.
Then in whatever controller you need this, do:
#article_list = ArticleList.new.ticker_articles
For more explanation of query objects, see this codeclimate article #4. Depending on what you're doing to set #article_list, this might also be called a service object (#2) or something else entirely. No matter what you call it though, its responsibility would be to return the value you need for #article_list and that's it.

Get current_user in Rails form validation by defining a virtual attribute

Rails form validation is designed to go in the model most easily. But I need to make sure the current user has the required privileges to submit a post and the current_user variable is only accessible in the controller and view.
I found this answer in a similar question:
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.`
How can I set this up with my post and user controller so that the current_user variable is accessible in the model?
Solution:
This whole thing is wrong from an application design perspective as #Deefour's answer pointed out. I changed it so my view doesn't render the form unless the condition is true.
The "similar question" is saying you can do something like this
class YourModel < ActiveRecord::Base
attr_accessor :current_user
# ...
end
and then in your controller action you can do something like
#your_model = YourModel.find(params[:id])
#your_model.current_user = current_user
#your_model.assign_attributes(params[:your_model])
if #your_model.valid?
# ...
You can then use self.current_user within YourModel's validation methods.
Note I don't think this is what you should be doing though, as I don't consider this "validation" as much as "authorization". An unauthorized user shouldn't even be able to get the part of your action where such an update to a YourModel instance could be saved.
As for doing the authorization with Pundit as requested, you'd have a file in app/policies/your_model.rb
class YourModelPolicy < Struct.new(:user, :your_model)
def update?
user.some_privilege == true # change this to suit your needs, checking the "required privileges" you mention
end
end
Include Pundit in your ApplicationController
class ApplicationController < ActionController::Base
include Pundit
# ...
end
Then, in your controller action you can do simply
def update
#your_model = YourModel.find(params[:id])
authorize #your_model
# ...
The authorize method will call YourModelPolicy's update? method (it calls the method matching your action + ? by default) and if a falsy value is returned a 403 error will result.
Authorization shouldn't be done in models. Models have already many responsibilities don't you think?
That's a controller thing, and actually you can have the logic in other place using some gem like cancan and in your controller you would do something like:
authorize! :create, Post
You can define a "virtual attribute" in your model like this:
class Book < ActiveRecord::Base
attr_accessor :current_user
end
Its value can be set directly in your controller like this:
class BooksController < ApplicationController
def create
book = Book.new
book.current_user = current_user
book.save!
end
end
And inside your model's validation routine, you can access it like any other ActiveRecord field:
def validate_user_permission
errors[:current_user] = "user does not have permission" unless current_user.is_gold?
end
I can't remember if this is the case with ActiveRecord, but you might be able to set virtual attributes via the mass-assignment methods like create, update, and new in the controller:
def create
Book.create!(current_user: current_user)
end
In order to do that, you would probably have to add the following line to your model to enable mass-assignment of that virtual attribute:
attr_accessible :current_user
I agree with Ismael - this is normally done in the controller. It's not an attribute of the model, it's a permission issue and related to the controller business logic.
If you don't need all the power of a gem like CanCan, you can role your own.
class BooksController < ApplicationController
before_filter :gold_required, :only => :create
def create
book = Book.new
book.save!
end
# Can be application controller
private
def gold_required
return current_user && current_user.is_gold?
end
end
You may want to put the filter on the 'new' method as well.

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.

Rails: How to get the model class name based on the controller class name?

class HouseBuyersController < ...
def my_method
# How could I get here the relevant model name, i.e. "HouseBuyer" ?
end
end
This will do it:
class HouseBuyersController < ApplicationController
def index
#model_name = controller_name.classify
end
end
This is often needed when abstracting controller actions:
class HouseBuyersController < ApplicationController
def index
# Equivalent of #house_buyers = HouseBuyer.find(:all)
objects = controller_name.classify.constantize.find(:all)
instance_variable_set("##{controller_name}", objects)
end
end
If your controller and model are in the same namespace, then what you want is
controller_path.classify
controller_path gives you the namespace; controller_name doesn't.
For example, if your controller is
Admin::RolesController
then:
controller_path.classify # "Admin::Role" # CORRECT
controller_name.classify # "Role" # INCORRECT
It's a bit of a hack, but if your model is named after your controller name then:
class HouseBuyersController < ApplicationController
def my_method
#model_name = self.class.name.sub("Controller", "").singularize
end
end
... would give you "HouseBuyer" in your #model_name instance variable.
Again, this makes a huge assumption that "HouseBuyersController" only deals with "HouseBuyer" models.
For namespaces working:
def resource_class
controller_path.classify.constantize
end
The accepted solution did not work for me as my controller and model was namespaced. Instead, I came up with the following method:
def controllers_model
(self.class.name.split('::')[0..-2] << controller_name.classify).join('::')
end
This is not possible if you are using the default MVC, which your code doesn't seem to follow. Your controller seems to be a model but maybe you just got a type there. Anyway, controllers and models are fundamentally separated in Rails MVC so controllers cannot know which model they are associated with.
For example you could have a model named post. This can have a controller posts_controller or could have a controller like articles_controller. Rails only knows about models when you def the actual code in the controller such as
def index
#posts = Post.all
#posts = Article.all
end
In rails standard controllers there is no way to know what the model is.

Resources