I have a 'Cost' model in rails. Something like the following:
class Cost < ActiveRecord::Base
belongs_to :cost_type
has_many :cost_distributions
attr_accessor :epp
def initialize()
end
However, in my tests, when I try to create new instance with the empty constructor
cost = Cost.new
I get an error: wrong number of arguments (0 for 1). Why is it ignoring my empty constructor?
You need to allow ActiveRecord to do its own initialization since you are essentially overriding the behavior. Just change your initialize to this:
def initialize()
super
end
However, if you don't supply a constructor at all, Rails lets you create the model without parameters:
Cost.new
So is your empty initialize method doing anything else? If not, its not even needed.
def initialize(*args)
super
end
Is the secret sauce.
In general, overriding ActiveRecord's initialize method isn't a very good idea.
If your initialize() does "nothing", you don't need it. Just remove it.
class Cost < ActiveRecord::Base
belongs_to :cost_type
has_many :cost_distributions
attr_accessor :epp
end
You will still be able to invoke Cost.new (the right initialize method will be provided by ActiveRecord itself, if you don't override it).
Related
I have an application I'm building where I need one model to create instances of another model. I want every Car to have 4 tires.
Car model
class Car < ActiveRecord::Base
has_many :tires
after_create :make_tires
def make_tires
4.times { Tire.create(car: self.id) }
end
end
Tire model
class Tire < ActiveRecord::Base
belongs_to :car
end
However, inside of make_tires there is an error that there is no activerecord method for create or new if I try it for Tire. When I inspect Tire it doesn't have those methods.
How can I remedy this?
The error is this: undefined method 'create' for ActiveRecord::AttributeMethods::Serialization::Tire::Module
I have tested two environments: Testing and Development and they both fail for the same error.
It is a name conflict. Sit down and relax while I explain.
Solution with explanation:
In Ruby classes are just instances of class Class (which is a subclass of class Module). Instances of Module (including instances of Class) are quite weird objects, especially weird is their connection with ruby constants. You can create a new class at any point using standard ruby notation:
my_class = Class.new { attr_accessor :a }
instance = my_class.new
instance.a = 3
insatnce.a #=>
instance.class.name #=> nil
Well, our class has no name. It is just an anonymous class. How do classes get their name? By assigning it to a constant (for the first time):
MyClass = my_class
my_class.name #=> 'MyClass'
When you define class using a class keyword:
class MyClass
...
end
You just create a new instance of Class and assign it to a constant. Because of that, Ruby compiler seeing a constant has no idea whether it is a class or a number under it - it has to make a full search for that constant.
The logic behind finding a constant is quite complex and depends on the current nesting. Your case is quite simple (as there is no nesting), so ruby will try to find Tire class inside your class first and when failed it's subclasses and included modules.
Your problem is that your class inherits from ActiveRecord::Base (which is correct), which includes ActiveRecord::AttributeMethods::Serialization module, which defines Tire constant already. Hence, ruby will use this constant instead, as this is the best match for that name in given context.
To fix it, you must tell the compiler not to look within the current class but directly in the "top namespace" (which in ruby is Object. Seriously, try Object.constants) - you can do that using :: in front of your constant, like ::Tire.
Note: even though it works, this issue is a first warning for you that your code starts to smell. You should look after this ActiveRecord::AttributeMethods::Serialization::Tire::Module thingy as it seems you will encounter it more than once in the future.
Other stuff:
You can simplify your method slightly:
def make_tires
4.times { tires.create }
end
At that point you might encounter some error you had initially. If you do, then please find what is going on with that Tire::Module thing. If you don't care about the smell:
has_many :tires, class_name: '::Tire'
I'm not sure what's causing the exception you are seeing but you have a number of issues. First, you need to pass in a car instance instead of the id in make_tires. Like this:
def make_tires
4.times { Tire.create(car: self) }
end
You also need to have attr_accessible :car in the Tire model. Like this:
class Tire < ActiveRecord::Base
belongs_to :car
attr_accessible :car
end
some of my models has a "company_id" column, that I want to set automatically. So I thought to override some method in activerecord base.
I tried this, in config/initializers, but does not work:
class ActiveRecord::Base
after_initialize :init
def init
if (self.respond_to(:company_id))
self.company_id= UserSession.find.user.company_id
end
end
end
Solution after Simone Carletti answer:
I created a module:
module WithCompany
def initialize_company
self.company_id= UserSession.find.user.company_id
end
end
And included this in the model:
class Exam < ActiveRecord::Base
include WithCompany
after_initialize :init
def init
initialize_company
end
end
Is there something else that I can do?
update 2
Best practices says to do not set session related fields in models. Use controllers for that.
There are two problems here. The first, is that you are injecting a bunch of stuff into all ActiveRecord models, whereas it would be better to add the feature only to the relevant models.
Secondary, you are breaking the MVC pattern trying to inject into the model the session context.
What you should do instead, is to code your feature in a module, and mix the module only in the relevant models. As per the context, rather than overriding the default AR behavior, add a new method where you pass the current session context (dependency injection) and returns the model initialized with the required company, when the session is set properly and the model is company-aware.
I have a standard ActiveRecord model with the following:
class MyModel < ActiveRecord::Base
custom_method :first_field, :second_field
end
At the moment, that custom_method is picked up by a module sent to ActiveRecord::Base. The functionality basically works, but of course, it attaches itself to every model class, not just MyModel. So if I have MyModel and MyOtherModel in the same action, it'll assume MyOtherModel has custom_method :first_field, :second_field as well.
So, my question is: How do I attach a method (eg: def custom_method(*args)) to every class that inherits from ActiveRecord::Base, but not by attaching it to ActiveRecord::Base itself?
Any ideas appreciated.
===
Edit
The custom_method is currently attached to ActiveRecord::Base by the following:
module MyCustomModule
def self.included(base)
base.extend(self)
end
def custom_method(*args)
# Zippity doo dah - code goes here
end
end
ActiveRecord::Base.send(:include, MyCustomModule)
Do you know about descendants?
ActiveRecord::Base.descendants
You have to be sure to touch the models before calling it.
See excellent discussion here:
Is there a way to get a collection of all the Models in your Rails app?
I concur with the commentors above that you may want to consider adding your methods to the meta class, or an intermediary class, or a Module mixin.
I'm trying to always run some code after .find or .where or whatever is used to retrieve objects.
For example, the following describes what I want, but does not work
Class Person < ActiveRecord::BA
#mortality=true
end
I want #mortality=true to run whenever a Person object is created
And based on my current understanding of ORM/ActiveRecord, a new object is created whenever retrieval is done. Hopefully that is correct.
You want to do this in the after_initialize method:
class Person < ActiveRecord::Base
def after_initialize
#mortality = true
end
end
Note that this is something you should avoid doing if possible because it happens on every object, even when you retrieve enormous result sets.
In this (albeit simple) case, you can do the assignment lazily by overriding the getter:
class Person < ActiveRecord::Base
def mortality
#mortality.nil? ? true : #mortality
end
end
(you can't use the nil gate ||= here because it filters false values as well)
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
Look for after_find and after_initialize callbacks.
Not sure on my Ruby syntax here.
I want to define a method that I can call like this: client.invoices.average_turnaround. So my average_turnaround method needs to work with a collection of ActiveRecord objects.
Here's my code thus far:
class Invoice < ActiveRecord::Base
...
def self.average_turnaround
return self.map(&:turnaround).inject(:+) / self.count
end
end
So I'm trying to find the sum of the turnaround times for each invoice, then divide it by the total number of invoices.
Ruby is complaining that there is no map method defined for Class. I was expecting self to be an Array.
How do I write a method that works on a collection of Invoices and uses the map function? Where am I going wrong?
If you want to use map within the class method as opposed to through an association extension. For example if it would be useful to call Invoice.average_turnaround directly or Invoice.where(x: y).average_turnaround. Place all. in front of map.
class Invoice < ActiveRecord::Base
...
def self.average_turnaround
all.map(&:turnaround).inject(:+) / all.count
end
end
Use average_turnaround using any collection.
You defined a class method, which is called on the class itself. What you need is an association extension. The method should be defined on your client model like this:
class Client < ActiveRecord::Base
has_many :invoices do
def average_turnaround
return map(&:turnaround).inject(:+) / count
end
end