Im trying to destroy multiple records in my database table where :list column has the same name, however I get an error when I click on Destroy link: Could not find table 'bookmarks_posts', it says the error is in my controller line:
if #bookmarks.destroy_all
Why is it expecting a join table? How can I change that? Also I don't want to destory anything outside the given Bookmarks table. (I am using sqlite3, if that changes anything)
My table migration:
class CreateBookmarks < ActiveRecord::Migration
def change
create_table :bookmarks do |t|
t.string :list
t.references :user, index: true, foreign_key: true
t.references :post, index: true, foreign_key: true
t.timestamps null: false
end
end
end
My controller - destroy and show:
def destroy
#list = Bookmark.find(params[:id])
#bookmarks = Bookmark.where(:list => #list.list)
if #bookmarks.destroy_all
redirect_to bookmarks_url
end
end
def show
#lists = Bookmark.where(user_id: current_user.id).where(post_id: nil)
#list = Bookmark.find(params[:id])
#bookmarks = Bookmark.where.not(post_id: nil).where(list: #list.list)
#posts = Post.where(:id => #bookmarks.map(&:post_id))
end
in my show view I use this:
<%= link_to 'Destroy', #list, method: :delete %>
My models:
class Bookmark < ActiveRecord::Base
has_and_belongs_to_many :users
has_and_belongs_to_many :posts
end
"Why is it expecting a join table?"
Because you have specified a HABTM association between Bookmark and Post models. So when you delete a Bookmark or a Post, it wants to remove any rows in the join table that are referencing the deleted item's ID.
The problem seems to be that you're either specifying the wrong association type in your models, or you've created the wrong migration to support a HABTM association.
For discussion, let's assume your database migration above is correct, eg: you want to store a user_id and post_id in the Bookmarks table. This means that you would have the following associations:
class Bookmark < ActiveRecord::Base
belongs_to :user
belongs_to :post
end
class User < ActiveRecord::Base
has_many :bookmarks
end
class Post < ActiveRecord::Base
has_many :bookmarks
end
If you actually need a HABTM relationship, then you need to do a migration that creates a join table.
One way to figure out what the type of association you need is to remember that if a table has the ID of another model (eg: Bookmarks table has a user_id column), then that is a belongs_to association.
Related
I'm setting up my rails association for an app and I'm not sure if my associations are correct for my use case. The use case is: A product can be added once by a user. Once created other users can then add the same product to their own "feed" within the app. I want to be able to do User.products to list all of a users products. And for products I want to be able to do something like Product.where(id: 2).users to list all of the users that have added the product. I'm currently using a has_and_belongs_to_many association but I think that this is incorrect for what I am trying to achieve?
User model: has_and_belongs_to_many :products
Product model: has_and_belongs_to_many :users
add_index "products_users", ["product_id"], name: "index_products_users_on_product_id"
add_index "products_users", ["user_id"], name: "index_products_users_on_user_id"
Do this:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :created_products, class_name: "Product", foreign_key: :user_id #-> created product
has_and_belongs_to_many :products #-> list of products
end
#app/models/product.rb
class Product < ActiveRecord::Base
belongs_to :user #-> created the product
has_and_belongs_to_many :users #-> list of users
end
You'll need to add the appropriate foreign_key to your User model (user_id in the Product model for the belongs_to :user association) --
--
If your has_and_belongs_to_many relationship is working already, the above should be sufficient.
If not, you need to look up this documentation to see how it works, and then create a join table called products_users (which is populated with the appropriate data):
$ rails g migration CreateProductsUsers
#db/migrate/create_products_users______.rb
class CreateProductsUsers < ActiveRecord::Migration
def change
create_table :products_users, id: false do |t|
t.references :product
t.references :user
end
end
end
$ rake db:migrate
It will allow you to create a single product for a user (IE the Product object will have a direct association with the user who created it). The Product and User models will also be joined with the habtm relationship.
In your controllers, you could use the following:
#config/routes.rb
resources :products #-> url.com/products
scope "profile" do
resources :products, only: :index #-> url.com/profile/products
end
This will allow you to use the following:
#app/controllers/products_controller.rb
class ProductsController < ApplicationController
before_action :product, only: :edit
def index
#products = current_user.products #-> if you're using Devise
end
def edit
#product = current_user.created_products.find params[:id]
end
def new
#product = current_user.created_products.new
end
def create
#product = current_user.created_products.new product_params
#product.save
end
private
def product
redirect_to root_path, notice: "This is not your product" unless current_user.products.exists? params[:id]
end
def product_params
params.require(:product).permit(:x, :y, :z)
end
end
To be able using has_and_belongs_to_many create association, you must create one temperator table container 2 column are product_id, user_id
you can refer
http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/has_and_belongs_to_many
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
Let's say I have a model Movie. Movies can have_many of each other through an intermediary model AssociatedMovie.
How can I specify the nature of the relationship between two Movies? For any given pair of Movies, the relationship may be prequel/sequel, or remake/original, or inspired/inspired by, or related/related, etc. Right now, I can't give the relationships names.
Here's my schema and associations:
create_table "movies", force: true do |t|
t.string "title"
end
create_table "associated_movies", force: true do |t|
t.integer "movie_a_id"
t.integer "movie_b_id"
end
class Movie < ActiveRecord::Base
has_many :movies, :through => :associated_movies
end
class AssociatedMovie < ActiveRecord::Base
has_many :movies
end
And here's the query for setting each Movie's associated Movies:
def movie_associated_movies
associated_movie_ids = AssociatedMovie.
where("movie_a_id = ? OR movie_b_id = ?", self.id, self.id).
map { |r| [r.movie_a_id, r.movie_b_id] }.
flatten - [self.id]
Movie.where(id: associated_movie_ids)
end
I think I'd probably have to add movie_a_type and movie_b_type attributes to AssociatedMovie. But I'm not sure how I could specify which Movie is attached to which type.
Anyone have any ideas?
You're already half-way there with has_many :through (using an intermediary model) - this allows you to add as many extra attributes as you like.
I think your problem is down to your relationships, which I'll explain below:
#app/models/movie.rb
class Movie < ActiveRecord::Base
has_many :associated_movies, foreign_key: :movie_a_id
has_many :movies, through: :associated_movies, foreign_key: :movie_b_id
end
#app/models/associated_movie.rb
class AssociatedMovie < ActiveRecord::Base
belongs_to :movie_a, class_name: "Movie"
belongs_to :movie_b, class_name: "Movie"
end
The above will give you access to:
#movie = Movie.find params[:id]
#movie.associated_movies #-> collection of records with movie_a and movie_b
#movie.movies #-> all the movie_b objects
--
Because you're using has_many :through, rather than has_and_belongs_to_many, you'll be at liberty to add as many attributes to your join model as you need:
To do this, you just have to add a migration:
$ rails g migration AddNewAttributes
#db/migrate/add_new_attributes_________.rb
class AddNewAttributes < ActiveRecord::Migration
def change
add_column :associated_movies, :relationship_id, :id
end
end
$ rake db:migrate
-
... I apologize if this is a little off-course; however I would actually add a separate model for your relationships (considering you have them predefined):
#app/models/relationship.rb
class Relationship < ActiveRecord::Base
#columns id | movie_a_type | movie_b_type | created_at | updated_at
has_many :associated_movies
end
#app/models/associated_movie.rb
class AssociatedMovie < ActiveRecord::Base
belongs_to :movie_a, class_name: "Movie"
belongs_to :movie_b, class_name: "Movie"
belongs_to :relationship
delegate :movie_a_type, :movie_b_type, to: :relationship
end
This may seem a little bloated (it is), but it will provide extensibility.
You'll have to add another table, but it will ultimately provide you with the ability to call the following:
#movie.associated_movies.each do |associated|
associated.movie_a #-> current movie
associated.movie_b #-> related movie
associated.movie_a_type #-> "Original"
associated.movie_b_type #-> "Sequel"
end
You'd then be able to pre-populate the Relationship model with the various relationships you'll have.
I can add to the answer as required.
In Ruby on Rails 4, how do you create a many-to-many relationship inside a relationship model for a friends list such as Facebook using the has_many :through ... syntax ?? I'm a newbie and currently learning Ruby on Rails 4. I have looked at this link.
But still have a hard time grasping it.
you will need a join table that references both sides of the relations
let us say you have an relation Post and another relation Category with a many to many relationship between them you need a join table to be able to represent the relationship.
migration for a join table would be
class CreateCategoriesPosts < ActiveRecord::Migration
def change
create_table :categories_posts do |t|
t.integer :category_id
t.integer :post_id
t.timestamps
end
add_index :categories_posts, [:category_id, :post_id]
end
end
and in the models/post.rb
Class Post < ActiveRecord::Base
has_and_belongs_to_many :categories
end
and in the models/category.rb
Class Category < ActiveRecord::Base
has_and_belongs_to_many :posts
end
more here:
http://guides.rubyonrails.org/association_basics.html#the-has-and-belongs-to-many-association
I think #RAF pretty much nailed it. But to use the OP's example:
class User < ActiveRecord::Base
has_and_belongs_to_many :users_list
end
class UsersList < ActiveRecord::Base
has_and_belongs_to_many :users
end
Although at first it might seem like a User should have only one list of friends (UsersList), that might not always be the case. Think of types within the UserList model, such as: 'close friends', 'work friends', 'all friends' for example.
My advice: dig into the Rails guides. This is a concept worth learning and truly understanding (which I'm still doing :).
many-to_many relationships are a simple concept, but complex when using the database because of the way databases work. A person could have 1 to N different friends, which means that a single entry for a database would need a dynamic amount of memory for each entry, which in the db world is a no-no. So instead of creating a list of friends you would have to make a table that represents the links between friends, for example:
friendship.rb
class Friendship < ActiveRecord::Base
belongs_to :friend, foreign_key: 'friend_A' # this entry has a field called 'friend_A'
belongs_to :friend, foreign_key: 'friend_B' # this entry has a field called 'friend_B'
end
These links will represent your network of friends. However, as the two previous answers have mentioned, Rails has some nifty magic, "has_and_belongs_to_many", which will do this for you.
NOTICE: The problem here is that in my StatusesController, in the index action, the #relationship object only gets the statuses of all your friends, but does not get your own statuses. Is there a better way of approaching this? I am trying to create a view to view all statuses of users that are your friends, and your own statuses too, and so far, I can't seem to figure out how to order it chronologically, even if in my status model, i included "default_scope -> { order(created_at: :desc) } ". Any advice would be deeply appreciated
class User < ActiveRecord::Base
has_many :relationships
has_many :friends, :through => :relationships
has_many :inverse_relationships, class_name: 'Relationship', foreign_key: 'friend_id'
has_many :inverse_friends, through: 'inverse_relationships', :source => :user end
#
class Relationship < ActiveRecord::Base
# before_save...
belongs_to :user
belongs_to :friend, class_name: 'User'
end
#
class RelationshipsController < ApplicationController
def friend_request
user_id = current_user.id
friend_id = params[:id]
if Relationship.where( user_id: user_id, friend_id: friend_id, accepted: false).blank?
Relationship.create(user_id: user_id, friend_id: friend_id, accepted: false)
redirect_to user_path(params[:id])
else
redirect_to user_path(params[:id])
end
end
def friend_request_accept
# accepting a friend request is done by the recipient of the friend request.
# thus the current user is identified by to_id.
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
if Relationship.exists?(relationship) and relationship.accepted == false
relationship.update_attributes(accepted: true)
end
redirect_to relationships_path
end
def friend_request_reject
relationship = Relationship.where(user_id: params[:id], friend_id: current_user.id).first
relationship.destroy
redirect_to relationships_path
end
################################
def index
#relationships_pending = Relationship.where(friend_id: current_user.id, accepted: false)
end
end
#
class StatusesController < ApplicationController
def index
#status = Status.new
#relationship = Relationship.where('friend_id = ? OR user_id = ?', current_user.id, current_user.id).
where( accepted: true)
end
def new
#status = Status.new
end
end
#
I want my users to have many skills. I do have a users and skills database table.
I used has_many_and_belongs_to association in user.rb
has_many :skills
which I am not sure if its correct. And in skill.rb
has_and_belongs_to_many :users
I also created a migration like that:
def change
create_table :user_skills do |t|
t.belongs_to :users
t.belongs_to :skills
end
Is this correct?
So IF this is correct, how do I add new skills to my user? What is the general approach?
What I thought of,
In my users controller on update action I will be updating user's skill and update the user_skills table.
How is this done?
Also How do I iterate through my user_skills table for a specific user? (in view)
Any guidance, resource, tip will be great help for me as its the first time i do something like this in Rails.
Thanks
In Rails, most would prefer to use has_many :through over habtm associations. Here's a guide on how to use it: ActiveRecord guide.
A has_many through association for users and skills would look like this in your relevant models:
class User < ActiveRecord::Base
has_many :user_skills
has_many :skills, through: :user_skills
end
class UserSkill < ActiveRecord::Base
belongs_to :user
belongs_to :skill
end
class Skill < ActiveRecord::Base
has_many :user_skills
has_many :users, through: :user_skills
end
Your migration would look like:
def change
create_table :user_skills do |t|
t.references :user, index: true
t.references :skill, index: true
end
end
The indexes in the migration are for faster look-ups for using the reference_id. It's advisable to do that for all references.
To add new skills to your user, you can refer to this SO answer.
To update a user's skill, you could do this:
#skill = #user.skills.find(params[:skill_id])
#skill.update(skill_params)
To create a user's skill, you could do this:
#user.skills.create(skill_params)
To add a skill to user, you could do this in your update action:
#user.update(user_params)
#app/views/users/edit.html.erb
<%= f.select :skill_ids, Skill.all.collect {|x| [x.name, x.id]}, {}, :multiple => true %>
When working with has_many through, you won't need to go through the user_skills table to get a specific user. You would, however, might need to get a specific user from a skill. To do this:
#skill.users.find(user_id)
Hope that helps!
If you set user to have_and_belong_to_many :skills also then this will work.
To create a new skill for a user do
user.skills.create!{...}
or to associate an existing skill with a user do
user << skill
"In my users controller on update action I will be updating user's skill and update the user_skills table. How is this done?"
user = User.find params[:id]
skills = user.skills
You can then do what you like to users skills
"Also How do I iterate through my user_skills table for a specific user? (in view)"
user.skills.each do |skill|
...
end
for more on HABTM association see http://guides.rubyonrails.org/association_basics.html#has-and-belongs-to-many-association-reference
Forgive me If I get it wrong, try to fill in the gaps but I think you want something that looks like this.
controller
def index
#to fetch all skills associated to users (add where u.id=? to fetch for a single user)
#users = User.select("u.name, s.name").
from("users u, skills s, users_skills us").
where("u.id = us.user_id").
where("s.id = us.skill_id")
end
def new
#user = User.new
#skills = Skill.all
end
def create
#user = User.new(params[:user])
...............................
end
in the create form
<%= form_for #user do |f| %>
<%= f.collection_select(:skill_ids, #skills,:id,:name)%>
<%= f.submit "Save" %>
<% end %>
In order to use HABTM you need a join table named either users_skills or skills_users (not sure it matters). It should contain two integer columns named user_id and skill_id. You should create indices for them as well. In your User model you want has_and_belongs_to_many :skills and in your Skill model you want has_and_belongs_to_many :users.
You need has_and_belongs_to_many on both sides of the realtionship.
class User
has_and_belongs_to_many :skills
class Skill
has_and_belongs_to_many :users
Alternatively (and better, in my opinion) would be to use has_many :through:
class User
has_many :user_skills
has_many :skills, through: :user_skills
class Skill
has_many :user_skills
has_many :users, through: :user_skills
I have two models
Post
has_many :comments
Comment
belongs_to :post
When I want display a list of posts and it's comment count. I usually include comments in the post like this .
Post.find(:all,:include => :comments)
To display a number of comment for post.
post.comments.size
Can I create a has_many relation which return count of comments ?
has_one :comments_count
Can I include this relationship like this ?
Post.find(:all,:include => :comments_count)
Rails has a counter cache which will automatically update a columns value based on the count of associated items. This will allow you to include the count when you return the posts object. Just add it to the belongs_to on comment.
Comment
belongs_to :post, :counter_cache => true
You'll need to add a new integer column to posts for the counter:
class AddCounterCacheToPosts < ActiveRecord::Migration
def self.up
add_column :posts, :comments_count, :integer
end
end
To answer you question, yes you can; but this is not the most efficient way to do it. Normally you add a column to Post called comments_count and you updated that column every Comment CRUD action.
Add the column:
rails g migration add_comment_count_to_post
Then in that migration add the following line:
add_column :posts, :comments_count, :integer, :default => 0
Then there are two way to handle it from here.
The first is a custom before_save and before_destroy in Comment model.
app/models/comment.rb
class Comment << ActiveRecord::Base
belongs_to :post
before_save :update_comments_count
before_destroy :update_comments_count
def update_comment_count
post.update_attribute(:comment_count, post.comments.count)
end
end
The second way is to use Rails custom helper for this:
app/models/comment.rb
class Comment << ActiveRecord::Base
belongs_to :post, :counter_cache => true
end