So here we go. I've got an Activerecord::Base model, let it be called a human.
class human < ActiveRecord::Base
has_one :Animal
end
Animal is an abstract class -
class animal < ActiveRecord::Base
self.abstract_class = true;
end
And I have a subclass of animal, let it be dog
class dog < Animal
in case I don't use abstract class, I can't add instance variables to 'Dog' (because it stores in 'Animal' table). In case I use abstract class, I can't add an 'Animal' to 'Human' - because rails doesn't know, how to store, for example, 'Dog'(ActiveRecord error: couldn't find table ''). This situation drives me crazy, and I just can't get over it.
Am I missing something or just doin' it completely wrong?
By convention in Ruby, Animal would refer to a class (actually, it's a bit more involved - this link has some more detail). In your original post, "class dog" should be "class Dog" b/c the class name is a constant, and if you had a has_one association between human and animal, you could say human.animal = (some instance of animal), but human.Animal is likely to have strange effects if it doesn't just immediately crash. The STI approach that others are recommending will do exactly what you want, though you would set the 'type' value, not 'Animal' (please don't actually do this directly).
You should read up on the meaning of capitalization in Ruby and RoR, STI, active record associations, and polymorphic associations. Something like this should work (not tested, and it's bad normalization - you can use has_one associations and a pattern called delegation to set up a situation where generic animal traits are in one table, and 'human specific' traits are in another to avoid a bunch of NULL columns in your database):
# remember to set up your migrations to add a 'type' column to your Animal table
# if animals can own other animals who own other animals, you may want to look at
# acts_as_tree, which does trees in relational databases efficiently
class Animal < ActiveRecord::Base
self.abstract_class = true
end
class Dog < Animal
# this is bad normalization - but you can keep this simple by adding
# a human_id field in your animal table (don't forget to index)
# look into the 'belongs_to' / 'references' type available for DB migrations
belongs_to :human
end
class Human < Animal
has_one :dog, :autosave => true # or you could use 'has_many :dogs'
end
human = Human.new # => adds record to Animal table, with type = 'human'
dog = Dog.new
human.dog = dog
human.save
ActiveRecord has built-in support for polymorphic associations, so you could do that:
http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
ActiveRecord by default gets the table name from the name of a model. You can override that, however. If you want Dogs in one table, Cats in other, etc. then you can do (in Rails 3.2):
class Dog < Animal
self.table_name = 'dogs'
end
class Cat < Animal
self.table_name = 'cats'
end
(You'll have to add migrations to create those tables.)
However, if you want all animals to exist in one table, you should look at Single-Table-Inheritance. See the ActiveRecord docs for more on that.
Related
I was recently working on a project where I faced a dilemma of choosing between two ways of getting same results. Here is the class structure:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
end
An author has first name, last name. I want to get the full name of the author for a given book as an instance method.
In simple active record terms, since book is associated with author, we can get the author name for a book as follows:
For example in Book class, we have:
class Book < ApplicationRecord
belongs_to :author
def author_name
"#{author.first_name} #{author.last_name}"
end
end
And we get the result!
But, according to the target of minimizing dependencies (POODR Book), future ease of change and better object oriented design, the book should not know properties of an author. It should interact with an author object by interfaces.
So Book should not be the one responsible for getting the Author name. The author class should.
class Book < ApplicationRecord
belongs_to :author
def author_name
get_author_name(self.author_id)
end
private
#minimizing class dependecies by providing private methods as external interfaces
def get_author_name(author_id)
Author.get_author_name_from_id(author_id)
end
end
class Author < ApplicationRecord
has_many :books
#class methods which provides a gate-way for other classes to communicate through interfaces, thus reducing coupling.
def self.get_author_name_from_id(id)
author = self.find_by_id(id)
author == nil ? "Author Record Not Found" : "#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
Now, book is just interacting with the public interface provided by Author and Author is handling the responsibility of getting full name from its properties which is a better design for sure.
I tried running the queries as two separate methods in my console:
class Book < ApplicationRecord
def author_name
get_author_name(self.author_id)
end
def author_name2
"#{author.last_name} + #{author.first_name}"
end
end
The results are shown below:
Looks like both run the same queries.
My questions are
Does rails convert author.last_name called inside the Book class to
the same SQL query as Author.find_by_id(author_id).last_name called inside
Author class (through message passing from Book class) in case of bigger data size?
Which one is more performant in case of bigger data size?
Doesn't calling author.last_name from Book class violates design
principles ?
It's actually much more common and simplier to use delegation.
class Book < ApplicationRecord
belongs_to :author
delegate :name, to: :author, prefix: true, allow_nil: true
end
class Author < ApplicationRecord
has_many :books
def name
"#{first_name.titleize} #(last_name.titleize}"
end
end
As to performance, if you join the authors at the time of the book query you end up doing a single query.
#books = Book.joins(:author)
Now when you iterate through #books and you call individually book.author_name no SQL query needs to be made to the authors table.
1) Obviously not, it performs JOIN of books & authors tables. What you've made requires 2 queries, instead of 1 join you'll have book.find(id) and author.find(book.author_id).
2) JOIN should be faster.
3) Since last_name is a public interface, it absolutely doesn't violate design principles. It would violate principles if you were accessing author's last name from outside like that: Book.find(1).author.last_name - that's a bad thing. Correct is: Book.find(1).authors_last_name - and accessing author's name inside Model class.
Your provided example seems to be overcomplicated to me.
According to the example you shared, you only want to get full name of the book's author. So, the idea of splitting responsibility is correct, but in Author class should be simple instance method full_name, like:
class Author < ApplicationRecord
has_many :books
def full_name
"#{author.first_name.titleize} #{author.last_name.titleize}"
end
end
class Book < ActiveRecord::Base
belongs_to :author
def author_name
author.full_name
end
end
Note, there're no direct queries in this code. Once you'll need the author's name somewhere (in a view, in api response, etc), Rails will make the most optimized query possible (depends on your use case though, it may be ineffective for example, if you call iterate over books and call author in a loop)
I prefer the second approach because the full_name is property of author not a book. If the book wants to access that information, it can using book.author&.full_name (& is for handling cases of books with no authors).
but I would suggest a refactoring as below:
class Book < ApplicationRecord
belongs_to :author
end
class Author < ApplicationRecord
has_many :books
def full_name
"#{firstname} #{lastname}"
end
end
Does rails convert author.last_name called inside the Book class to the same SQL query as Author.find_by_id(author_id).last_name called inside Author class (through message passing from Book class) in case of bigger data size?
Depend upon the calling factor, like in your example both will generate the same query. But if you have a include\join clause while getting the Book/Author, both will generate different queries.
As per the rails convention, Author.find_by_id(author_id).last_name is not recommended as it will always fire a query on database whenever the method is called. One should use the rails' association interface to call the method on related object which is smart to identify the object from memory or fetch it from database if not in memory.
Which one is more performant in case of bigger data size?
author.last_name is better because it will take care of joins, include, and memoization clauses if used and avoid the N+1 query problem.
Doesn't calling author.last_name from Book class violates design principles?
No, you can even use delegate like #Steve Suggested.
In my experience, it's a balancing act between minimizing code complexity and minimizing scalability issues.
However, in this case, I think the simplest solution that would separate class concerns and minimize code would be to simply use: #book.author.full_name
And in your Author.rb define full_name in Author.rb:
def full_name
"#{self.first_name} #{self.last_name}"
end
This will simplify your code a lot. For example, if in the future you had another model called Magazine that has an Author, you don't have to go define author_name in the Magazine model as well. You simply use #magazine.author.full_name. This will DRY up your code nicely.
I want to have two layers of STI. The first layer is working but am unsure how to have the first model lookup to another table also using STI. Example:
class Instrument < ActiveRecord::Base
end
class Guitar < Instrument
end
class Piano < Instrument
# and so on...
Ok. But I want to track categories of instruments so I can tell their type:
class InstrumentType < ActiveRecord::Base
end
class StringInstrumentType < InstrumentType
end
# etc.
So... our Guitar model becomes:
class Guitar < Instrument
belongs_to :string_intrument_type
end
class StringInstrumentType < InstrumentType
has_many :guitars
end
How do I implement this? What do I name the foreign-key column and how do I tell Rails/AR what to do?
Am I way off base here?
This scenario is invented but we are finding some real-world refactoring wants us to move in this direction.
Your first STI makes sense, the other one does not. On the abstract level, strings are rather an instance of an InstrumentType, not its subclass. It seems to me that you might rather use sth like:
class Instrument < ActiveRecord::Base
# abstract
end
class StringInstrument < Instrument
# abstract
end
module Instrument::Keyboard
end
class Guitar < StringInstrument
end
class Piano < StringInstrument
include Instrument::Keyboard
end
or similar. Obviously it depends A LOT on your actual code. In example you gave instrument types are rather constant in time, hence it is possible to use inheritance.
My guess is however, that your types are dynamic and can be added by clients. What you are looking for then is probably a tree structure. So every type has its parent and each parent type might have couple of children. You can then simply do Instrument belongs_to instrument_type and point to some InstrumentType tree leaf.
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
I have a following model structure.
I have an Itinerary that has many itinerary nodes. Each itinerary node is a wrapper around either a place, hotel, activity etc. So for example.
Itinerary = "Trip to Paris"
Itinerary.itinerary_nodes = [Node1, Node2, Node3] where
Node1 = "JFK Airport"
Node2 = "CDG Airport"
Node3 = "Eiffel Tower"
So essentially nodes represents the places you will visit in your itinerary. In my model structure; lets assume that my airports are modeled different from monuments or hotels. Now I want to create a association such that;
class ItineraryNode
include Mongoid::Document
has_one :stopover
end
Where each stopover can be a different object. It's type and id is stored by default and is later inflated using that.
So how do I declare multiple models to be associated to ItineraryMode? I can implement this specifically by ensuring that I set these attributes manually in initializer; but curious if something like this is supported by default.
Cheers
This is not a "has_one", it is a "belongs_to" (polymorphic)
class ItineraryNode
include Mongoid::Document
belongs_to :stopover, :polymorphic => true
belongs_to :itinerary
end
class Airport
include Mongoid::Document
has_many :itinerary_nodes, :as => :stopover
end
class Place
include Mongoid::Document
has_many :itinerary_nodes, :as => :stopover
end
So now you can get:
#itinerary.itinerary_nodes.each do |node|
if node.stopover.is_a? Airport
puts "Welcome to #{note.stopover.name}"
elsif node.stopover.is_a? Event
puts "Bienvenue, would you like a drink?"
elsif node.stepover.is_a? Place
puts "The ticket line is over there"
end
end
(I used an if construct just to show better the polymorphism, you would use a case construct here...)
You see that node.stepover can be of many classes.
EDIT (after the comment, I understand that the ItineraryNodemodel is an attempt to a handcrafted polymorphism for a many-to-many association.
From the Mongoid documentation:
Polymorhic behavior is allowed on all relations with the exception of has_and_belongs_to_many.
So you need to use an intermediate model (ItineraryNode). The provided solution is the simplest one I can think of.
I think my question is best described as an example. Let's say I have a simple model called "Thing" and it has a few attributes that are simple data types. Something like...
Thing
- foo:string
- goo:string
- bar:int
That isn't hard. The db table will contain three columns with those three attributes and I can access them with something like #thing.foo or #thing.bar.
But the problem I'm trying to solve is what happens when "foo" or "goo" can no longer be contained in a simple data type? Assume that foo and goo represent the same type of object. That is, they are both instances of "Whazit" just with different data. So now Thing might look like this...
Thing
- bar:int
But now there is a new model called "Whazit" that looks like this...
Whazit
- content:string
- value:int
- thing_id:int
So far this is all good. Now here is where I'm stuck. If I have #thing, how can I set it up to refer to my 2 instances of Whazit by name (For the record, the "business rule" is that any Thing will always have exactly 2 Whazits)? That is, I need to know if the Whazit I have is basically foo or goo. Obviously, I can't do #thing.foo in the current setup, but I'd that is ideal.
My initial thought is to add a "name" attribute to Whazit so I can get the Whatzits associated with my #thing and then choose the Whazit I want by name that way. That seems ugly though.
Is there a better way?
There are a couple of ways you could do this. First, you could set up two belongs_to/has_one relationships:
things
- bar:int
- foo_id:int
- goo_id:int
whazits
- content:string
- value:int
class Thing < ActiveRecord::Base
belongs_to :foo, :class_name => "whazit"
belongs_to :goo, :class_name => "whazit"
end
class Whazit < ActiveRecord::Base
has_one :foo_owner, class_name => "thing", foreign_key => "foo_id"
has_one :goo_owner, class_name => "thing", foreign_key => "goo_id"
# Perhaps some before save logic to make sure that either foo_owner
# or goo_owner are non-nil, but not both.
end
Another option which is a little cleaner, but also more of a pain when dealing with plugins, etc., is single-table inheritance. In this case you have two classes, Foo and Goo, but they're both kept in the whazits table with a type column that distinguishes them.
things
- bar:int
whazits
- content:string
- value:int
- thing_id:int
- type:string
class Thing < ActiveRecord::Base
belongs_to :foo
belongs_to :goo
end
class Whazit < ActiveRecord::Base
# .. whatever methods they have in common ..
end
class Foo < Whazit
has_one :thing
end
class Goo < Whazit
has_one :thing
end
In both cases you can do things like #thing.foo and #thing.goo. With the first method, you'd need to do things like:
#thing.foo = Whazit.new
whereas with the second method you can do things like:
#thing.foo = Foo.new
STI has its own set of problems, though, especially if you're using older plugins and gems. Usually it's an issue with the code calling #object.class when what they really want is #object.base_class. It's easy enough to patch when necessary.
Your simple solution with adding a "name" doesn't need to be ugly:
class Thing < ActiveRecord::Base
has_one :foo, :class_name => "whazit", :conditions => { :name => "foo" }
has_one :goo, :class_name => "whazit", :conditions => { :name => "goo" }
end
In fact, it's quite similar to how STI works, except you don't need a separate class.
The only thing you'll need to watch out for is setting this name when you associate a whazit. That can be as simple as:
def foo=(assoc)
assos.name = 'foo'
super(assoc)
end