I getting troubles with a self referential table.
I got an orb model which can hold planets, stars and moons. I want to tell that one thing "orbit" another
i look at the rails guide, but i coulnd get it working
My model:
class Orb < ActiveRecord::Base
belongs_to :orb_type
has_and_belongs_to_many :books
belongs_to :orbit, :class_name => "Orb"
has_many :orbs, :class_name => "Orb", :foreign_key => "orb_id"
attr_accessible :descr, :nome, :orb_type_id, :book_ids, :orb_id
validates :nome, uniqueness: true, presence: true
end
I think i am using bad relations names (maybe in the wrong way around)
1.9.3-p448 :002 > earth = Orb.find(1)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 1]]
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :003 > moon = Orb.find(2)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 2]]
=> #<Orb id: 2, nome: "Lua", descr: "asd", orb_type_id: 2, created_at: "2013-09-25 14:53:46", updated_at: "2013-09-25 14:55:31", orb_id: nil>
1.9.3-p448 :004 > sun = Orb.find(3)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 3]]
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :006 > moon.orbit=earth
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :007 > earth.orbit=sun
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :008 > earth
=> #<Orb id: 1, nome: "Terra", descr: "123123", orb_type_id: 1, created_at: "2013-09-25 14:53:35", updated_at: "2013-09-25 14:57:40", orb_id: nil>
1.9.3-p448 :009 > sun
=> #<Orb id: 3, nome: "Sol", descr: "asd", orb_type_id: 3, created_at: "2013-09-25 14:53:55", updated_at: "2013-09-25 14:53:55", orb_id: nil>
1.9.3-p448 :010 > moon
=> #<Orb id: 2, nome: "Lua", descr: "asd", orb_type_id: 2, created_at: "2013-09-25 14:53:46", updated_at: "2013-09-25 14:55:31", orb_id: nil>
in the end nothing get associated, the FK still nill.
The collun orb_id was added latter on the model. I setup a migration and added it in the model. I don't think it could be related to my problem...
EDIT:
Now everything is even odd. i change my model to:
class Orb < ActiveRecord::Base
belongs_to :orb_type
has_and_belongs_to_many :books
belongs_to :orbit, :class_name => "Orb"
has_many :orbits, :class_name => "Orb", :foreign_key => "orb_id"
attr_accessible :descr, :nome, :orb_type_id, :book_ids, :orb_id
validates :nome, uniqueness: true, presence: true
end
In rails console (rails c) i try:
1.9.3-p448 :008 > earth = Orb.find(1)
Orb Load (0.2ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."id" = ? LIMIT 1 [["id", 1]]
=> #<Orb id: 1, nome: "Terra", descr: "", orb_type_id: 1, created_at: "2013-09-25 17:51:26", updated_at: "2013-09-25 18:16:58", orb_id: 3>
1.9.3-p448 :009 > earth.orbit
=> nil
1.9.3-p448 :010 > earth.orbits
Orb Load (0.3ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."orb_id" = 1
=> [#<Orb id: 2, nome: "Lua", descr: "", orb_type_id: 2, created_at: "2013-09-25 17:51:40", updated_at: "2013-09-25 18:17:31", orb_id: 1>]
1.9.3-p448 :011 >
What the heck? orbits seen to return what i want, but it i try to use it:
1.9.3-p448 :004 > earth.orbits.nome
NoMethodError: Orb Load (0.4ms) SELECT "orbs".* FROM "orbs" WHERE "orbs"."orb_id" = 1
undefined method `nome' for #<ActiveRecord::Relation:0x00000003d593c0>
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/activerecord-3.2.12/lib/active_record/relation/delegation.rb:45:in `method_missing'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/activerecord-3.2.12/lib/active_record/associations/collection_proxy.rb:100:in `method_missing'
from (irb):4
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands/console.rb:47:in `start'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands/console.rb:8:in `start'
from /usr/local/rvm/gems/ruby-1.9.3-p448/gems/railties-3.2.12/lib/rails/commands.rb:41:in `<top (required)>'
from script/rails:6:in `require'
from script/rails:6:in `<main>'
Technically, it's not great that you have planets, stars, and moons all within the same model. They all behave differently and should each warrant their own model. E.g. a star will never orbit a planet, and a planet will never orbit a moon (by definition). By structuring your database the way you have, you're setting yourself up for corrupted data, which will always happen if you allow it to.
If you must have them all within the Orb model, I would recommend 2 models: 1 for the Orbs (stars, planets, moons), and 1 for the Orbits. The Orbit class would essentially be a table like so:
Orbit model:
| id | orbited_id | orbiter_id |
--------------------------------
| 0 | planet_id | moon_id |
| 1 | star_id | planet_id |
| 2 | planet_id | moon_id |
| ...| etc | etc |
And then you can set up the association in orb.rb:
has_and_belongs_to_many :orbits,
class_name: 'Orb',
join_table: :orbits,
foreign_key: :orbited_id,
association_foreign_key: :orbiter_id,
uniq: true
This will allow you to do things like
>> sun = Orb.find(1)
>> sun.orbits
=> [ <Orb mercury>, <Orb venus>, ..., <Orb pluto> ]
i figure it out. I should make the rom in the table withe the name of relation (orbit_id) instead of doing it the table name (orb_id)
Fixing that everything started to work nicely :)
has_many :orbters, class_name: "Orb", foreign_key: "orbit_id"
belongs_to :orbit, class_name: "Orb"
Related
I discovered this bug while reproducing Michael Hartl's scaffolded rails demo_app from his rails tutorial.
I went into the rails console
rails c
I asked for the User.all array of hashes and out it through its paces:
2.0.0-p0 :012 > User.all
User Load (0.3ms) SELECT "users".* FROM "users"
=> [#<User id: 2, name: "Lisa Johnson", email: "ljohnson#yahoo.com", created_at: "2013-04-02 03:30:06", updated_at: "2013-04-02 03:30:06">]
2.0.0-p0 :013 > User.all[0]
User Load (0.2ms) SELECT "users".* FROM "users"
=> #<User id: 2, name: "Lisa Johnson", email: "ljohnson#yahoo.com", created_at: "2013-04-02 03:30:06", updated_at: "2013-04-02 03:30:06">
2.0.0-p0 :015 > User.all[0]['id']
User Load (0.2ms) SELECT "users".* FROM "users"
=> 2
So far, so good.
However, User.all does not respond to the command to list say just the id's or names of all users:
2.0.0-p0 :017 > User.all { |i| puts i['id'] }
User Load (0.2ms) SELECT "users".* FROM "users"
=> [#<User id: 2, name: "Lisa Johnson", email: "ljohnson#yahoo.com", created_at: "2013-04-02 03:30:06", updated_at: "2013-04-02 03:30:06"gt;]
2.0.0-p0 :019 >User.all{ |i| puts i['name'] }
User Load (0.3ms) SELECT "users".* FROM "users"
=> [#<User id: 2, name: "Lisa Johnson", email: "ljohnson#yahoo.com", created_at: "2013-04-02 03:30:06", updated_at: "2013-04-02 03:30:06">]
Assigning an arbitrary variable a to the array of hashes User.all resolves the issue:
2.0.0-p0 :021 >a.each {|i| puts i['id'] }
2
=> [#<User id: 2, name: "Lisa Johnson", email: "ljohnson#yahoo.com", created_at: "2013-04-02 03:30:06", updated_at: "2013-04-02 03:30:06">]
2.0.0-p0 :022 >a.each {|i| puts i['name'] }
Lisa Johnson
=> [#<User id: 2, name: "Lisa Johnson", email: "ljohnson#yahoo.com", created_at: "2013-04-02 03:30:06", updated_at: "2013-04-02 03:30:06">]
This User.all issue affects at least ruby versions 1.9.2,1.9.3 and 2.0.0. Whoever is responsible for writing the gem that created User.all needs to go over his all method. For whatever it's worth, I am working with rails 3.2.12
Nothing to discuss - its a straightforward bug report. If there is a question, the question would be "why is User.all is not behaving like the array of hashes it is?"
Well my models are User, Exercise and Writing.
class User < ActiveRecord::Base
has_many :exercises, :through => :writings
has_many :writings
end
class Exercise < ActiveRecord::Base
has_many :users, :through => :writings
has_many :writings
end
class Writing < ActiveRecord::Base
belongs_to :user
belongs_to :exercise
attr_accessible :writing_date, :exercise_id
end
my db/schema.rb
ActiveRecord::Schema.define(:version => 20120517142448) do
create_table "exercises", :force => true do |t|
t.string "etitle"
t.text "ebody"
t.decimal "average", :precision => 3, :scale => 1
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_kind", :default => 0
end
create_table "writings", :force => true do |t|
t.integer "user_id"
t.integer "exercise_id"
t.date "writing_date"
t.datetime "created_at"
t.datetime "updated_at"
end
the join table is Writing. I try to show on exercise view (app/view/exercises/index.html.erb) for each exercise_id her writing_date. Each user_id has many exercise_id through Writing table. Each record on Writing table has writing_id, user_id, exercise_id and writing_date. In console for specifically exercise_id I made it like this:
1.9.2p290 :001 > #exercise = Exercise.find_by_id(9)
=> #<Exercise id: 9, etitle: "PLS51-2012-E01", ebody: "q", average: #<BigDecimal:53eeba8,'0.1E2',9(18)>, created_at: "2012-05-20 14:31:07", updated_at: "2012-05-20 14:34:27", askisi_file_name: nil, askisi_content_type: nil, askisi_file_size: nil, askisi_updated_at: nil>
1.9.2p290 :002 > #exercise.writings
=> [#<Writing id: 6, user_id: 1, exercise_id: 9, writing_date: "2012-06-29", created_at: "2012-05-20 14:36:45", updated_at: "2012-05-20 14:36:45">, #<Writing id: 12, user_id: 7, exercise_id: 9, writing_date: "2012-06-20", created_at: "2012-05-20 14:40:12", updated_at: "2012-05-20 14:40:12">, #<Writing id: 13, user_id: 7, exercise_id: 9, writing_date: "2012-05-02", created_at: "2012-05-20 15:41:54", updated_at: "2012-05-20 15:41:54">]
1.9.2p290 :003 > #exercise.writings.order("writing_date ASC")
=> [#<Writing id: 13, user_id: 7, exercise_id: 9, writing_date: "2012-05-02", created_at: "2012-05-20 15:41:54", updated_at: "2012-05-20 15:41:54">, #<Writing id: 12, user_id: 7, exercise_id: 9, writing_date: "2012-06-20", created_at: "2012-05-20 14:40:12", updated_at: "2012-05-20 14:40:12">, #<Writing id: 6, user_id: 1, exercise_id: 9, writing_date: "2012-06-29", created_at: "2012-05-20 14:36:45", updated_at: "2012-05-20 14:36:45">]
1.9.2p290 :004 > #exercise.writings.order("writing_date ASC").last
=> #<Writing id: 6, user_id: 1, exercise_id: 9, writing_date: "2012-06-29", created_at: "2012-05-20 14:36:45", updated_at: "2012-05-20 14:36:45">
Do I need scope?
Thanks in advance!
Finaly the solution for my problem that can help someone with the same situation is simple. File app/view/exercises/index.html.erb
Code:
<%= exercise.writings.last.try(:writing_date) %>
$ rails -v
Rails 3.1.1
$ ruby -v
ruby 1.9.2p312 (2011-08-11 revision 32926) [i686-linux]
If you want to reproduce the problem, just follow me:
First, create these three model(just copy):
#school.rb
class School
include Mongoid::Document
include Mongoid::Timestamps
has_many :students
end
#student.rb
class Student
include Mongoid::Document
include Mongoid::Timestamps
has_many :books
belongs_to :school
accepts_nested_attributes_for :books
end
#book.rb
class Book
include Mongoid::Document
include Mongoid::Timestamps
field :name
belongs_to :student
validate :check
def check
# The calling for the 'school' method caused the issue
self.student.school
end
end
Second, run your console and paste:
ruby-1.9.2-head :001 > School.destroy_all;Student.destroy_all; Book.destroy_all; School.create
ruby-1.9.2-head :001 > Student.create school_id: School.first.id, 'books_attributes' => {'1' => {'name' => 'I am a book'}}
Then, let's see what happend:
ruby-1.9.2-head :002 > Book.count
MONGODB xxx_development['$cmd'].find({"count"=>"books", "query"=>{}, "fields"=>nil})
=> 2
And even more, if you set the 'student has_many books' relation to 'autosave: true':
class Student
......
has_many :books, autosave: true
......
end
Let's see what will happend:
ruby-1.9.2-head :001 > School.destroy_all;Student.destroy_all; Book.destroy_all; School.create
ruby-1.9.2-head :001 > Student.create school_id: School.first.id, 'books_attributes' => {'1' => {'name' => 'I am a book'}}
ruby-1.9.2-head :002 > Student.count
MONGODB xxx_development['$cmd'].find({"count"=>"students", "query"=>{}, "fields"=>nil})
=> 2
ruby-1.9.2-head :004 > Student.all.to_a
MONGODB xxx_development['students'].find({})
=> [#<Student _id: 4f62a8341d41c81bc6000002, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, school_id: BSON::ObjectId('4f62a8341d41c81bc6000001')>, #<Student _id: 4f62a8341d41c81bc6000003, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, school_id: nil>]
ruby-1.9.2-head :005 > Book.count
MONGODB xxx_development['$cmd'].find({"count"=>"books", "query"=>{}, "fields"=>nil})
=> 2
ruby-1.9.2-head :006 > Book.all.to_a
MONGODB xxx_development['books'].find({})
=> [#<Book _id: 4f62a8341d41c81bc6000003, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, name: "I am a book", student_id: BSON::ObjectId('4f62a8341d41c81bc6000002')>, #<Book _id: 4f62a8341d41c81bc6000002, _type: nil, created_at: 2012-03-16 02:40:52 UTC, updated_at: 2012-03-16 02:40:52 UTC, name: nil, student_id: nil>]
This bug really run me crazy.
Why there are additional models when calling 'school' in a book validate method?
Or there is something I did wrong?
The code is fine here, you're not doing anything incorrect - but Mongoid has no issue with this same code on master or 2.4.x. See my suggestions here to find the culprit:
https://github.com/mongoid/mongoid/issues/1826
My models:
class Contact < ActiveRecord::Base
has_many :addresses
has_many :emails
has_many :websites
accepts_nested_attributes_for :addresses, :emails, :websites
attr_accessible :prefix, :first_name, :middle_name, :last_name, :suffix,
:nickname, :organization, :job_title, :department, :birthday,
:emails_attributes
end
class Email < ActiveRecord::Base
belongs_to :contact
validates_presence_of :account
validates_format_of :account, :with => /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/i, :on => :create
attr_accessible :contact_id, :account, :label
end
If I run the following query, the emails are returned as expected:
c = Contact.find(3)
Contact Load (3.2ms) SELECT `contacts`.* FROM `contacts` LIMIT 1
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
c.emails
Email Load (4.4ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` = 3
=> [#<Email id: 3, contact_id: 3, account: "not#real.address", label: "work", created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">]
However, attempting to :include the relationship does not:
c = Contact.find(3, :include => :emails)
Contact Load (0.5ms) SELECT `contacts`.* FROM `contacts` WHERE `contacts`.`id` = 3 LIMIT 1
Email Load (0.8ms) SELECT `emails`.* FROM `emails` WHERE `emails`.`contact_id` IN (3)
=> #<Contact id: 3, prefix: nil, first_name: "Micah", middle_name: nil, last_name: "Alcorn", suffix: nil, nickname: nil, organization: nil, job_title: nil, department: nil, birthday: nil, created_at: "2011-07-04 23:50:04", updated_at: "2011-07-04 23:50:04">
As you can see, the SQL is being executed, but the emails are not being returned. I intend to return all contacts with each containing email(s), so :joins won't do any good. What am I missing?
The emails are there. Did you try c.emails? You will find that the emails will be there without Rails doing an additional DB query.
The thing that :include does is called eager loading, which basically means Rails will try a best effort method of prepopulating your objects with their relations, so that when you actually ask for the relation no additional DB queries are needed.
See the section "Eager loading of associations" here:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
You might also want to check out this RailsCast:
http://railscasts.com/episodes/181-include-vs-joins
How would you model the references and citations to publications (articles, books, chapters, etc...)?
A publication can be an article, book or a chapter and it has many references to other publications and other publications refer to it (call these citations)
I need to be able to list the relationships among the publications: The references in a publication and the citations from other publications to this publication
My initial understanding is that this would be a polymorphic relationship to handle the different types of publications and that it would require a bidirectionalself join.
My stab at it
Publication
belongs_to :writing, :polymorphic =>true
has_and_belongs_to_many :references
:class_name => "Publication"
:join_table => 'reference_citation'
:foreign_key => 'reference_id'
:foreign_key => 'citation_id'
Book, Chapter, Article all have:
has_many :publications :as =>writing
I find this a bit confusing so any suggestions that would help clarify it would be great. Even object and field naming suggestions.
[I asked a less clear version of this question here.]
I also probably need to use has many through because I will need the ability to destroy the relationship
Here's a solution using a self-referential relationship using single table inheritance. Use these commands to create the app:
$ rails myproject
$ cd myproject
$ script/generate model publication type:string name:string
$ script/generate model citation publication_id:integer reference_id:integer
The setup the relationships this way:
class Publication < ActiveRecord::Base
has_many :citations
has_many :cited_publications, :through => :citations, :source => :reference
has_many :references, :foreign_key => "reference_id", :class_name => "Citation"
has_many :refered_publications, :through => :references, :source => :publication
end
class Citation < ActiveRecord::Base
belongs_to :publication
belongs_to :reference, :class_name => "Publication"
end
class Article < Publication
end
class Book < Publication
end
class Chapter < Publication
end
Now we can create the DB and try it out from the console:
$ rake db:migrate
$ script/console
Loading development environment (Rails 2.2.2)
>> a = Article.create!(:name => "Article")
=> #<Article id: 1, ...>
>> b = Book.create!(:name => "Book")
=> #<Book id: 2, ...>
>> a.citations.create(:reference => b)
=> #<Citation id: 1, publication_id: 1, reference_id: 2, created_at: "2009-02-15 14:13:15", updated_at: "2009-02-15 14:13:15">
>> a.citations
=> [#<Citation id: 1, ...>]
>> a.references
=> []
>> b.citations
=> []
>> b.references
=> [#<Citation id: 1, publication_id: 1, reference_id: 2, created_at: "2009-02-15 14:13:15", updated_at: "2009-02-15 14:13:15">]
>> a.cited_publications
=> [#<Book id: 2, type: "Book", name: "Book", created_at: "2009-02-15 14:11:00", updated_at: "2009-02-15 14:11:00">]
>> a.refered_publications
=> []
>> b.cited_publications
=> []
>> b.refered_publications
=> [#<Article id: 1, type: "Article", name: "Article", created_at: "2009-02-15 14:10:51", updated_at: "2009-02-15 14:10:51">]
Here's a solution that doesn't use Single Table Inheritance for the publications. That means that there are articles, books and chapters tables, instead of one publications table. Here are the commands to run to create the app:
$ rails myproject
$ cd myproject
$ script/generate model book name:string
$ script/generate model chapter name:string
$ script/generate model article name:string
$ script/generate model citation publication_type:string publication_id:integer reference_type:string reference_id:integer
Create this file in lib/acts_as_publication.rb:
module ActsAsPublication
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def acts_as_publication
has_many :citations, :as => :publication
has_many :references, :as => :reference, :class_name => "Citation"
end
end
end
Create this file in config/initializers/acts_as_publication.rb:
ActiveRecord::Base.send(:include, ActsAsPublication)
Then call that in each model, Article, Book and Chapter, like this:
class Article < ActiveRecord::Base
acts_as_publication
end
Then add these relationships in app/models/citation.rb:
class Citation < ActiveRecord::Base
belongs_to :publication, :polymorphic => true
belongs_to :reference, :polymorphic => true
end
Now we can create the DB and try it out from the console:
$ rake db:migrate
$ script/console
Loading development environment (Rails 2.2.2)
>> a = Article.create!(:name => "a")
=> #<Article id: 1, ...>
>> b = Article.create!(:name => "b")
=> #<Article id: 2, ...>
>> Citation.create!(:publication => a, :reference => b)
=> #<Citation id: 1, publication_type: "Article", publication_id: 1, reference_type: "Article", reference_id: 2, created_at: "2009-02-15 13:14:27", updated_at: "2009-02-15 13:14:27">
>> a.citations
=> [#<Citation id: 1, ...>]
>> a.references
=> []
>> b.citations
=> []
>> b.references
=> [#<Citation id: 1, ...>]
>> Book.create!(:name => "foo")
=> #<Book id: 1, name: "foo", created_at: "2009-02-15 13:18:23", updated_at: "2009-02-15 13:18:23">
>> a.citations.create(:reference => Book.first)
=> #<Citation id: 2, publication_type: "Article", publication_id: 1, reference_type: "Book", reference_id: 1, created_at: "2009-02-15 13:18:52", updated_at: "2009-02-15 13:18:52">
>> Book.first.references
=> [#<Citation id: 2, ...>]
>> a.citations
=> [#<Citation id: 1, publication_type: "Article", publication_id: 1, reference_type: "Article", reference_id: 2, created_at: "2009-02-15 13:14:27", updated_at: "2009-02-15 13:14:27">, #<Citation id: 2, publication_type: "Article", publication_id: 1, reference_type: "Book", reference_id: 1, created_at: "2009-02-15 13:18:52", updated_at: "2009-02-15 13:18:52">]
I have an incomplete answer over at http://github.com/francois/so-536261/tree/master
Basically, the DB schema does support your use case, but ActiveRecord doesn't. The solution will probably involve using find by sql or other tricks.