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.
Related
Using SimpleDelegator, I created a few decorators to add extra functionalities to my objects. I need to decorate an object twice, as below:
Tracked.new(Audited.new(User.new))).save
Here is the basic structure of the decorator(s):
class Tracked #or Audited
delegate :id, to: :__getobj__
def initialize(extened_object)
super(extened_object)
#extened_object = extened_object
end
def itself
__getobj__
end
def save
super
# the extended behavior
end
end
I want to access the class User from the object. In Audited, if I call #extended_object.class with Audited.new(User.new)).save, I get User. In Tracked, if I call #extended_object.class with Tracked(Audited.new(User.new))).save, I get Audited instead.
How can I get the Class of the extended_object regardless of the number of times I decorate it?
I don't think you can do this with SimpleDelegator.
You need to implement this method yourself.
For example:
class MyDelegator < SimpleDelegator
def original_object
obj = __getobj__
obj.is_a?(MyDecorator) ? obj.original_object : obj
end
end
And all of your decorators should be inherited from MyDelegator
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.
I want to create a select list for lets say colors, but dont want to create a table for the colors. I have seen it anywhere, but can't find it on google.
My question is: How can I put the colors in a model without a database table?
Or is there a better rails way for doing that?
I have seen someone putting an array or a hash directly in the model, but now I couldn't find it.
class Model
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :whatever
validates :whatever, :presence => true
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
attr_accessor will create your attributes and you will create the object with initialize() and set attributes.
The method persisted will tell there is no link with the database. You can find examples like this one:
http://railscasts.com/episodes/219-active-model?language=en&view=asciicast
Which will explain you the logic.
The answers are fine for 2013 but since Rails 4 all the database independent features of ActiveRecord are extracted into ActiveModel. Also, there's an awesome official guide for it.
You can include as many of the modules as you want, or as little.
As an example, you just need to include ActiveModel::Model and you can forgo such an initialize method:
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
Just use:
attr_accessor :name, :age
The easiest answer is simply to not subclass from ActiveRecord::Base. Then you can just write your object code.
What worked for me in Rails 6:
class MyClass
include ActiveModel::Model
attr_accessor :my_property
end
If the reason you need a model without an associated table is to create an abstract class real models inherit from - ActiveRecord supports that:
class ModelBase < ActiveRecord::Base
self.abstract_class = true
end
If you want to have a select list (which does not evolve) you can define a method in your ApplicationHelper that returns a list, for example:
def my_color_list
[
"red",
"green",
"blue"
]
end
I have a class, with some fake relationships I want to implement:
module FormStack
class Connection
def forms; end
def fields; end
end
end
I have metaprogramically generated classes for both forms, and fields (as they are RESTful resources, they share the same action names and params), and I want to include those methods in my fake relationships in my FormStack::Connection class. can this be done?
I essentially want <FromStack::Connection Instance>.forms to behave as if it were FormStack::Form, so I can do things like <connection>.forms.all or <connection>.forms.find(id).
Is this possible?
Any best practices I should maybe be looking at? (This seems a little strange to me, but I think it's an elegant way to have the methods implemented in a useful way, while still having an ActiveRecord-esque abstraction of the restful resources / objects).
Here is the code I'm working with, if you want to look: https://github.com/TinderBox/formstack/tree/connection_instances
Why not just use simple composition? Pass whatever object has the has_many FormStack::Form relation in when you initialize a new FormStack::Connection instance. Then you can directly invoke the #forms method on the FormStack::Form collection instance, or you can use delegation.
FormStack::Connection.new(FormStack::FormCollection.new(params[:form]) #sample class name -- obviously use whatever has the real has_many :forms
module FormStack
class Connection
def initialize(form_collection)
#form_collection = form_collection
end
def forms
#form_collection.forms
end
def fields
#form_collection.fields
end
end
end
Or
module FormStack
class Connection
extend Forwardable
def_delegators :#form_collection, :forms, :fields
def initialize(form_collection)
#form_collection = form_collection
end
end
end
Unless there is a better way, this is how I've solved my problem for now:
def method_missing(meth, *args, &block)
method_name = meth.to_s
if "forms" == method_name
FormStack::Form.connection = self
FormStack::Form
elsif ...
else
super
end
end
https://github.com/TinderBox/formstack/blob/082793bed97e97cc65c703c8ca3cb382cbdf743a/lib/formstack/connection.rb
How to add before_validation callback to single object, not whole class in ActiveRecord 3?
In AR 2 I did like this:
module ObjExtend
def before_validation
p 'before_validation'
return super
end
end
obj.extend ObjExtend
but it does not work anymore
That's because in rails3 you can no longer just define methods in your models named for the callback in question. You have to declare them with before_validation :foo or what have you (:foo refers to an instance method of the model class but you can also pass in a proc or a class).
So.. This isn't the ideal solution but it should give you some ideas:
obj = MyModel.first
class << obj
before_validation :foo
def foo
p 'before_validation'
return super
end
end
This code opens the object's 'eigenclass' (there's various names for this thing) and behaves as though it were the original class context.