How to use has_many through association with additional attributes - ruby-on-rails

Looking at the documentation on associations, I've managed to set up my classes to use has_many, :through. However, I can't seem to find any example on how to actually use the association.
My User model has_many :attendees and has_many :events, through: :attendees. My Event model has_many :attendees and has_many :users, through: :attendees.
Attendee model:
class Attendee < ActiveRecord::Base
attr_accessible :status
validates_inclusion_of :status, in: [:performing, :invited, :going, :maybe]
belongs_to :user
belongs_to :event
def status
read_attribute(:status).to_sym
end
def status=(value)
write_attribute(:status, value.to_s)
end
end
I tried using the following code:
at1 = Attendee.new(user: u1, event: e1)
at1.status = :invited
at1.save
Unsurprisingly, I get a mass assignment error with user and event. It seems besides the point to declare attr_accesible for user and event though. how would I use the association here, and set the custom status attribute?

It's absolutely not beside the point to declare attr_accessible. That's what you're missing.
Keep in mind attr_accessor is something else. For more on this, check: Difference between attr_accessor and attr_accessible
Also keep in mind that attr_accessible has been deprecated in Rails 4 in favor of Strong Parameters, effectively moving all whitelisting of attributes from the models to the controllers.

Related

How to prevent nested create failing with scoped has_many in ActiveRecord?

A report_template has_many report_template_columns, which each have a
name and an index attribute.
class ReportTemplateColumn < ApplicationRecord
belongs_to :report_template
validates :name, presence: true
end
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) }, dependent: :destroy
accepts_nested_attributes_for :report_template_columns, allow_destroy: true
end
The report_template_columns need to be ordered by the index column. I'm applying this with a scope on the has_many association, however doing so causes the following error:
> ReportTemplate.create!(report_template_columns: [ReportTemplateColumn.new(name: 'id', index: '1')])
ActiveRecord::RecordInvalid: Validation failed: Report template columns report template must exist
from /usr/local/bundle/gems/activerecord-5.1.4/lib/active_record/validations.rb:78:in `raise_validation_error'
If I remove the scope the same command succeeds.
If I replace the order scope with where scope that command fails in the same way, so it seems to be the presence of the scope rather than the use of order specifically.
How can I apply a scope to the has_many without breaking the nested creation?
I believe you need the :inverse_of option added to the has_many association.
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) },
dependent: :destroy, inverse_of: :report_template
end
The api states that :inverse_of:
Specifies the name of the belongs_to association on the associated object that is the inverse of this has_many association. Does not work in combination with :through or :as options. See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
I also like how the cocoon gem words their reason for using it:
Rails 5 Note: since rails 5 a belongs_to relation is by default required. While this absolutely makes sense, this also means associations have to be declared more explicitly. When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations. There are two ways: either declare the belongs_to as optional: false, but the cleanest way is to specify the inverse_of: on the has_many. That is why we write: has_many :tasks, inverse_of: :project

How to validate presence of associated fields in Ruby on Rails

I have tables applications and business.
Applications
has_many :business
Business
belongs_to :applications
If I will create an Application, I should have at least one Business. I used link_to_add in the same form where I create applications.
I used validate_association :applciations but it didn't work.
You may be better to use validates_associated:
#app/models/business.rb
class Business < ActiveRecord::Base
belongs_to :application
validates :name, :other, param, presence: true
end
#app/models/application.rb
class Application < ActiveRecord::Base
has_many :businesses
validates_associated :businesses
end
This gives you the ability to check the validity of any associated data you pass through the model. However, this will not determine if there is at least one associated business passed through your model.
--
Numerical Validation
You'll want to look at the following
The reject_if method in Rails will not give you the ability to check the number of associated items which have been sent. This will have to be custom coded, which the author of the above post has addressed (in 2012) by setting a custom constant:
#app/models/company.rb
class Company < ActiveRecord::Base
OFFICES_COUNT_MIN = 1
validates :name, presence: true
validate do
check_offices_number
end
has_many :offices
accepts_nested_attributes_for :offices, allow_destroy: true
private
def offices_count_valid?
offices.count >= OFFICES_COUNT_MIN
end
def check_offices_number
unless offices_count_valid?
errors.add(:base, :offices_too_short, :count => OFFICES_COUNT_MIN)
end
end
end
I have not tested this myself, but to explain how it works, you'll basically have a custom validator, which allows you to check whether the number of associated objects is less than or equal to the CONSTANT you assign in the class.
You can, of course, achieve this without a constant, but the above example should demonstrate how you're able to create the functionality where at least 1 associated item should be sent
Not sure if this is what you meant but you could do the following in your models e.g.
has_many :businesses, dependent: :destroy
validates :title, presence: true
and in the other model:
belongs_to :application
validates :name, presence: true
You can use validates_presence_of
Validates that the specified attributes are not blank (as defined by Object#blank?), and, if the attribute is an association, that the associated object is not marked for destruction. Happens by default on save.
Example:
Applications
has_many :businesses
validates_presence_of :business
Business
belongs_to :applications
Update:
I think you will be better to using accepts_nested_attributes_for
Nested attributes allow you to save attributes on associated records through the parent. By default nested attribute updating is turned off and you can enable it using the accepts_nested_attributes_for class method. When you enable nested attributes an attribute writer is defined on the model.
app/models/application.rb
Class Application < ActiveRecord::Base
has_many :businesses
accepts_nested_attributes_for :businesses, reject_if: :all_blank
end
#app/models/business.rb
Class Business < ActiveRecord::Base
belongs_to :application
end
This will give you the ability to call the reject_if: :all_blank method -
:reject_if
Allows you to specify a Proc or a Symbol pointing to a method that checks whether a record should be built for a certain attribute hash. The hash is passed to the supplied Proc or the method and it should return either true or false. When no :reject_if is specified, a record will be built for all attribute hashes that do not have a _destroy value that evaluates to true. Passing :all_blank instead of a Proc will create a proc that will reject a record where all the attributes are blank excluding any value for _destroy.

Data modeling of Grandparent, Parent, and Child relationships in Rails

Is it a bad practice to set a model (table) association between both parent and child AND grandparent and child? For example, if I want to easily query a user's projects or a user's tasks, is the following setup recommended? If so, is there a good way to ensure both foreign keys always point to the same user?
Any help will be greatly appreciated.
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
has_many :projects
has_many :tasks
end
class Project < ActiveRecord::Base
attr_accessible :name, :status
belongs_to :user
has_many :tasks
end
class Task < ActiveRecord::Base
attr_accessible :name, :status
belongs_to :user
belongs_to :project
end
There is nothing wrong with what you are trying to do, in fact rails has already predefined some association methods to help you. Just a couple of modifications and you are good to go
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name
has_many :projects
has_many :tasks, through: :projects
end
By adding the through: :projects to your tasks now means you can access all of a users tasks like so
$user = User.first #or whatever
$user.tasks
=> [AR array of all tasks]
You can play around with it in irb. In addition you don't need belongs_to :user in your Task model. It's already taken care of.
Look at section 2.4 for more details
EDIT: I have made the assumption (based upon your description) that a task belongs to a project, and a project belongs_to a user, and that user's don't have tasks directly, but through projects. If that was wrong, let me know and we'll figure it out from there.

Validation in Polymorphic association with validates_associated

I have these models: Post, TextPost and PhotoPost and I use polymorphic association.
You can find these three models here or look below.
post.rb
class Post < ActiveRecord::Base
belongs_to :user
default_scope { order ("created_at DESC")}
belongs_to :content, polymorphic: true
has_reputation :votes, source: :user, aggregated_by: :sum
end
photo_post.rb
class PhotoPost < ActiveRecord::Base
has_attached_file :image, styles: {
post: "200x200>"
}
end
text_post.rb
class TextPost < ActiveRecord::Base
attr_accessible :body
end
What I want, is to validate the presence of :body and :image when a users submits a text_post or photo_post respectively. So far, I found out that I have to use validates_associated.
The full project can be found on Github.
I have experiment a lot to find out how validates_associated works and searched for examples online but I don't have a clue what's going on.
(If you need any more info please let me know)
Any help/guidance is greatly appreciated.
I think for starters there needs to be some form of association between these 3 models here. You have Post, PhotoPost and TextPost. As a post can has a type of post. Also remember that type in rails is a reserved word. But anyways your models should be set out as followed:
class Post< ActiveRecord::Base
belongs_to :postable, polymorphic: true
end
class PhotoPost < ActiveRecord::Base
has_many :posts, as: :postable
end
class TextPost < ActiveRecord::Base
has_many :posts, as: :postable
end
As from looking at the snippet you provided it doesn't show any set up of polymorphic association. With regards to validating the association of the polymorphic association you might want to take a read of this answer: Validate presence of polymorphic parent. Furthermore also might want to read this article: Validating a polymorphic association for a new record . The purpose of the validate_associated validation helper simply ensures that the association between the two records are valid or as they put it works. In your case if you were to use that validation helper. This would not be what you'd want because all that would be doing is validating the association between the two models. See the second link I provided this is what I believe you are after.
Also in your controller you need to build the relationship between the models. So in your controller I think you could have something like this inside your new action:
def new
#Post = Postfind(params[:id])
#PhotoPost = #post.photoposts.build
#TextPost = #post.textposts.build
end

Validate presence of foreign key on associated objects and using collection.build

I'm having the following models:
class Price < ActiveRecord::Base
belongs_to :article, inverse_of: :prices
validates :article_id, presence: true
end
class Article < ActiveRecord::Base
has_many :prices, dependent: :destroy, inverse_of: :article
end
The code when creating them raises validation error when saving (Prices is invalid):
article = Article.new
article.prices.build( { amount: 55.0 } )
article.save! #=> Validation failed: Prices is invalid
So Rails isn't smart enough to save the parent object (Article) before the child objects (Prices) so article_id can be assigned to the price before it is saved.
How do you use validations on the foreign key when using the build function?
It seems like a pretty standard scenario that should work?
(I know you can use database level constraints, but I'm asking about application level validations here)
In Rails way you can do like this
class Article < ActiveRecord::Base
has_many :prices, dependent: :destroy, inverse_of: :article
validates_associated :prices
end
but this is not 100% solution to this.
You can try this gem https://github.com/perfectline/validates_existence

Resources