Rails: How to set up Active Record Associations? - ruby-on-rails

I am relatively new to rails and doing the simplest thing: Associate users and posts. I read this, but what more than this do I need to do to make it work (or is this the only thing)?:
class User < ActiveRecord::Base
has_many :posts, :dependent => :destroy
end
class Post < ActiveRecord::Base
belongs_to :user
end
Update:
I can't make it work. When I make a post with a signed in user, I get false when I do #user.posts.any? in the console. My code:
post.rb
class Post < ActiveRecord::Base
attr_accessible :title, :user_id
belongs_to :user
before_create :default_values
user.rb (I use devise)
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :posts, dependent: :destroy
end
20130320162700_create_posts.rb
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.integer :user_id
t.timestamps
end
end
end

You should make sure to include User's id in the migration that creates the posts table.
In your migration file (in the db/migrate folder you will find a file named like 20130325105934_create_posts.rb)
Inside the file you will find the migration code. Along the other declared attributes add
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
......
t.integer :user_id
......
end
end
It should be enough to make things roll :-)
Inside your code then you can create a new user as
#user = User.new(:login => "my_user", .....)
and then add posts with one of these two ways (there are others two).
post = Post.new(:title => "something", :text => "more of something", :user_id = #user.id)
or
post = Post.new(:title => "something", :text => "more of something")
#user.posts << post

Related

Rails: How to multiple associations between two models

I have the following association between Reviews and Users:
Since I'm using Devise, I kept just a single Users table and identify the roles using client or seller columns (boolean).
So as you can imagine, I need to know the user that made the review and the user being "reviewed".
The first question is: Can I make use of references while creating the migration? I manually created these columns like this: t.integer :client_id, foreign_key: true and t.integer :seller_id, foreign_key: true
The second is: How can I specify the relationship in the models? I did like this has_many :reviews, foreign_key: "client_id" and has_many :reviews, foreign_key: "seller_id" but i'm not sure if it's correct.
Here's the full code of migration:
class CreateReviews < ActiveRecord::Migration[6.0]
def change
create_table :reviews do |t|
t.text :description
t.integer :rating, null: false
t.integer :client_id, foreign_key: true
t.integer :seller_id, foreign_key: true
t.timestamps
end
end
end
The User Model:
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :reviews, foreign_key: "client_id"
has_many :reviews, foreign_key: "seller_id"
end
and the Review model:
class Review < ApplicationRecord
belongs_to :user
end
Rails Version: 6.0.3.2 - Ruby Version: 2.6.6
I see what you are trying to achieve.
First thing first, remove foreign_key: true in your CreateReviews migration because it has no effect, you might want to index those two columns by replacing it with index: true.
Then in your User model have two different has_many associations eg
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :client_reviews, foreign_key: "client_id", class_name: 'Review'
has_many :seller_reviews, foreign_key: "seller_id", class_name: 'Review'
end
Why two different associations? well because when you have two same associations it will always use the last association hence overriding the first one.
You might want to try it in your console and see the output, for your case if you inspect the query you will see that it is using seller_id column to find reviews if you try something like.
user = User.first
p user.reviews.to_sql
Now refactor your Review model to have something like this
class Review < ApplicationRecord
belongs_to :client, foreign_key: :client_id, class_name: 'User'
belongs_to :seller, foreign_key: :seller_id, class_name: 'User'
end
Now you can create client_reviews and seller_reviews and query each one
seller = User.create(name: 'Seller 1)
client = User.create(name: 'Client 1')
seller.seller_reviews.create(description: 'I like your product', client: client)
review = Review.first
p review.client
p review.seller
Hope it helps give the picture of what you can do.

Rails ActiveModel designing belongs_to and has_many with single class

I need some help modeling my models and controller. Here is what I want to achieve:
I want to have a devise user named User (as usual) and a second model named Project. A Project should belong to a single User and at the same time should have many participants. The participants in a project should also be users (with devise registration/login) but the user, that created the project should not be able to participate.
So far, so good. Here comes the tricky part: In my controller I want to be able to write:
def participate
p = Project.find(id: params[:id])
p.participants << current_user unless p.participants.includes?(current_user) && !p.user_id.equal(current_user.id)
if p.save
redirect_back
else
render :project
end
end
This doesn't work because p.participants is not an array and the query (I tried it in rails console) does not check my n:m table.
Here is my current model setup:
class Project < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
belongs_to :user
has_and_belongs_to_many :participants, class_name: "User"
end
class User < ApplicationRecord
before_validation :set_uuid, on: :create
validates :id, presence: true
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_and_belongs_to_many :projects
end
Finally my migrations:
class CreateProjects < ActiveRecord::Migration[6.0]
def change
create_table :projects, id: false do |t|
t.string :id, limit: 36, primary_key: true
t.string :title
t.belongs_to :user, index: true, foreign_key: true, type: :uuid
t.datetime :published_at
t.timestamps
end
end
end
class CreateJoinTableProjectsUsers < ActiveRecord::Migration[6.0]
def change
create_join_table :users, :projects do |t|
t.index :project_id
t.index :user_id
end
end
end
It is better to use has_many: through instead of has_and_belongs_to_many. This allows you to write cleaner code for validation.
Remove has_and_belongs_to_many from User and Project models
Add has_many :through to User and Project models
rails g model UserProject user:references project:references
rails db:migrate
class User < ApplicationRecord
..
has_many :user_projects
has_many :projects, through: :user_projects
..
end
class Project < ApplicationRecord
..
has_many :user_projects
has_many :participants, through: :user_projects, source: 'user'
..
end
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
end
Add validation to UserProject model
class UserProject < ApplicationRecord
belongs_to :user
belongs_to :project
validate :check_participant
private
def check_participant
return if project.participants.pluck(:id).exclude?(user.id) && project.user != user
errors.add(:base, 'You cannot be participant')
end
end
Update participate method
def participate
p = Project.find(id: params[:id])
begin
p.participants << current_user
redirect_back
rescue ActiveRecord::RecordInvalid => invalid
puts invalid.record.errors
render :project
end
end

Undefined method 'about' for nill class

I have two table: User has one Account, Account belongs_to User. When I want to get some value from field 'about' (table Accounts), I have a problem 'undefined method about' for nil:NilClass'. The challenge is to get the list of followers and gain their avatar or information 'about' from another table and output it in View.
My method in controller
def list_of_follower
#followers_id = Follow.select("follower_id ").where("followable_id = ?", current_user)
#followers = User.where("id in (?)", #followers_id)
#followables_id = Follow.select("followable_id").where("follower_id = ?", current_user)
#followables = User.where("id in (?)", #followables_id)
end
View list_of_follower.html.haml
%h1 My followers
- #followers.each do |f|
%ul.list-group
%li.list-group-item
%p=f.name
%p=f.account.about
%h1 I'm follower
- #followables.each do |followable|
%ul.list-group
%li.list-group-item
%p=followable.name
%p=followable.account.about
Create_Accounts.rb
class CreateAccounts < ActiveRecord::Migration
def change
create_table :accounts do |t|
t.belongs_to :user, index: true
t.text :about
t.timestamps null: false
end
end
end
User.rb
class User < ActiveRecord::Base
acts_as_followable
acts_as_follower
acts_as_liker
has_one :account
has_many :posts
has_many :comments
accepts_nested_attributes_for :account
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
def email_required?
false
end
def email_changed?
false
end
validates :login, :email, uniqueness: true
end
Account.rb
class Account < ActiveRecord::Base
belongs_to :user
mount_uploader :avatar, AvatarUploader
end
table User content is displayed without problems (work requests), but the contents of the table associated with it does not displayed, what is the problem?
I think, a problem is not every user has an account.
You can try this:
%p=f.account.try(:about)

Private fields for multiple models, how to make on RoR?

I have 4 models with complex relations. 3 of them should have descriptions, that should be enable only for user who's create. In other words every user has his own description for Group (for example), or for Post, o something else. Let's talk about only one model, because others are very same. What I have:
user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :omniauthable, omniauth_providers: [:vkontakte]
has_and_belongs_to_many :groups
has_many :descriptions
end
group.rb
class Group < ActiveRecord::Base
has_and_belongs_to_many :users
has_many :descriptions, :as => :describable
accepts_nested_attributes_for :descriptions
end
description.rb
class Description < ActiveRecord::Base
belongs_to :user
belongs_to :describable, :polymorphic => true
end
table for descriptions
create_table "descriptions", force: :cascade do |t|
t.integer "user_id" -- belongs_to
t.string "content"
t.integer "describable_id"
t.string "describable_type"
end
How to display the description for group that belongs to current_user (I use devise)? How to build an update form with nested description?
I try to do it, but it's not work. I've ask question about part of problem here.
Why do you have an extra model called description?
Although it's not a problem in itself, you really don't need to have a model just for description.
--
Profile
Instead, you may wish to put the details into a profile model, or simply in the user model (there's nothing wrong with adding extra attributes to a Devise model).
We use a profile model, which gives us the ability to add as many "extra" fields as we want to the user model:
You can set it up like this:
#app/models/user.rb
class User < ActiveRecord::Base
has_one :profile
accepts_nested_attributes_for :profile
before_create :build_profile
delegate :description, :name, to: :profile, prefix: false #-> #user.description
end
#app/models/profile.rb
class Profile < ActiveRecord::Base
belongs_to :user
end
This will allow you to create a single profile per user, have that profile built when the user is created, and then change as many options inside the profile as you wish.

SQLite3::SQLException while trying to set foreign keys?

I have three models: User, Micropost, and Comment. I'm trying to set foreign keys as follows:
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :content
t.timestamps
end
add_index :comments, :micropost_id, :user_id
end
end
But I get this error:
An error has occurred, this and all later migrations canceled:
SQLite3::SQLException: near "user_id": syntax error: CREATE user_id
INDEX "index_comments_on_micropost_id" ON "comments" ("micropost_id")
I understand that Rails insert foreign keys based on belongs_to and has_many declarations in the models. But I have everything set:
comment.rb:
class Comment < ActiveRecord::Base
belongs_to :micropost
belongs_to :user
end
micropost.rb:
class Micropost < ActiveRecord::Base
attr_accessible :title, :content
belongs_to :user
has_many :comments
end
user.rb:
class User < ActiveRecord::Base
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
# Setup accessible (or protected) attributes for your model
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :microposts
has_many :comments
end
Any suggestions to fix this?
If you want to create an index on 2 columns, the syntax is add_index table_name, [column1_name, column2_name], options. You also need to define the columns in the table (ActiveRecord does not add them automatically when you add belongs_to in the model class). So your migration should be
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.text :content
t.integer :micropost_id
t.integer :user_id
t.timestamps
end
add_index :comments, [:micropost_id, :user_id]
end
end

Resources