I just spent an hour and half troubleshooting my app trying to figure out why
User.articles
was throwing errors. The models looked okay:
class User < ActiveRecord::Base
has_secure_password
has_many :articles
validates :email, presence: true, uniqueness: true
validates :name, presence: true, uniqueness: true
end
and
class Article < ActiveRecord::Base
validates :title, presence: true, uniqueness: true
validates :content, presence: true
belongs_to :user
has_many :article_categories
has_many :categories, through: :article_categories
end
In the end, the problem was that the migration for articles did not have the line
t.belongs_to :user
Along the way, I also tried putting this line in the user migration
t.has_many :articles
but it threw an error.
Why do migrations only need the belongs_to side of the relationship and not has_many?
Migrations provide a .belongs_to because that actually defines a column, the foreign key linking the tables. On the other hand, has_many doesn't actually do anything to the table itself; including it in a migration would have absolutely non value or effect.
The confusion is on your part. The migrations doesn't have to have has many and belongs to because it's already defined in the models. For the relationship to work, generate a migration that adds user_id(integer) column to Articles table.
i.e
rails generate migration add_user_id_to_articles
class AddUserIdToArticles < ActiveRecord::Migration
def change
add_column :articles, :user_id, :integer
end
end
Related
I'm experiencing some issues when trying to add a has_one relation to existing model. I ran
rails g migration AddPracticeToLesson practice:references
and generated the following migration
class AddPracticeToLesson < ActiveRecord::Migration[7.0]
def change
add_reference :lessons, :practice, null: true, foreign_key: true
end
end
and I have the lesson model as
class Lesson < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :slugged
acts_as_list
belongs_to :course
has_many :sections, -> { order(position: :asc) }, as: :sectionable, dependent: :destroy
has_one :quiz, through: :sections
has_one :practice # ADDED THIS
has_one_attached :cover_image
end
to which I added the line has_one :practice. I also added belongs_to :lesson, optional: true for the practice model which now looks like
class Practice < ApplicationRecord
extend FriendlyId
friendly_id :title, use: :slugged
enum status: [:unpublished, :published]
has_many :sections, -> { order(position: :asc) }, as: :sectionable, dependent: :destroy
has_one :quiz, through: :sections
has_one_attached :cover_image
belongs_to :lesson, optional: true
end
but now upon running #lesson.practice will result in an error stating
PG::UndefinedColumn: ERROR: column practices.lesson_id does not exist
which I don't understand. Why is it trying to access practices.lesson_id and not just find the practice with the lesson.practice_id?
The problem is most likely that you have defined the reference on the wrong table.
From https://guides.rubyonrails.org/v7.0/association_basics.html#the-has-one-association:
A has_one association indicates that one other model has a reference to this model. That model can be fetched through this association.
What this means in practice is that your table practice (or practices whichever is the real table name) should have a reference to lessons.
In your migration, change
add_reference :lessons, :practice, null: true, foreign_key: true
to
add_reference :practice, :lessons, null: true, foreign_key: true
If you want to use the Rails generators,
rails g migration AddLessonRefToPractices lesson:references
seemed to create the correct migration
class AddLessonRefToPractices < ActiveRecord::Migration[7.0]
def change
add_reference :practices, :lesson, foreign_key: true
end
end
I took the naming convention for the migration from https://guides.rubyonrails.org/v7.0/active_record_migrations.html#creating-a-standalone-migration (scroll down to where they define the AddUserRefToProducts migration, almost until section 2.2).
Naming the migration matters, at least in some cases, as Rails tries to conclude from the name and given attributes what you want to do.
Put another way, whenever you use the association belongs_to the reference field must always be defined in that table (https://guides.rubyonrails.org/v7.0/association_basics.html#the-belongs-to-association). As your Practice model defines belongs_to :lesson, the reference field must be defined in the table for Practice.
I have the models Account and User. Both models have an email attribute.
An Account has_many :users and a User belongs_to :account
I would like to validate the uniqueness of the email accross both models when an Account is being created so the Account email is invalid if it's taken by a User (since the account email later becomes the admin user email).
I added a scope to the email constraint in the Account model but it is not working (the form is not being rejected).
Account model:
has_many :users
validates :email, uniqueness: { scope: :users, case_sensitive: false }
What is the correct way to implement this? Do I need to add an index to the DB?
This is an alternative method based on a separate table and a polymorphic assocation:
class CreateEmailAddresses < ActiveRecord::Migration[6.1]
def change
create_table :email_addresses do |t|
t.string :email, unique: true
t.references :entitity, polymorphic: true
t.timestamps
end
add_index :email_addresses, [:entity_type, :entity_id], unique: true
end
end
class EmailAddress < ApplicationRecord
validates_uniqueness_of :email
validates_uniqueness_of :entity_id, scope: :entity_type
belongs_to :entity, polymorphic: true
end
class User < ApplicationRecord
has_one :email_address,
as: :entity,
dependent: :destroy
delegate :email, to: :email_address
accepts_nested_attributes_for :email_address
end
class Account < ApplicationRecord
has_one :email_address,
as: :entity,
dependent: :destroy
delegate :email, to: :email_address
accepts_nested_attributes_for :email_address
end
It avoids having to restructure your domain or create a user simply to create a email but will cause issues with authentication libraries such as Devise as well as lacking a real foreign key to guarentee referential integrity which can lead to orphaned records.
IMO not a great solution as it most likely will create as many problems as it solves.
An application level validation doesn't actually guarentee the uniqueness of your data - it merely catches a lot of the cases where users try to input duplicates and provides feedback.
So while you could naively implement a custom validation that queries both tables it won't even survive the race condition caused by a double clicking senior citizen or any insertion method that circumvents validations.
Since there is no way (AFAIK) to create indices across tables so you might want to just restructure your domain and add an "owner" (call it whatever you want) to the accounts table:
class AddOwnerToAccounts < ActiveRecord::Migration[6.1]
def change
add_reference :accounts, :owner, null: false, foreign_key: { to_table: 'users' }
end
end
class Account < ApplicationRecord
has_many :users
belongs_to :owner,
class_name: 'User',
inverse_of: :owned_accounts
delegate :email, to: :owner
end
class User < ApplicationRecord
belongs_to :account
has_many :owned_accounts,
class_name: 'Account',
foreign_key: :owner_id,
inverse_of: :owner
end
This an only be achieved with a custom validation method.
validate :email_unique_for_account_and_user
private
def email_unique_for_account_and_user
if User.where(email: email).where.not(id: id).exists? ||
Account.where(email: email).where.not(id: id).exists?
errors.add(:email, :taken)
end
end
IMO it is not possible to define a unique index over multiple database tables.
I'm creating a blog where articles can be responses to other articles. Articles can also be part of groups. However articles do not have to be in a group or be a response to another article.
I'm attempting to follow the Rails docs to create articles as self-joined records.
I created user, group and article scaffolds:
bin/rails g scaffold user username:string email:string birthday:date
bin/rails g scaffold group name:string user:references
bin/rails g scaffold article user:references parent:references title:string subheading:string body:text pub_date:datetime group:references hidden:boolean prompt:boolean
I'm trying to use allow_nil in the model validation.
class Article < ApplicationRecord
belongs_to :user
belongs_to :parent, class_name: "Article"
has_many :replies, class_name: "Article", foreign_key: "parent_id"
belongs_to :group
validates :parent, length: {minimum: 1}, allow_nil: true
validates :group, length: {minimum: 1}, allow_nil: true
end
However when I run the db:seed:
user1 = User.create(username: "pete01",email: "pete01#gmail.com",
birthday:"1980-01-30")
article1 = Article.create!(user:user1, parent:nil, title:"My First Article",
subheading:"This is important", body:"The body of my first article",
pub_date:"2015-12-26", group:nil, hidden:false, prompt:false)
I get this error:
ActiveRecord::RecordInvalid: Validation failed: Parent must exist, Group must exist
Is there somewhere else where I should be telling Rails it does not need to validate Group and Parent?
Solution is to find the file new_framework_defaults.rb, change this to false:
Rails.application.config.active_record.belongs_to_required_by_default = false
#app/models/article.rb
class Article < ActiveRecord::Base
belongs_to :parent, class_name: "Article"
belongs_to :group
validates :parent, presence: true, allow_nil: true
validates :group, presence: true, allow_nil: true
end
Several problems for you:
You're validating parent & group -- these are associative objects.
Your error says "[Object] must exist", which means your validation works -- Rails cannot find a "nil" association (it's expecting objects).
What you should have is either to validate parent_id & group_id, or to validate the presence of the associative object with something like presence.
I've included the following validations I would use:
validates :parent, presence: true, allow_nil: true
validates :group, presence: true, allow_nil: true
You could also try:
validates :parent_id, length: { minimum: 1 }, allow_nil: true
validates :group_id, length: { minimum: 1 }, allow_nil: true
Articles can also be part of groups
You'll probably want to use a has_and_belongs_to_many association for it then:
#app/models/article.rb
class Article < ActiveRecord::Base
has_and_belongs_to_many :groups
end
#app/models/group.rb
class Group < ActiveRecord::Base
has_and_belongs_to_many :articles
end
You'll need a join table called articles_groups with the columns article_id & group_id:
You can create the migration as follows:
$ rails g migration CreateArticlesGroups
# db/migrate/create_articles_groups__________.rb
class CreateArticlesGroups < ActiveRecord::Migration
def change
create_table :articles_groups, id: false do |t|
t.belongs_to :article, index: true
t.belongs_to :group, index: true
end
end
end
$ rake db:migrate
This will allow you to populate the associative objects like this:
#article = Article.find params[:article_id]
#group = Group.find params[:id]
#article.groups << group
This is most likely to be the result of migrating to Rails 5, where the default for belongs_to relations changed. See this answer for details,
allow_nil: true is an option to a validator, not to the validates method. You are using the length: key which will call the LengthValidator with the {minimum: 1} hash as arguments (similar to using validates_length_of).
E.g., use
validates :parent, length: {minimum: 1, allow_nil: true}
instead of
validates :parent, length: {minimum: 1}, allow_nil: true
Note: If using multiple validators, you will need to specify allow_nil for each of them.
Try to remove your validations. Also check your migration for Article. Maybe you have null: false there for group_id and parent_id
class Article < ApplicationRecord
belongs_to :user
belongs_to :parent, class_name: "Article"
has_many :replies, class_name: "Article", foreign_key: "parent_id"
belongs_to :group
end
I'm trying to understand how to implement one-to-many relationship through reference table. I'm looking on this guide I though just write on one model has_many so it will be one-to-many but I'm not completely sure (I wrote something but it's not working). Anyway I'm doing this to save for me a table, and doing it right and not just working.
The model is as following:
Microposts, :id, :content
Tag, :id, :name
Tag_microposts, :tag_id, :micropost_id
Article, :id, :text
Article_microposts, :article_id, :micropost_id
I can do two microposts tables with the id of the tag/article. But I think doing it like this is better and righter.
In the end what's interesting me is to get microposts through tag model. So in the tag_controller be able to do:
def index
#tag = Tag.find(params[:id])
#microposts = #tag.microposts
end
Some code:
class Tag < ActiveRecord::Base
...
has_many :tag_microposts, foreign_key: :tag_id, dependent: :destroy
has_many :microposts, through: :tag_microposts, source: :micropost
...
end
class TagMicropost < ActiveRecord::Base
validates :tag_id, presence: true
validates :micropost_id, presence: true
end
class Micropost < ActiveRecord::Base
belongs_to :user
belongs_to :tag
validates :content, presence: true, length: {minimum: 10, maximum: 250}
validates :user_id, presence: true
end
May I ask why you are using a reference table for this? You can do a one-to-many association with only the two models you are associating. If you want to associate a tag with many posts you can just do this in your models.
class Tag < ActiveRecord::Base
has_many :microposts
end
class Micropost < ActiveRecord::Base
belongs_to :tag
#added in edit
belongs_to :article
validates :content, presence: true, length: {minimum: 10, maximum: 250}
validates :user_id, presence: true
end
This should let you do:
#tag.microposts
Just fine. The forgien key will be stored in your Micro post model so be sure to add the column. You can use an active migration for that. Call the column tag_id, rails should take care of the rest.
Edit*
A added the article association. The problem you raised is only relevant if you need to get the article/tag given the micropost. The code to do that is still pretty simple with this model.
#tag ||= #micropost.tag
Using the conditional assignment operator like this will only assign #tag if the association is there. If you give me more specifics about how these models will be used I can give you a better answer.
I'm having trouble validating a model from a has_many through association. Below are the relevant models:
Broadcast Model
class Broadcast < ActiveRecord::Base
attr_accessible :content,
:expires,
:user_ids,
:user_id
has_many :users, through: :broadcast_receipts
has_many :broadcast_receipts, dependent: :destroy
validates :user_id, presence: true
validates :content, presence: true
end
Broadcast Receipt Model
class BroadcastReceipt < ActiveRecord::Base
belongs_to :broadcast
belongs_to :user
attr_accessible :user_id, :cleared, :broadcast_id
validates :user_id , presence: true
validates :broadcast_id , presence: true
end
There is also an association with Users that have_many broadcasts receipts through broadcast receipts.
The problem appears to be with the following line:
validates :broadcast_id , presence: true
Whenever I try to create a Broadcast, I get a rollback with no error messages given. However, when removing the above line, everything works as expected.
This looks like a problem with the Broadcast not being saved before the Broadcast Receipts are being created.
Is there any way I'd be able to validate the broadcast_id is set on the receipt model?
This appears to be the same issue discussed here: https://github.com/rails/rails/issues/8828, which was solved by adding :inverse of to the has_many associations to the join model.
There might be some problem in your code structuring. You could give this version a try.
class Broadcast < ActiveRecord::Base
# I assume these are the recipients
has_many :broadcast_receipts, dependent: :destroy
has_many :users, through: :broadcast_receipts
# I assume this is the creator
validates :user_id, :content, presence: true
attr_accessible :content, :expires, :user_id, :user_ids
end
class BroadcastReceipt < ActiveRecord::Base
belongs_to :broadcast
belongs_to :user
# You should be able to validate the presence
# of an associated model directly
validates :user, :broadcast, presence: true
attr_accessible :cleared
end