I'm trying to achieve a really simple "multiple relationship" in Rails (3.2.12) with Mongoid (3.1.2). I got three models:
class User
include Mongoid::Document
field :name
has_many :collections
end
class Collection
include Mongoid::Document
field :name
belongs_to :user
has_many :things
end
class Thing
include Mongoid::Document
field :name
belongs_to :collection
end
So basically there is a user who has collections and there are collections which have "Things".
Well ... first question: Is this even possible? And if not: Why not? ;-)
I can create users now (from a rails console or via web), and I can create collections belonging to users. But when I try to create a "Thing" now ... well it just doesn't get created:
2.0.0-p0 :014 > u = User.first
=> #<User _id: 514b2b32e05658e1f1000005, name: "test">
2.0.0-p0 :015 > c = Collection.create!(name: "somename", user: u)
=> #<Collection _id: 514b30f0e05658ca67000001, name: "somename", user_id: "514b2b32e05658e1f1000005">
2.0.0-p0 :016 > t = Thing.create!(name: "a thing", collection: c)
=> #<Thing _id: 514b3120e05658ca67000002, name: "a thing", collection_id: "514b30f0e05658ca67000001">
2.0.0-p0 :017 > Thing.first
=> nil
The "Collection" gets created just fine:
2.0.0-p0 :018 > Collection.first
=> #<Collection _id: 514b2b65e05658e1f1000006, name: "test", user_id: "514b2b32e05658e1f1000005">
Change the Collection class name to something else, say to 'Stockpile'. MongoDB uses 'collection' as part of it's terminology, so it may not play well with your models.
Related
class Hotel
include Mongoid::Document
field :title, type: String
embeds_many :comments
end
class Comment
include Mongoid::Document
field :text, type: String
belongs_to :hotel
validates :text, presence: true
end
h = Hotel.create('hotel')
=> <#Hotel _id: 52d68dd47361731d8b000000, title: "hotel">
c = Comment.new(text: 'text')
=> <#Comment _id: 52d68f3d7361731d8b040000, text: "text", hotel_id: nil>
h.comments << c
=> [#<Comment _id: 52d68f3d7361731d8b040000, text: "text", hotel_id: nil>]
h.save
=> true
Hotel.last.comments
=> []
variant 2
h.comments << Comment.new(text: 'new', hotel_id: h.id)
=> [<#Comment _id: 52d68f3d7361731d8b040000, text: "text", hotel_id: nil>, <#Comment _id: 52d691e17361731d8b050000, text: "new", hotel_id: BSON::ObjectId('52d68dd47361731d8b000000')>]
h.save
=> true
Hotel.last.comments
=> []
I see two possible problems:
Hotel.last is not necessarily Hotel 52d68dd47361731d8b000000. You should look at h.comments or to be paranoid, h.reload and h.comments.
Your associations are confused.
From the fine manual:
Embedded 1-n
One to many relationships where the children are embedded in the
parent document are defined using Mongoid's embeds_many and
embedded_in macros.
Defining
The parent document of the relation should use the embeds_many macro
to indicate it has n number of embedded children, where the document
that is embedded uses embedded_in.
So your relation should be defined like this:
class Hotel
embeds_many :comments
end
class Comment
embedded_in :hotel
end
You're using belongs_to: hotel in Comment when you should say embedded_in :hotel.
The docs also say that:
Definitions are required on both sides to the relation in order for it to work properly.
and your relation is incorrectly configured on one side so it won't work properly.
Say I have two ActiveRecord Models (I'm not going to include migrations):
class User < ActiveRecord::Base
belongs_to :subscription, :inverse_of => :users
end
and
class Subscription < ActiveRecord::Base
has_many :users, :inverse_of => :subscription
end
In rails console I can create a new object for each model, then add a user to the subscription model:
>> s = Subscription.create
=> #<Subscription id: 1>
>> u = User.create
=> #<User id: 1, subscription_id: nil>
>> s.users << u
=> [#<User id: 1, subscription_id: 1>]
>> u
=> #<User id: 1, subscription_id: 1>
But if I use the Array.clear method, it clears out the collection, but doesn't immediately update the associated model:
>> s.users.clear
=> []
>> u
=> #<User id: 1, subscription_id: 1>
If I call u.reload it will pull the most recent version from the database, but it shouldn't have to do that in order to update my cached model. I tried using :inverse_of as suggested in Bi-Directional Associations, but it didn't work.
I have three models, Employee, Job, LeaveDay. like below:
class Employee
field :name
belongs_to :job
end
class Job
field :job_title
has_many :employees
has_many :leave_days
end
class LeaveDay
belongs_to :job
field :no_of_leave_days
end
I want to establish an relationship in which I want to track no_of_leave_days of employee related to their job. How is it possible. Thanks in advance.
i think u need to add 'belongs_to: employee' in LeaveDay and 'has_many: leave_days' in Employee. so, #emp.leave_days will get u all the leave for all the jobs of a particular employee. similarly, #emp.job.leave_days will return the leave_days for the current job of the cuttent employee.
I'm not really familiar with Mongoid. You may read here more about relations in mongoid.
It's very easy example. Hope it'll help.
This is models definition:
class Employee
include Mongoid::Document
field :name
belongs_to :job
end
class Job
include Mongoid::Document
field :job_title
has_many :employees
embeds_many :leave_days
end
class LeaveDay
include Mongoid::Document
embedded_in :job
field :no_of_leave_days
end
And example of usage:
pry(main)> j = Job.create(job_title: "Test Job")
=> #<Job _id: 510633e0784931179a000001, _type: nil, job_title: "Test Job">
pry(main)> LeaveDay.create(job: j, no_of_leave_days: 1)
=> #<LeaveDay _id: 5106348f784931179a000002, _type: nil, no_of_leave_days: 1>
pry(main)> LeaveDay.create(job: j, no_of_leave_days: 2)
=> #<LeaveDay _id: 51063687784931179a000003, _type: nil, no_of_leave_days: 2>
pry(main)> LeaveDay.create(job: j, no_of_leave_days: 3)
=> #<LeaveDay _id: 51063689784931179a000004, _type: nil, no_of_leave_days: 3>
pry(main)> Job.first.leave_days.map(&:no_of_leave_days)
=> [1, 2, 3]
pry(main)> Employee.create(name: 'Employee name', job: Job.first)
=> #<Employee _id: 510637cf784931179a000005, _type: nil, name: "Employee name", job_id: "510633e0784931179a000001">
pry(main)> Employee.first.job.leave_days.map(&:no_of_leave_days)
=> [1, 2, 3]
I have see solutions to this sort of issue with 1:N, but they dont seem to read across to 1:1, this is using MongoDB 1.8, Mongoid 2.0.0.rc.8, Rails 3.0.5
class Coach
include Mongoid::Document
field :name, :type => String
belongs_to :coached, :class_name => Team, :inverse_of => :coach, :foreign_key => "coach_id"
belongs_to :assisted, :class_name => Team, :inverse_of => :assist, :foreign_key => "assist_id"
end
class Team
include Mongoid::Document
field :name, :type => String
has_one :coach, :class_name => Coach, :inverse_of => :coached
has_one :assist, :class_name => Coach, :inverse_of => :assisted
end
Then I start and Rails Console session and:
irb(main):001:0> c = Coach.new(:name => "Tom")
=> #<Coach _id: da18348d298ca47ad000001, _type: nil, _id: BSON::ObjectId('4da18348d298ca47ad000001'), name: "Tom", coach_id: nil, assist_id: nil>
irb(main):002:0> a = Coach.new(:name => "Dick")
=> #<Coach _id: 4da18352d298ca47ad000002, _type: nil, _id: BSON::ObjectId('4da18352d298ca47ad000002'), name: "Dick", coach_id: nil, assist_id: nil>
irb(main):003:0> t = Team.new(:name => "Allstars")
=> #<Team _id: 4da18362d298ca47ad000003, _type: nil, _id: BSON::ObjectId('4da18362d298ca47ad000003'), name: "Allstars">
irb(main):005:0> t.coach = c
NoMethodError: undefined method `constantize' for Coach:Class
irb(main):005:0> c.coached = t
NoMethodError: undefined method `constantize' for Team:Class
any advice would be much appreciated!
You're referencing the class Team when defining Coach, but the class does not yet exist.
You have two options:
Declare the class_name as a String instead of the constant e.g. :class_name => 'Team' (preferred, see gist)
Completely remove :class_name => Team option and let Mongoid figure out the correct classes participating in the association. There is one caveat: You will need to make sure that the class Team is declared before the class Coach (the order of loading your source codes now matters, so this solution is not ideal)
How do I display my polymorphic associations in my view? I have the following model.
class Blog < ActiveRecord::Base
belongs_to :users
has_one :category, :as => :categorizable
end
class Category < ActiveRecord::Base
belongs_to :categorizable, :polymorphic => true
end
class User < ActiveRecord::Base
has_many :blogs
has_many :categories, :as => :categorizable
end
However, my problem is, I get a nil when trying to display the category.
rails console
=> user = User.first
=> user.blogs
[]
=> user.categories
[]
=> category = user.categories
=> category = Category.new
=> category.name = "Education"
=> category.save!
true
=> user.categories
[#<Category id: 1, name: "Education", categorizable_id: 1, categorizable_type: "User", created_at: "...", updated_at: "...">]
=> user.blogs.create(:title => "...", :body => "...", :category => category)
[#<Blog id: 1, user_id: 1, title: "...", body: "...", created_at: "...", updated_at: "...">]
=> blogs = user.blogs.all
=> blogs.each do |blog|
puts blog.category
end
nil
=> blogs.first.category
[#<Category id: 1, name: "Education", categorizable_id: 1, categorizable_type: "User", created_at: "...", updated_at: "...">]
I don't get it, why blog.category is returning nil when I use the each block? How do I display the entries of my polymorphic model through my views?
Update:
The design is, as a user, I want to be able to create categories, and assign them to my blogs. Each blogs has one category on them, which I'd like to access via the Category model as a categorizable blog.
It should be really nice, if it's working as-is as the logic is trying to say, but currently it does not. Polymorphic models are supposed to be elegant, but right now, polymorphic sucks for me and unusable. I'm still waiting for someone to help me provide with a better solution.
There is a flaw in the design. Consider the following:
user = User.create
cat = user.categories.create(:name=>"Education")
Now there is one category:
> cat
# Category id: 1, name: "Education" categorizable_id:1 categorizable_type: "User"
When you add a blog and assign the same category to it:
user.blogs.create(:category=>cat)
It overwrites the polymorphic type and id:
# UPDATE "categories" SET "categorizable_type" = 'Blog', "categorizable_id" = 1,
"updated_at" = '2011-02-27 06:20:29.968336' WHERE ("categories"."id" = 1)
And now the category is no longer associated with the user:
user.reload
user.categories # => []
You're really trying to model a many to many relationship here. I'd suggest adding join tables for UserCategory and BlogCategory, and getting rid of the polymorphism, which isn't helping.
Pardon me for my earlier answer. zetetic's answer makes absolute sense. Try avoiding polymorphs.
Update: I've decided that polymorphic model was not the way to go, and I've solved it with good ol' STI which does the same thing.