Rails - Create instance of a model from within another model - ruby-on-rails

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

Related

Associations: one to many with existing table

Rails beginner here:
I already have a database and table, so the naming convention is giving me some headaches
class Item < ActiveRecord::Base
belongs_to :categorie, :foreign_key => "catid"
end
class Categorie < ActiveRecord ...
has_many :item
end
i = Item.first # Ok
c = i.Categorie # Ok, finds proper Categorie based on "catid" of i
c.Item # fails with Categorie_id column not found ! how can i map Categorie_id to "catid"?
You're a rails beginner but you might not be a programmer beginner so I'll dive in and explain classes a little bit.
A class is simply a data object that holds methods. Nothing more. Here's a simple one that holds one method:
class Cow
def talk
"moo"
end
end
Cow is the class, talk is the method. Now, if we have the above classes in memory, we cannot do this in the console:
talk
Because that method isn't available at the global scope. This is a good thing, because this could cause bugs and is inefficient. Imagine if we have a few animals:
class Cat
def talk
"meow"
end
end
class Dog
def talk
"woof"
end
end
Running talk, how would the computer know which talk to run? Instead, we call the method that's inside the class like this:
Cow.talk #=> "moo"
Cat.talk #=> "meow"
Dog.talk #=> "woof"
Hopefully now, this code:
Item.first
is less cryptic. Item is a class, and first is a method available inside that class.
Now I know Item is a model, but in rails, models are simply classes that inherit a bunch of useful methods from ActiveRecord. At the top of the Item model you should see this:
class Item < ActiveRecord::Base
That's what pulls in all of the useful methods, such as the first method we're using. Because of this inheritance, we can imagine your Item class looks a bit like this:
class Item < ActiveRecord::Base
def first
# code is in here that queries the table in your database that has
# the downcased and pluralized name of Item (so items) and returns the first
# row of that table
end
# down here is all of your methods you've probably created. Validations and the like.
end
first, rather than return a string like in my example does something far more useful; it queries the table in your database that has the downcased and pluralized name of its class. So Item.first queries the items table, and returns the first row.
Now, I have to be honest, despite what you say, I find it highly doubtful that i.Categorie finds the proper Categorie based on the "catid" of i. If it truly does I feel you've done some crazy workaround to get that working. This is what should happen:
i.Categorie
NoMethodError: undefined method `Categorie' for #<Item:0x00000005905830>
In plain English, this means
NoMethodError: there is no 'Categorie' method inside that instance of the 'Item' class.
And this makes sense because I see no 'Categorie' method in here:
class Item < ActiveRecord::Base
def first
# code is in here that queries the table in your database that has
# the downcased and pluralized name of Item (so items) and returns the first
# row of that table
end
# down here is all of your methods you've probably created. Validations and the like.
end
Now the reason c.Item doesn't work is because c is set to nil because nil was returned by i.Categorie due to the non-method error, and nil certainly doesn't have the method Item inside it.
c = i.Categorie # c is set to nil due to noMethodError
c.Item
NoMethodError: undefined method `Item' for nil:NilClass
Hopefully you understand a bit more what's going on now. If you want your code to work you should be doing this. Look closely, there are a few nuances:
i = Item.first # i is set to the first instance of Item
c = i.categorie # c is set to the instance of Categorie that i belongs to
is = c.items # returns an array consisting of all the Item instances that belong to the Categorie instance in c
We could also do this:
is.first # returns i
So where do all these handy methods come from? The categorie method inside i (i.category), the items method inside c (c.items)? The answer is they're created dynamically by Rails based on your inheritance and pulled into the relevant model by < ActiveRecord::Base.
By "based on your inheritance" I mean, how you've used the inheritance methods, belongs_to and has_many:
class Item < ActiveRecord::Base
belongs_to :categorie, :foreign_key => "catid" # creates categorie method that returns the instance of Categorie this instance of Item belongs to
end
class Categorie < ActiveRecord ...
has_many :item # creates items method that returns an array of all the instances of Item that belong to this instance of Categorie
end
I would also point out that Categorie is a pretty terrible Model name, purely because it's spelt wrongly. Maybe Type would be better?
You can do
Item.create (:catid => #categorie.id)
#categorie = Categorie.find(params[:id]) or with Categorie.all
place the each loop & find the id .
First you should have used Category for model because rails intelligently understands the plural categories or tables.
Secondly, you should have something like this;
class Item < ActiveRecord::Base
belongs_to :Category, :foreign_key => "catid"
end
class Categorie < ActiveRecord ...
has_many :items
end
i = Item.first
c = i.Category
c.items #to find all items that belong to the category c

How do I create a base class that abstracts a has and belongs to many relationship in Rails 4.1

I have the following HABTM relation and corresponding logic that is identical in at least 3 different tables, so I wanted to factor it, and related methods, into a common base class. The problem is, even though the association does show up in the object's association_cache, trying to access it results in an exception (ArgumentError comparison of nil object with a string). I'm confused about why this isn't working, as this seems like basic OOP programming.
My base model is set up like this:
class ProfileItem < ActiveRecord::Base
self.abstract_class = true
has_and_belongs_to_many :profiles
attr_accessor :profile_ids
before_destroy :clean_up
before_save :update_profiles
def get_profile_names
self.profiles.each do |p| # << exception here
...
Look into a concept called concerns, introduced in Rails 4. Abstracting a class inherited from ActiveRecord::Base is a recipe for trouble.
A good explanation How to use concerns in Rails 4.

Prevent STI when inheriting from an ActiveRecord model

On Rails 3.2.6, I have a class that inherits from ActiveRecord::Base:
class Section < ActiveRecord::Base
...
end
When I inherit from this class, Rails will assume I want STI:
class AnotherSection < Section
..Rails assumes I have a type field, etc...
end
I want to be able to inherit from the Section class and use the subclass as a normal Ruby subclass, without the Rails STI magic.
Is there a way to prevent STI when subclassing from an ActiveRecord::Base model?
You can achieve this by disabling the inheritance_column for the model, like so:
class AnotherSection < Section
# disable STI
self.inheritance_column = :_type_disabled
end
The accepted answer will definitely work, but the recommended (dare I say "proper" :) way is to set abstract_class:
class Section < ActiveRecord::Base
self.abstract_class = true
end
The only fully supported strategy to store inheritance on ActiveRecord is STI. You can, however, simulate concrete class-table inheritance at your own risk. The concrete class-table inheritance with abstract superclass works fine, as pointed by smathy.
BUT ... If what you want is to make AnotherSection just an ordinary class (that will not be persisted at the database), you could disable the discriminator column (as suggested by Veraticus). However, if you save the AnotherSection it will be persisted in the same table as Section, and you will not be able to tell them apart. Also, if you use AnotherSection to find a Section, it will return an AnotherSection, breaking the original instantiation:
#create a Section and saves it
sect = Section.create()
sect.save()
#retrieve the Section as a AnotherSection, breaking polymorphism...
sect = AnotherSection.find(sect.id)
# another section is more than a section, it is inconsistent.
If AnotherSection is not intended to be persisted, the safest path it to override the persistence operations, such as save() and find():
class AnotherSection < Section
# disable STI, as pointed by Veraticus
self.inheritance_column = :_type_disabled
# disable save and finding
def save(*args)
#exception? do nothing?
end
def find(*args)
#exception? do nothing?
end
def find_by(*args)
#exception? do nothing?
end
# this does not stops here! there is first, last, and even a forty_two finder method! not to mention associations...
end
in a nutshell, you can do this, but you SHOULDN´T. The risk is high.
You should consider another option, such as using MIXIN instead of inheritance.

Rails gets wrong class when two classes have the same name

In my Rails I have the following models:
A STI sub-class
class Subscription::Discount < Subscription
def self.new_with_url
...
end
end
and another model class (doing completely different things, this is a STI base class)
class Discount < ActiveRecord::Base
end
So in my controller, I uses Subscription::Discount when I create users:
#user.subscription = ::Subscription::Discount.new_with_url()
However it complains: undefined method 'new_with_url' for #<Class:0x007fbb499c6740>
I think Rails is not calling the right class with new_with_url. On top of that I am not sure what #<Class:0x007fbb499c6740> is. So, two questions:
Without renaming any model, how can I reference Subscription::Discount properly?
Why is the error message saying #<Class:0x007fbb499c6740>, I can understand if it is Discount instead of that anonymous class.
EDIT:
Here are all the relevant models:
app/model/discount.rb
app/model/coffee_discount.rb (CoffeeDiscount < Discount)
app/model/subscription.rb
app/model/subscription/discount.rb (Subscription::Discount < Subscription)
The method is named create_with_url but you're calling new_with_url.
Fix the method name.

Modules vs. Classes and their influence on descendants of ActiveRecord::Base

Here's a Ruby OO head scratcher for ya, brought about by this Rails scenario:
class Product < ActiveRecord::Base
has_many(:prices)
# define private helper methods
end
module PrintProduct
attr_accessor(:isbn)
# override methods in ActiveRecord::Base
end
class Book < Product
include PrintProduct
end
Product is the base class of all products. Books are kept in the products table via STI. The PrintProduct module brings some common behavior and state to descendants of Product. Book is used inside fields_for blocks in views. This works for me, but I found some odd behavior:
After form submission, inside my controller, if I call a method on a book that is defined in PrintProduct, and that method calls a helper method defined in Product, which in turn calls the prices method defined by has_many, I'll get an error complaining that Book#prices is not found.
Why is that? Book is a direct descendant of Product!
More interesting is the following..
As I developed this hierarchy PrintProduct started to become more of an abstract ActiveRecord::Base, so I thought it prudent to redefine everything as such:
class Product < ActiveRecord::Base
end
class PrintProduct < Product
end
class Book < PrintProduct
end
All method definitions, etc. are the same. In this case, however, my web form won't load because the attributes defined by attr_accessor (which are "virtual attributes" referenced by the form but not persisted in the DB) aren't found. I'll get an error saying that there is no method Book#isbn. Why is that?? I can't see a reason why the attr_accessor attributes are not found inside my form's fields_for block when PrintProduct is a class, but they are found when PrintProduct is a Module.
Any insight would be appreciated. I'm dying to know why these errors are occurring!
You might have better luck delaying the attr_accessor call in PrintProduct until mixin-time:
module PrintProduct
def self.included(base)
base.attr_accessor :isbn
end
# other instance methods here
end
The problem is likely something to do with timing of the attr_accessor call and how that applies to modules mixed in. I'm not certain that the timing is defined by the Ruby spec, so it might vary betweeen implementations or versions.

Resources