Aliasing a referenced relationship field in Mongoid - ruby-on-rails-3

In the Mongoid model below, how do I alias the belongs_to relationship field?
class Contact
field :nm, :as => :name, :type => String # field aliasing
embeds_one :address, :store_as => :ad # embedded document aliasing
belongs_to :account # referenced relation doesn't support store_as
end
I want to store the account id in a field called ac instead of account_id.

You can use :foreign_key to specify the mongodb field name.
belongs_to :account, foreign_key: :ac
However, if you want to use account_id, you need to declare its alias:
alias :account_id :ac
or defining account_id before belongs_to:
field :account_id, as: :ac

Mongoid allows to use arbitrary name for a relationship by using of 'inverse_of'
If an inverse is not required, like a belongs_to or has_and_belongs_to_many, ensure that :inverse_of => nil is set on the
relation. If the inverse is needed, most likely the inverse cannot be
figured out from the names of the relations and you will need to
explicitly tell Mongoid on the relation what the inverse is.
So, for use 'ac' as an alias it's necessary to add inverse_of:
class Contact
field :nm, :as => :name, :type => String # field aliasing
embeds_one :address, :store_as => :ad # embedded document aliasing
belongs_to :ac, class_name: 'Account', inverse_of: :contact
end
class Account
has_one :contact, class_name: 'Contact', inverse_of: :ac
end

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

Many to many relationship with metadata stored in the mapping table in activerecord

I have two tables with a many to many relationship, through a third table. In the third table is a piece of data I need to assign when I build the relationships between the two tables, how can I use ActiveRecords build method to assign that?
Here is code to show what I mean:
class Company < Contact
has_many :contact_companies
has_many :people, :through => :contact_companies
accepts_nested_attributes_for :people, :allow_destroy => true
accepts_nested_attributes_for :contact_companies
end
class Person < Contact
has_many :contact_companies
has_many :companies, :through => :contact_companies
accepts_nested_attributes_for :companies, :allow_destroy => true
accepts_nested_attributes_for :contact_companies
end
class ContactCompany < ActiveRecord::Base
belongs_to :person
belongs_to :company
end
ContactCompany contains a data member called "position". What I want to do is something like:
c = Person.new
c.companies.build(:name => Faker::Company.name, :position => positions.sample)
EDIT:
When I try the code above I get "unknown attribute: position".
The c.companies.build line is attempting to build a Company object which does not have the position attribute (the ContactCompany does) hence the error. It looks like you are trying to set attributes on two different models, so you'll have to make sure you are setting the appropriate attribute on the right model:
# you can chain these calls but I separated them for readability
cc = c.contact_companies.build(:position => positions.sample)
cc.build_company(:name => Faker::Company.name)

ActiveRecord Find with condition based on associated record having a value in a list

With the following Rails models:
class Group < ActiveRecord::Base
has_many :group_locations, :dependent => :restrict
has_many :locations, :through => :group_locations, :dependent => :restrict
end
class Location < ActiveRecord::Base
has_many :group_locations, :dependent => :destroy
has_many :groups, :through => :group_locations
end
class GroupLocation < ActiveRecord::Base
belongs_to :group
belongs_to :location
end
I am trying to Find all of the locations that are associated with at least one of several groups contained in the string "group_list" (e.g. "1,2,3,4,5"). If it was a field from the Location record, I would specify a condition of "*field in (#{group_list})*". But how do I accomplish my goal when I want to have at least one of the location's "group_location" whose "group_id" is in the list (or, alternatively, one "group" whose group_id is in the list).
I know how to do it with pure SQL, but how do you do it with Rails?
#taro You are right. Started by adding the code
joins(:group_locations).where("group_id in (?)", group_id_array)
Then I proceeded to define a scope just to make it a nice package:
scope :locations_in_groups, lambda { |grparray| joins(:group_locations).where("group_id in (?)", grparray) }
Thanks for your help.

Setting up a polymorphic has_many :through relationship

rails g model Article name:string
rails g model Category name:string
rails g model Tag name:string taggable_id:integer taggable_type:string category_id:integer
I have created my models as shown in the preceding code. Articles will be one of many models which can have tags. The category model will contain all categories which may be assigned. The tag model will be a polymorphic join-table which represents tagged relationships.
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :categories, :through => :taggable
end
class Category < ActiveRecord::Base
has_many :tags, :as => :taggable
has_many :articles, :through => :taggable
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
I can't seem to get this to work, I can do it non polymorphic, but I must have something wrong with the polymorphic part. Any ideas?
Edit: Still not getting this right:
class Article < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :categories, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Category < ActiveRecord::Base
has_many :taggables, :as => :tag
has_many :articles, :through => :taggables, :source => :tag, :source_type => "Article"
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
To create a polymorphic has_many :through, you must first create your models. We will use'Article,' 'Category,' and 'Tag' where 'Tag' is the join-model and Article is one of many objects which can be "tagged" with a category.
First you create your 'Article' and 'Category' models. These are basic models which do not need any special attention, just yet:
rails g model Article name:string
rails g model Category name:string
Now, we will create our polymorphic join-table:
rails g model Tag taggable_id:integer taggable_type:string category_id:integer
The join-table joins together two tables, or in our case one table to many others via polymorphic behavior. It does this by storing the ID from two separate tables. This creates a link. Our 'Category' table will always be a 'Category' so we include 'category_id.' The tables it links to vary, so we add an item 'taggable_id' which holds the id of any taggable item. Then, we use 'taggable_type' to complete the link allowing the link to know what it is linked to, such as an article.
Now, we need to set up our models:
class Article < ActiveRecord::Base
has_many :tags, :as => :taggable, :dependent => :destroy
has_many :categories, :through => :tags
end
class Category < ActiveRecord::Base
has_many :tags, :dependent => :destroy
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
end
class Tag < ActiveRecord::Base
belongs_to :taggable, :polymorphic => true
belongs_to :category
end
After this, setup your database using:
rake db:migrate
That's it! Now, you can setup your database with real data:
Category.create :name => "Food"
Article.create :name => "Picking the right restaurant."
Article.create :name => "The perfect cherry pie!"
Article.create :name => "Foods to avoid when in a hurry!"
Category.create :name => "Kitchen"
Article.create :name => "The buyers guide to great refrigeration units."
Article.create :name => "The best stove for your money."
Category.create :name => "Beverages"
Article.create :name => "How to: Make your own soda."
Article.create :name => "How to: Fermenting fruit."
Now you have a few categories and various articles. They are not categorized using tags, however. So, we will need to do that:
a = Tag.new
a.taggable = Article.find_by_name("Picking the right restaurant.")
a.category = Category.find_by_name("Food")
a.save
You could then repeat this for each, this will link your categories and articles. After doing this you will be able to access each article's categories and each categorie's articles:
Article.first.categories
Category.first.articles
Notes:
1)Whenever you want to delete an item that is linked by a link-model make sure to use "destroy." When you destroy a linked object, it will also destroy the link. This ensures that there are no bad or dead links. This is why we use ':dependent => :destroy'
2)When setting up our 'Article' model, which is one our 'taggable' models, it must be linked using :as. Since in the preceeding example we used 'taggable_type' and 'taggable_id' we use :as => :taggable. This helps rails know how to store the values in the database.
3)When linking categories to articles, we use:
has_many :articles, :through => :tags, :source => :taggable, :source_type => 'Article'
This tells the category model that it should have many :articles through :tags. The source is :taggable, for the same reason as above. The source-type is "Article" because a model will automatically set taggable_type to its own name.
You simply cannot make the join table polymorphic, at least Rails does not support this out of the box. The solution is (taken from Obie's Rails 3 way):
If you really need it, has_many :through is possible with polymorphic associations, but only by specifying exactly what type of polymorphic associations you want. To do so you must use the :source_type option. In most cases you will have to use the :source option, since the association name will not match the interface name used for the polymorphic association:
class User < ActiveRecord::Base
has_many :comments
has_many :commented_timesheets, :through => :comments, :source => :commentable,
:source_type => "Timesheet"
has_many :commented_billable_weeks, :through => :comments, :source => :commentable,
:source_type => "BillableWeek"
It's verbose and the whole scheme loses its elegance if you go this route, but it works:
User.first.commented_timesheets
I hope I helped!

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.