Embedded reference not saved - ruby-on-rails-3

Under mongoid and rails 3 I have a collection of Users and a collection a Projects which embed many Relationships, the models are:
class User
include Mongoid::Document
field :name, :type => String
referenced_in :relationship, :inverse_of => :user
end
class Project
include Mongoid::Document
field :title, :type => String
embeds_many :relationships
end
class Relationship
include Mongoid::Document
field :type, :type => String
references_one :user
embedded_in :subject, :inverse_of => :relationships
end
My problem is that the referenced user of a relationship is never saved into the relationship. For example for following command only saves :type:
project1 = Project.new( :title => "project1", :relationships => [ {:type => "master", :user => "4d779568bcd7ac0899000002"} ] )
My goal is to have a project document similar to this:
{ "_id" : ObjectId("4d77a8b2bcd7ac08da00000f"), "title" : "project1", "relationships" : [
{
"type" : "master",
"user" : ObjectId("4d775effbcd7ac05a8000002"),
"_id" : ObjectId("4d77a8b2bcd7ac08da000010")
}
] }
The :user is never present, am I missing something here? Thanks a lot for your help!
Ted

So a couple things you might want to change:
1) Avoid the field name "type" as this is a rails magic column name used by single table inheritance. Maybe change them to user_type and relationship_type.
2) With Mongoid 2.0 and up you can use Active Model syntax like has_many and belongs_to instead of references. http://mongoid.org/docs/relations/referenced/1-n.html
3) For your create, instead of assigning user with a user ID, try assigning a user object.
project1 = Project.new( :title => "project1", :relationships => [ {:type => "master", :user => User.first} ] )
Or you could assign a user_id like so:
project1 = Project.new( :title => "project1", :relationships => [ {:type => "master", :user_id => "the_use_id_you_want_to_associate"} ] )
FYI, you don't have to specify the inverse_of in "referenced_in :relationship, :inverse_of => :user". Just "referenced_in :relationship" will do the trick.

Related

Rails ActiveRecord builds wrong foreign key if there are two belongs_to relationships to same model

Using Rails 3.2.3, I have User and Message models. Each message is owned by a user, and each message has an optional from_user field that also takes a user.id.
app/models/user.rb
class User < ActiveRecord::Base
has_many :messages, :foreign_key => "owner_id", :inverse_of => :owner
has_many :messages, :foreign_key => "from_user_id", :inverse_of => :from_user
end
app/models/message.rb
class Message < ActiveRecord::Base
belongs_to :owner, :class_name => "User", :inverse_of => :messages
validates :owner, :presence => true # Every message must have an owner_id
belongs_to :from_user, :class_name => "User", :inverse_of => :messages
end
The problem I'm seeing is with the .build method. The main reason to use .build is to instantiate a (possibly protected) foreign key, right? (See the Rails Guide on Active Record associations: "the link through their foreign key will be created.") However when I run
#message = #user.messages.build(<accessible attributes>)
I find that it is filling in the optional from_user and not the mandatory owner.
Is there some way to control which foreign key .build fills in? Or do I need to just use .new and assign all foreign keys manually?
#message = Message.new(<accessible attributes>)
#message.owner = #user
#message.from_user = #another_user
ActiveRecord doesnt like that you have 2 associations with the same name. You're going to have to change the association names. This means that you will also have to supply the class_name attibute. Maybe something like:
has_many :owner_messages, :class_name => 'Message', :foreign_key => "owner_id", :inverse_of => :owner
has_many :user_messages, :class_name => 'Message', :foreign_key => "from_user_id", :inverse_of => :from_user

Rails 3.1 validates uniqueness of nested attributes for polymorphic association

Given the following models:
##Invoice.rb
has_many :line_items, :as => :line_itemable
accepts_nested_attributes_for :line_items
##LineItem.rb
belongs_to :line_itemable, :polymorphic => true
validates :employee_id, :presence => true, :uniqueness => { :scope => [ :line_itemable_id, :line_itemable_type ] }
How would I go about validating the following new invoice
i = Invoice.new
i.line_items << [ LineItem.new( :employee_id => 1 ), LineItem.new( :employee_id => 1 ) ]
i.valid?
The invoice should not be valid because the line_items employee_id's are the same and yet no error is thrown and the line_items are added to the database. If the Invoice is an existing record the validations do work.
Any ideas? Is this a bug?
To prevent bad data I have added the following index but would like to have the proper rails validations
add_index :line_items, [ :employee_id, :line_itemable_type, :line_itemable_id ], :unique => true, :name => 'index_line_item_employee_id'
I would like to code exactly as you did, but the only way that I find to bypass this was to write a custom validate in the Invoice class. This solution has a drawback that the problematic fields doesn't get highlighted.

MongoID, embedding a document in multiple documents

I have a model Address like following
class Address
include Mongoid::Document
field :line1
field :city
# more fields like this
embedded_in :user, :inverse_of => :permanent_address
embedded_in :user, :inverse_of => :current_address
embedded_in :college, :inverse_of => :address
end
There are models College and User which embed address
class College
include Mongoid::Document
references_many :users
embeds_one :address
# some fields and more code
end
class User
include Mongoid::Document
referenced_in :college, :inverse_of => :users
embeds_one :permanent_address, :class_name => "Address"
embeds_one :current_address, :class_name => "Address"
# fields and more code
end
I am getting some problems with the above setup. I am using single form to ask for current and permanent address along with some more information, but only current_address is getting saved and that too with the data I populate in permanent_address.
Parameters:
{"utf8"=>"✓",
"authenticity_token"=>"KdOLvzmKyX341SSTc1SoUG6QIP9NplbAwkQkcx8cgdk=",
"user"=> {
"personal_info_attributes"=>{...},
"nick_names_attributes"=>{...},
"current_address_attributes"=>{
"line1"=>"",
"area"=>"",
"country"=>"USA",
"postal_code"=>"sd",
"city"=>"",
"state"=>"",
"landmark"=>"",
"id"=>"4d891397932ecf36a4000064"
},
"permanent_address_attributes"=>{
"line1"=>"",
"area"=>"asd",
"country"=>"india",
"postal_code"=>"",
"city"=>"",
"state"=>"",
"landmark"=>""
},
"commit"=>"Submit", "id"=>"4d8903d6932ecf32cf000001"}
MONGODB alma_connect['users'].find({:_id=>BSON::ObjectId('4d8903d6932ecf32cf000001')})
MONGODB alma_connect['users'].update({"_id"=>BSON::ObjectId('4d8903d6932ecf32cf000001')},
{"$set"=>{
"current_address"=>{
"line1"=>"",
"area"=>"asd",
"country"=>"india",
"postal_code"=>"",
"city"=>"",
"state"=>"",
"landmark"=>"",
"_id"=>BSON::ObjectId('4d8916e9932ecf381f000005')}}})
I am not sure if this is something I am doing wrong here or there is some other problem. I am using Rails 3.0.4 and MongoID 2.0.0.rc.7
Update:
I upgraded to mongoid 2.0.1 and changed my user to include inverse of options in address.
class User
include Mongoid::Document
referenced_in :college, :inverse_of => :users
embeds_one :permanent_address, :class_name => "Address", :inverse_of => :permanent_address
embeds_one :current_address, :class_name => "Address", :inverse_of => :current_address
# fields and more code
end
I know the inverse of names doesn't make sense, but the main point here is just to make them different or if you have good names for relations in your embedded class(like :current_user, :permanent_user), you should use that for inverse of.
Looks good to me. I've a similar setup and it works as expected.

How to write an AREL statement on a self-referencing table

I have written quite a few AREL statements, but I'm tying myself in knots over this one. Here is my situation:
class Product < AR::Base
has_many :parents, :class_name => "ProductLink", :foreign_key => :to_product_id
has_many :children, :class_name => "ProductLink", :foreign_key => :from_product_id
# has an attribute called "identifier"
end
class ProductLink < AR::Base
belongs_to :parent, :class_name => "Product", :foreign_key => :from_product_id
belongs_to :child, :class_name => "Product", :foreign_key => :to_product_id
end
I want to retrieve all of the Products that have a child product with an identifier that matches some value.
I have twisted myself into a pretzel with this, seems easy, but I have been looking at it for too long now. I appreciate any help!
Got it!
brand.products.joins(:children => :child).where(:children => { :child => { :searchable_identifier.matches => "2136" } } )
That works great. See the hashed joins? That's what was throwing me off.

multiple joins using activerecord in rails

I'm building a small twitter style microblogging service where users can follow other users and get a feed of their messages
I have the following models:
class Follow < ActiveRecord::Base
belongs_to :follower, :class_name => "User"
belongs_to :followee, :class_name => "User"
end
class User < ActiveRecord::Base
has_many :follows, :foreign_key => 'follower_id',
:class_name => 'Follow'
has_many :followers, :through => :follows
has_many :followed, :foreign_key => 'followee_id',
:class_name => 'Follow'
has_many :followees, :through => :followed
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
To get a feed for the current user, I want to perform the following SQL query:
SELECT * FROM follows JOIN users JOIN messages WHERE follows.follower_id = current_user.id AND follows.followee_id = users.id AND users.id = messages.user_id;
What is the correct ActiveRecord way of doing this?
Not sure what you're looking for, but here is my suggestion:
I assume that you have other purposes for that Follow class, otherwise I don't see the purpose of it.
The "correct way" (i.e. my completely subjective way) to do it would actually be something like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :followers, :foreign_key => 'followed_id',
:class_name => 'User', :association_foreign_key => 'follower_id',
:include => [:messages]
has_and_belongs_to_many :follows, :foreign_key => 'follower_id',
:class_name => 'User', :association_foreign_key => 'followed_id'
has_many :messages
end
class Message < ActiveRecord::Base
belongs_to :user
end
Then create the following table:
create_table :users_users, :id => false do |t|
t.integer :followed_id
t.integer :follower_id
end
And you're set:
followed = User.find :first
follower = User.find :last
followed.followers << follower
followed.followers.first.messages
followed.followers.first.followers.first.messages # etc...
But from what I make it, you want to show all the messages from all the followers at the same time.
This should be possible to achieve by adding
has_and_belongs_to_many :followed_messages, :foreign_key => 'follower_id',
:class_name => 'Message', :association_foreign_key => 'followed_id'
to the User class, but I don't know how correct that way would be. Or it might be possible to achieve with association extensions but there I can't really give any examples.
Update:
By changing the :class_name, it will associate it with the Message.id, didn't think about that so it will not be correct in this way.
So the only "nice" option is to go through the User class like in the first example.
The only other options I can see is either the association extensions (which I can't give you an example for) or perhaps using a finder statement.
has_many :followed_messages, :class_name => 'Message',
:finder_sql => 'select * from messages where user_id in(select followed_id from users_users where follower_id = #{id})'
You probably have to customize that sql statement to get everything to work, but at least you should get the picture :)
Keijro's arrangement would work better, though if you need the Follow table, then you can execute the SQL query you specified as follows:
Follow.all(:joins => { :messages, :users }, :conditions => { "follows.follower_id" => current_user.id, "follows.followee_id" => "users.id", "users.id" => "messages.user_id"} )