I have a Comment class with a :foreign_key of post_id in the Post class.
class Comment < ActiveRecord::Base
belongs_to :post, :class_name => "Post", :foreign_key => "post_id", :counter_cache => true
belongs_to :author, :class_name => "User", :foreign_key => "author_id"
end
But my CreateComments migration does not define a database-level foreign key:
class CreateComments < ActiveRecord::Migration
def self.up
create_table :comments do |t|
t.column "post_id", :integer, :default => 0, :null => false
t.column "author", :string, :default => "", :limit => 25, :null => false
t.column "author_email", :string, :default => "", :limit => 50, :null => false
t.column "content", :text, :null => false
t.column "status", :string, :default => "", :limit => 25, :null => false
t.timestamps
end
end
def self.down
drop_table :comments
end
end
Instead post_id is a simple Integer column.
So, it seems that this foreign key relationship exists only in the mind of Rails, not at the database level.
Is this correct?
Also, is it necessary for the corresponding Post model to also declare its reciprocal foreign key relationship with Comments using the :foreign_key attribute or could that be omitted?
class Post < ActiveRecord::Base
set_table_name("blog_posts")
belongs_to :author, :class_name => "User", :foreign_key => 'author_id'
has_many :comments, :class_name => "Comment",
:foreign_key => 'post_id', :order => "created_at desc", :dependent => :destroy
has_many :categorizations
has_many :categories, :through => :categorizations
named_scope :recent, :order => "created_at desc", :limit => 5
end
The Rails default behaviour is that the column used to hold the foreign key on a model is the name of the association with the suffix _id added. The :foreign_key option lets you set the name of the foreign key directly. The associations between your Post and Comment model classes should look like this:
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
end
—Note that you don't need :class_name => "Post" in your Comment model. Rails already has that information. You should only be specifying :class_name and :foreign_key when you need to override the Rails' conventions.
You're correct that Rails maintains the foreign key relationships for you. You can enforce them in the database layer if you want by adding foreign key constraints.
I think you would benefit from reading A Guide to ActiveRecord Associations.
Related
How in rails 4.1 enum prescribe in migration something like this?
class CreateFriendsUsers < ActiveRecord::Migration
def change
create_table :friends_users, id: false do |t|
t.integer :friend_id
t.integer :user_id
t.integer :status, [:active, :deactive]
end
end
end
this is join table for user and user
has_and_belongs_to_many :friends,
:class_name => "User",
:foreign_key => "user_id",
:association_foreign_key => "friend_id",
:join_table => "friends_users",
:after_add => :add_friend,
:after_remove => :remove_friend
I am trying to save a person's Facebook friends into my database. I want to store the Facebook users in a table and then store their friendships in another table. The friendships would have the integer of the FacebookUser that requested the friendship and the integer of the friend, both of which are foreign keys to the facebook_users table. However I keep getting this message when I try to link the a user's facebook friends with friendships.
Error
ActiveRecord::HasManyThroughSourceAssociationNotFoundError: Could not find the source association(s) :friend or :friends in model Friendship. Try 'has_many :friends, :through => :friendships, :source
=> <name>'. Is it one of :FacebookUser or :FacebookFriend?
friendship.rb
class Friendship < ActiveRecord::Base
attr_accessible :facebook_user_id, :facebook_friend_id
belongs_to :FacebookUser
belongs_to :FacebookFriend, :class_name => :FacebookUser
end
facebook_user.rb
class FacebookUser < ActiveRecord::Base
attr_accessible :first_name, :gender, :last_name
has_many :friendships, :foreign_key => :facebook_user_id
has_many :friends, :through => :friendships, :source => :FacebookUser
end
Schema
create_table "facebook_users", :force => true do |t|
t.string "first_name"
t.string "last_name"
t.string "gender"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
end
create_table "friendships", :force => true do |t|
t.integer "facebook_user_id"
t.integer "facebook_friend_id"
end
the convention Rails uses is to use associations as defined by the class name and the foreign key. if you've set up your tables like above, you should change your models to the following.
class Friendship < ActiveRecord::Base
attr_accessible :facebook_user_id, :facebook_friend_id
belongs_to :facebook_user # implies a foreign key of facebook_user_id and class of FacebookUser
belongs_to :facebook_friend, class_name: 'FacebookUser' #implies a foreign key of facebook_friend_id
end
class FacebookUser < ActiveRecord::Base
attr_accessible :first_name, :gender, :last_name
has_many :friendships
has_many :friends, :through => :friendships, :source => :facebook_friend
end
I have a many-to-many association between a Post and a Tag model:
post.rb:
has_many :taggings, dependent: :destroy
has_many :tags, through: :taggings
tag.rb:
has_many :taggings, :dependent => :destroy
has_many :posts, :through => :taggings
tagging:
attr_accessible :tag_id, :post_id
belongs_to :post
belongs_to :tag
I want to have a page where I list all the tags and how many posts each tag has.
So I added a posts_count column to tags:
create_table "tags", :force => true do |t|
t.string "name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.integer "posts_count", :default => 0, :null => false
end
I've used counter cache before:
reply.rb:
belongs_to :post, :counter_cache => true
But I'm not sure how to do it with this many-to-many association. Any ideas?
Use common :counter_cache option for tags. Despite the fact that it counts Taggings objects, which belongs_to (just one) post, this is what you looking for.
# tagging:
attr_accessible :tag_id, :post_id
belongs_to :post
belongs_to :tag, :counter_cache => :posts_count
validates_uniqueness_of :tag_id, :scope => :post_id
Validator will prevent the creation of several identical tags for the same posts, thus you can avoid duplicate records.
In my Rails 3 project, I have a user model with a self referential join, through the follow model. I want to use this join table to find activity related to the followed user. I have almost everything set up correctly, except that the query generated by the join is totally ignoring the :primary_key option on the join model.
Here is the relevant schema for the relevant models:
create_table "users", :force => true do |t|
t.string "email", :default => "", :null => false
t.string "first_name"
t.string "last_name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "follows", :force => true do |t|
t.integer "user_id"
t.integer "followed_user_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "activities", :force => true do |t|
t.integer "user_id"
t.text "body"
t.datetime "created_at"
t.datetime "updated_at"
end
Here's the associations in the models
class User < ActiveRecord::Base
has_many :follows
has_many :followed_users, :through => :follows
has_many :followed_activities, :through => :follows
has_many :activities
end
class Follow < ActiveRecord::Base
belongs_to :user
belongs_to :followed_user, :class_name => "User"
has_many :followed_activities, :primary_key => :followed_user, :foreign_key => :user_id, :class_name => "Activity"
end
The following work just fine:
u = User.first
u.follows # returns corresponding records from the follows table
u.followed_users # returns all users that u is following
u.followed_users.first.activities # returns all activity records corresponding to the first person the user is following
Follow.first.activities # same as the previous
However, the following just returns an empty array:
u.followed_activities
Here is the sql that is generated from the last statement:
Activity Load (0.2ms) SELECT `activities`.* FROM `activities` INNER JOIN `follows` ON `activities`.user_id = `follows`.id WHERE ((`follows`.user_id = 1))
The reason it isn't working is because it is trying to join use 'follows'.id as the primary key rather than 'follows'.followed_user.
Is this a bug, or do I have to repeat the :primary_key declaration somewhere on the user model? I can't find any mention anywhere in the Rails api, or anywhere else online.
Rails Version: 3.0.7
I've found it intuitive to daisy chain relationships with the 'nested_has_many_through' gem, http://rubygems.org/gems/nested_has_many_through which will be a standard part of rails 3.1 and could give you another tool to tackle the issue here
It will let you do something like this:
class Author < User
has_many :posts
has_many :categories, :through => :posts, :uniq => true
has_many :similar_posts, :through => :categories, :source => :posts
has_many :similar_authors, :through => :similar_posts, :source => :author, :uniq => true
has_many :posts_of_similar_authors, :through => :similar_authors, :source => :posts, :uniq => true
has_many :commenters, :through => :posts, :uniq => true
end
class Post < ActiveRecord::Base
belongs_to :author
belongs_to :category
has_many :comments
has_many :commenters, :through => :comments, :source => :user, :uniq => true
end
This has super-simplified my queries and collections. I hope you find an answer to your problem, it's a tough one!
Justin, you have 2 associations called "followed_activities". sure, they have different context (different models), but I'd like to ask you to try method inside the association block like this:
has_many :followed_users, :through => :follows do
def activities
end
end
I'm trying to create a model for a comic book which has 1 writer and 1 artist. These are both instances of a Person. However how do I show this in my migration?
class CreateComics < ActiveRecord::Migration
def self.up
create_table :comics do |t|
t.column :name, :string
t.column :writer_id, :integer, :null => false
t.column :artist_id, :integer, :null => false
end
end
...
How do I say :writer_id should map to :person_id or is this kind of renaming frowned upon?
You create the mapping in your model class using the class_name and foreign_key option.
belongs_to :writer, :class_name => "Person", :foreign_key => "writer_id"
belongs_to :artist, :class_name => "Person", :foreign_key => "artist_id"
The migration has no knowledge of how you intend to use the tables defined within it. All that information goes in the models.
In short your migration is fine for what you've described will be done. But you need to flesh out the models to define the associations you're thinking of.
class Comic < ActiveRecord::Base
belongs_to :writer, :class_name => "Person"
belongs_to :artist, :class_name => "Person"
end
This allows you to reference the Writer and Artist from a Comic. However, you will probably want to reciprocate the association so that you can easily fetch a comic from a person based on their role in it's production.
class Person < ActiveRecord::Base
has_many :comics_as_writer, :class_name => "Comic", :foreign_key => :writer_id
has_many :comics_as_artist, :class_name => "Comic", :foreign_key => :artist_id
# some times you don't care what a person did for a comic,
# you just want to know what they worked on.
has_many :comics, :finder_sql => "SELECT comics.* FROM comics, people WHERE " +
"`comics`.`writer_id` = `people`.`id` OR " +
" `comics`.`artist_id` = `people`.`id`"
end
With these relationships defined the following is possible:
#comic.artist # => Person with id matching comics.artist_id
#comic.writer # => Person with id matching comics.writer_id
#person.comics_as_writer # => Array of comics where #person.id matches comics.writer_id
#person.comics_as_artist # => Array of comics where #person.id matches comics.artist_id
#person.comics # => Array of comics where #person.id matches comics.writer_id or comics.artist_id