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.
Related
This is a frustratingly simple problem, but it has vexed me.
I have two models, comments and notes. A comment can have one or no notes. Notes have only a text field. They have a shared form with an accepts_nested_attributes_for field.
At the moment, every time a comment is made, it creates an associated blank note. I only want the note to be created if something is entered in the note's text field. I don't want a gagillion blank notes.
I suspect this is a trivial problem, but I am damned if I can solve it.
I tried validates :text, presence: true, on note but, when it fails, it prevents the parent comment from being created, which is not what is wanted. Grrr.
note.rb
class Note < ApplicationRecord
belongs_to :comment
validates :text, presence: true
comment.rb
class Comment < ApplicationRecord
...
has_one :note, dependent: :destroy
accepts_nested_attributes_for :note
comment/_form.html.erb
<%= form.fields_for :note do |note_form| %>
Notes<br />
<%= note_form.text_area :text, cols: 57, rows: 8 %>
<% end %>`
It is, I guess, doing what it is supposed to do. I just don't want it to do that...
A virtual pint to anyone who can help.
New answer.
I had a look at your issue and turns out that accepts_nested_attributes_for [NOTE the link is for rails 6, but its same with rails 5] expects additional parameters. One of which is reject_if, that means you can pass a proc and if it returns false, the nested record will not save.
So, in your case, you can do the followings
# app/models/note.rb
class Note < ApplicationRecord
belongs_to :comment, optional: true
end
# app/models/comment.rb
class Comment < ApplicationRecord
has_one :note, dependent: :destroy
accepts_nested_attributes_for :note, reject_if: proc { |attributes| attributes['text'].blank? }
end
Note that,
accepts_nested_attributes_for :note, reject_if: proc { |attributes| attributes['text'].blank? }
which will return false if the text on note is blank and in turn avoid saving the blank note record.
I've had a quick test and this works in my rails 5.2 app. \o/
You can refer my bare minimum rails app on github if need be sameera207/so-question-58599317
Initial answer, doesn't work
Assuming this is rails 5, Try :inverse_of
UPDATE: optional: true added after the comment by the OP
class Note < ApplicationRecord
belongs_to :comment, inverse_of: :notes, , optional: true
validates :text, presence: true
class Comment < ApplicationRecord
...
has_one :note, dependent: :destroy, inverse_of: :comment
accepts_nested_attributes_for :note
This is a very good writeup about :inverse_of and it talks about accepts_nested_attributes_for, which I think the issue you are having
In my Rails 5 app I have the following setup:
class Client < ApplicationRecord
has_one :address, :as => :addressable, :dependent => :destroy
accepts_nested_attributes_for :address, :allow_destroy => true
end
class Company < Client
has_many :people
end
class Person < Client
belongs_to :company
end
class Address < ApplicationRecord
belongs_to :addressable, :polymorphic => true
validates :city, :presence => true
validates :postal_code, :presence => true
end
A person can belong to a company but doesn't necessarily have to.
Now I want to validate a person's address only if that person doesn't belong to a company. How can this be done?
There might be other approaches as well, but based on my experience, something like this should work.
validates :address, :presence => true, if: -> {!company}
Hope this helps.
Validations can take either an if or unless argument, which accept a method, proc or string to determine whether or not to run the validation.
In your case:
validates :address, presence: true, unless: :company
Update according to comments
The above only takes care of skipping the validation itself, but due to accepts_nested_attributes_for OP still saw errors when trying to persist a missing address. This solved it:
accepts_nested_attributes_for :address, reject_if: :company_id
Nabin's answer is good but wanted to show another way.
validate :address_is_present_if_no_company
def address_is_present_if_no_company
return if !company_id || address
errors.add(:address, "is blank")
end
My question is kind of a generalized one and I don't really know how to provide code for it. But, I was wondering if I could pass variables into a new page containing a form. Basically, the gist of my problem is I have a carpools table, a trips table, and a users table. The relevant models are shown below
users
class User < ActiveRecord::Base
#associations
has_many :users_trips
has_many :carpools
has_many :trips, :through => :users_trips
end
trips
class Trip < ActiveRecord::Base
#including the wysiwyg editor
include Bootsy::Container
#associations
belongs_to :user
has_many :carpools
has_many :users_trips
has_many :users, :through => :users_trips
#make sure trips get ordered from newest to oldest
default_scope -> { order(start_date: :desc) }
#validations
validates :name, presence: true, length: { maximum: 50 }
validates :description, presence: true
validates :start_date, presence: true
validates :end_date, presence: true
end
and carpools
class Carpool < ActiveRecord::Base
#associations
belongs_to :trip
belongs_to :user
#validations
validates :trip_id, presence: true
validates :make, presence: true
validates :user_id, presence: true
validates :model, presence: true
validates :leave_time, presence: true
validates :seats, presence: true
validates :year, presence: true
end
Also, if it helps, i have a many-to-many relationship table called users_trips. I am currently trying to have a user have the ability to sign his/her car up for a trip (hence a carpools table). But when I began to think about the form for the carpool, I realized I was going to have a problem passing the trip_id to the carpool. Assume each of the respective controllers for users/trips are correct. The controller for the carpools has not been made yet.
Also, if it helps, I currently have the idea of, on the trip page, there will be a button that allows the user to sign his/her car up for the trip. That button will lead to the form where the user can fill in the respective fields for their car.
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
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