Multiple role-based classes inherited from User class - Rails 4 - ruby

I have a class called User and a few classes for user roles (e.g. Admin, Contributor, Member) that inherit form the User class:
class Admin < User; end
Following the STI principle, roles are stored as jsonb in the users table like so:
t.jsonb :roles, default: {
'admin': false,
'contributor': false,
'member': true
}
Is it possible to set the appropriate subclass to an instance of a User for the corresponding role. In other words, do something like:
u = User.create(...)
u.class #<= returns 'User'
u.grant_role(admin)
u.class #<= should return 'Admin'
I am thinking of using a before_save callback so that each time User instance is instantiated or updated, an appropriate class is selected. Am I on the right track?

I don't think it is possible to change the type of an instance on the fly, but I'm not 100% sure about this, considering that we're talking about Ruby here. :)
Rather than STI, which is changing the class of User, how about using the strategy pattern?
Have a set RoleStrategy classes which get instantiated depending on the value of role.
For your case, it could be something like this:
class User
# :foo and :bar are role dependent behaviors that you want to have.
delegate :foo, :bar, to: :role_strategy
private
def role_strategy
# Need to do some error handlings here, for example, if role is nil.
"#{role.classify}RoleStrategy".constantize.new(self)
end
end
class RoleStrategy
def initialize(user)
#user = user
end
def foo
do_it
end
def bar
do_something
end
end
class AdminRoleStrategy < RoleStrategy
def bar
do_something_else
end
end
The behavior would automatically change depending on what the value of role is.

Related

How to access Strong parameters from Model in Rails 4

Let's suppose I have a customer controller with strong_parameters as follows:
def update
#customer.update_attributes(customer_params)
end
private
def customer_params
params.require(:customer).permit(:name, :age)
end
Now I want to access permitted parameters (name and age) from the Customer model. For example:
class Customer < ActiveRecord::Base
def self.some_method
allowed_parameters = customer_params
end
end
How can I access those parameters? How can I get allowed_parameters = customer_params from the Customer model?
Your question is unusual. Allowed attributes don't usually belong in the model. They are a controller concern, hence why Rails removed attr_accessible in favour of strong_parameters. If you need to protect attributes, ActiveRecord has attr_read_only.
If you want a central place to keep them, just add a constant in your model.
class Customer
ALLOWED_ATTRIBUTES = [:name, :role]
def allowed_attributes
ALLOWED_ATTRIBUTES
end
end
Now you can do User.allowed_attributes from anywhere.
private
def customer_params
params.require(:customer).permit(*User.allowed_attributes)
end
But this is not desirable in my opinion because allowed attributes should set in some context, like a user role, or an access level, etc...

How do I stub a Class object in Rspec?

I am trying hard to enforce encapsulation (but am probably not doing very well), and want to test the code in Rspec. The Customer class will take a class Object (as klass) when it is instantiated in a factory class. Through an as yet non-existent UI, the Customer will create an Order.
My current test is as follows. I just want to confirm that the order is the Order class.
describe 'Customer' do
let(:customer){Customer.new}
let(:customer_with_instantiation){Customer.new(:klass=>order, :name=>'Oscar Wilde', :number=>'0234567')}
let(:order){double :order, :name=>:order}
it 'klass object to be the order class when customer is instantiated with a klass attribute' do
expect(customer_with_instantiation.klass).to be_a(order)
end
end
Class code as follows:
class Customer
attr_accessor :name, :number, :klass
DEFAULT_CUSTOMER_ORDER = {:order_detail => [{ :dish=>"",
:item_count=>0 }],
:order_total_cost=>0 }
def initialize(options={})
#name=options.fetch(:name, "")
#number=options.fetch(:number, "")
#klass=options.fetch(:klass, Object)
#customer_order=DEFAULT_CUSTOMER_ORDER
end
def place_order(menu)
#requires user input
customer_order=klass.new({:order_detail => [{:dish => :pizza, :item_count => 3},
{:dish => :burger, :item_count => 3}],
:order_total_cost => 210})
klass.test_customer_order(customer_order, self)
end
end
class Order
attr_reader :order_detail, :order_total_cost
attr_accessor :total_check
def initialize(options={})
#order_detail=options.fetch(:order_detail, Object)
#order_total_cost=options.fetch(:order_total_cost, Object)
end
def self.test_customer_order(customer_order, customer, menu, assistant)
customer_order.total_check = 0
customer_order.order_detail.each do |order_item|
menu.dishes.each do |dish|
if order_item[:dish]==dish.name
customer_order.total_check += dish.price*order_item[:item_count]
end
end
end
assistant.take_order(customer_order, customer, customer_order.total_check)
end
end
Any help gratefully appreciated!
By using be_a, you're testing that klass is an instance of klass, which is probably not what you want.
It seems to me that when testing the initialize method and the getter for klass (which is what you're doing, in effect), you should only be interested in confirming that whatever you send into Customer.new can be read afterwards.
So maybe something like this:
class Foo
attr_reader :klass
def initialize(args)
#klass = args.fetch(:klass)
end
end
describe Foo do
describe "#initialize" do
let(:klass) { double }
let(:instance) { Foo.new(klass: klass)}
it "sets klass" do
expect(instance.klass).to eq(klass)
end
end
end
Some general points:
If you want to test whether the order is an instance of klass, you should probably rewrite your code to make that easier to test
klass isn't a very useful name in this case. It isn't clear why a Customer would need a klass.
You want to decouple the order from the customer, but the customer is clearly making some assumptions about the interface of the order. Did you really achieve anything?
I'd recommend not putting test methods in the classes themselves, but rather in the test files.
Using Object as a default in fetch is probably not what you want. To begin with, you probably want them to be instances of some class, not class objects.
Is it really the job of an instance of the Customer class to create orders? If the point is to make sure that any kind of abstract order can be instantiated based on user input, maybe a separate OrderCreator class would be more appropriate? This class could accept user data and an order class and the affected customer.

How to create and use global static class/singleton in rails?

I need some class or singleton object globally-accessible in controllers and easy to use. Now it is implemented in the libs/ folder like this:
class User
class << self
#user = nil
attr_reader :uid, :name
def init session
if session[:user_info].nil?
#user = nil
end
#user = session_data[:user]
end
def signed_in?
#user.nil? ? false : true
end
def guest?
not signed_in?
end
end
end
This code obviously is not good, as User initialized only once on application start, and in case of improper use User wouldn't be updated. I want to save ability to use the class or object without much addition steps, but have a new global instance for every new connection. How it should be done?
It looks like you're trying to create a standard "current user" method. I think you're complicating things a bit. All you need to do is load a user object based on session information and cache it in an instance variable. Something like this:
class ApplicationController < ActionController::Base
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
The first time you call it, it will look up the current user based on the ID stored in the session. Subsequent calls will return the user object you already loaded. If the user is not signed in, current_user will simply be nil.
You can add include Singleton to your User class definition and then use User.instance go get the user instance.
Place the code in Application Controller as this is the base class of all the classes. Doing so it will be globally-accessible in all the controllers as well.

Variable scope definitions and inheritance

I'm working on a extended search feature for my webpage.
I looked at ransack, however it's lacking some functionalities I need, makes the url-query string very long and has some bugs (reported).
Thus I started to implement my own hack.
First I want to present my idea, afterwards I want to ask kindly how to fix my issue and in the end if there are other ways to improve this.
The idea:
A model defines something like this (additionally, the model is inside an engine):
module EngineName
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
:name is to define the query-param to use e.g. this would be ?q[name]=something
I know that this is not fully generic like ransack, but well...
:as is to build up the correct form-tag. :string would be for text_field, :integer for number_field and so on. I want to extend it further to implement auto-generating of collections for associations etc.
Now the block is a simple scope to use.
I run into several shortcomings with ransack when building up complex queries (like with count() etc.). Now I can specify my own optimized query in squeel.
I extended ActiveRecord::Base to set up the logic (the global one, not inside the engine. I want to use it everywhere).
I defined a scope :search so I can use Model.search(param[q]) like in ransack.
Also I tried to keep a list of keys which are "searchable" defined by the search_for calls.
class ActiveRecord::Base
##searchable_attributes = Hash.new({})
def self.search_for(name, *opts, &search_scope)
return unless search_scope
##searchable_attributes[name] = {
:type => opts[:as],
:condition => search_scope
}
unless ##searchable_attributes.has_key? :nil
##searchable_attributes[:nil] = Proc.new { scoped }
end
end
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = ##searchable_attributes[:nil].call
q.each do |key, search|
next unless base.class.searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
end
Now the issues:
It has mostly to do with inheritance of the classes. But even after reading and trying 3, 4 it does not worked.
Please take a look at the second line in the scope :search.
There I'm calling the simple Proc I definied above which only includes "scoped"
This is to get arround the issue that self returns "ActiveRecord::Base" and not the model itself like "Post" or "Comment".
It's because the scope is called on the Base class on inheritance, however I did not find anything to fix this.
As search_for is called on the model itself (e.g. Post) the scope-model returned there is "the right one".
Does anyone know how to circumvent this?
The next question would be, how to store the list of "searchable" scopes. I used ##variables. But as they are shared within every subclass, this would be a no-go.
However, it needs to be static as the search_for is called without initialize a instance (isn't it?)
Last but not least, it is somekind horrible to always specify the base-model to use on every scope so that I can chain them together.
Is there any other possibilities to improve this?
Ok, it seems I got it finally myself my putting several other answers from other questions together.
Model:
module EngineName
class Post < ActiveRecord::Base
searchable
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end
end
My "Plugin" currently as an initializer:
class ActiveRecord::Base
def self.searchable
include Searchable
end
end
module Searchable
def self.included(base)
base.class_eval {
##searchable_attributes = Hash.new({})
def self.search_for(name, opts)
return unless block_given?
##searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
# Named scopes
scope :search, lambda {|q|
next unless q.kind_of?(Hash)
base = self.scoped
q.each do |key, search|
key = key.to_sym
next unless ##searchable_attributes.has_key?(key)
base = ##searchable_attributes[key][:condition].call(base, search)
end
base
}
}
end
end
Hope it'll help some others working on the same problem.
Rails provides a helper for class_attribute. This provides inheritable class attributes, but allows subclassess to "change their own value and it will not impact parent class". However a hash which is mutated using []= for example would effect the parent, so you can ensure that a new copy is made when subclassing using rubys inherited method
Therefore you could declare and initialise on the base class like so:
module Searchable
extend ActiveSupport::Concern
included do
class_attribute :searchable_attributes
end
module ClassMethods
def inherited(subclass)
subclass.searchable_attributes = Hash.new({})
end
def search_for(name,opts)
return unless block_given?
searchable_attributes[name] = {
:type => opts[:as],
:condition => Proc.new
}
end
end
end
Note that I used ActiveSupport::Concern to gain the neater syntax for defining stuff directly on the class and also mixing in class methods. Then you can simply add this to active record base:
ActiveRecord::Base.send(:include, Searchable)
now any classes get their own attributes hash:
class Post < ActiveRecord::Base
search_for :name, :as => :string do |b, q|
b.where{name =~ "%#{q}%"}
end
end

List only ActiveRecord subclass methods

Given the following ActiveRecord model:
class User < ActiveRecord::Base
has_many :games
def name
"Joe"
end
def city
"Chicago"
end
end
I'd like to retrieve a list of the methods I added directly to the User class (and not those added by extending ActiveRecord and/or adding associations). Example output:
["name","city"]
Calling User.instance_methods(false) returns method added by ActiveRecord:
["validate_associated_records_for_games", "games", "game_ids", "games=", "game_ids=", "after_create_or_update_associated_records_for_games", "before_save_associated_records_for_games"]
Along with any model attributes from database columns. I'd like to exclude those and just get the custom methods on the subclass.
My purpose is method tracing: I'd like to trace my custom methods while excluding those added by ActiveRecord.
Any ideas?
User.instance_methods - ActiveRecord::Base.instance_methods #=> [:name,:city]
UPDATE:
the order of these methods are significant
class User < ActiveRecord::Base
def self.my_own_methods
self.instance_methods - ##im
end
has_many :games
##im = self.instance_methods
def name
"Joe"
end
def city
"Chicago"
end
end
User.my_own_methods #=> [:name, :city]
This one tested and it works
Use a method_added hook which adds method names to a list, and monkey patch Active Record so that methods added by AR are not added to that list.
If you don't want to crack AR open and start poking around, you could also define a "class macro" which defines a method and adds it to the list. For your own custom methods, use the class macro rather than def.
If you're not familiar with what I'm referring to as a "class macro", it's simply a method like this:
class Class
def mydef(name,&block)
(#methods || []) << name
define_method(name,&block)
end
end
Using something like mydef to define methods rather than def is definitely ugly, but it would solve the problem without requiring any monkey-patching.

Resources