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...
Related
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.
Is it possible in Ruby to instantiate another object of a different class using a class method?
I have spent a lot of time to no avail researching Google, the Ruby docs, and Stack Overflow, for an answer to my query, so I am posting this question as a last resort.
Two classes, User, and Blog. In User below I am trying to create a new instance of an object for Blog, and carry over some attributes.
In class User there are 2 methods;
class User
attr_accessor :blogs, :username
def initialize(username)
self.username = username
self.blogs = []
end
def add_blog(date, text)
self.blogs << [Date.parse(date),text]
new_blog = [Date.parse(date),text]
Blog.new(#username,date,text)
end
end
Using the above add_blog I would like to initialize & send a new object to the Blog class.
class Blog
attr_accessor :text, :date, :user
def initialize(user,date,text)
user = user
date = date
text = text
end
end
Yes, it is possible to instantiate another object of a different class using a class method. We ruby programmers are doing it all the time.
Do you want to have the blogs attribute of the user to have an array of blogs? Because your code just puts a date-text tupel into the array.
I think you wanted to do something like this in your user class:
def add_blog(date, text)
parsed_date = Date.parse(date)
new_blog = Blog.new(#username, parsed_date, text)
self.blogs << new_blog
new_blog
end
I have showed it step after step, but you can combine several lines. The last line returns the new blog, you may not need it if you only want the blog be part of the blogs array.
Looks like your code has some flaws. Here is the corrected code:
You were not using # to assign values to instance variables, you were putting a date in #blogs, instead of Blog object.
If you want to pass instance of User to Blog from add_blog, you can use self which represents current instance of User class. If you want to carry just some attributes, then, you can do so by referring to attributes using #attribute_name or self.attribute_name syntax
require "date"
class User
attr_accessor :blogs, :username
def initialize(username)
#username = username
#blogs = []
end
def add_blog(date, text)
#blogs << Blog.new(#username, Date.parse(date),text)
self
end
def to_s
"#{#username} - #{#blogs.collect { |b| b.to_s }}"
end
end
class Blog
attr_accessor :text, :date, :user
def initialize(user,date,text)
#user = user
#date = date
#text = text
end
def to_s
"Blog of #{user}: #{#date} - #{#text}"
end
end
puts User.new("Wand Maker").add_blog("2015-08-15", "Hello")
# => Wand Maker - ["Blog of Wand Maker: 2015-08-15 - Hello"]
I have one model (Group) associated with my GroupController, but it has many instances of another model (People) in it. I want to be able to create new instances of People from my GroupController and add it to an array. What is the best way to go about doing this? The following is an excerpt from the GroupController:
class Group < Volt::ModelController
field :people_in_group
def add_people(name)
people_in_group = []
person = People.new(name)
people_in_group << person
end
end
The function breaks when I try to create a new person. Any advice?
Is this supposed to be a model? If so it should inherit from Volt::Model not Volt::ModelController
Thanks!
Try something like this:
class Person < Volt::Model
field :name, String
def initialize(a_name)
#name = a_name
end
end
class Group < Volt::Model
has_many :people
def add_people(name)
people << Person.new(name)
end
end
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.
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.