Specify an optional reference in your Rails model - ruby-on-rails

I have a Sponsors model and a Promo Codes model.
A sponsor can have zero or more promo codes
A promo code can have zero or one sponsors
Thus a promo code should have an optional reference to a sponsor, that is, a sponsor_id that may or may not have a value. I'm not sure how to set this up in Rails.
Here's what I have so far:
# app/models/sponsor.rb
class Sponsor < ActiveRecord::Base
has_many :promo_codes # Zero or more.
end
# app/models/promo_code.rb
class PromoCode < ActiveRecord::Base
has_one :sponsor # Zero or one.
end
# db/migrate/xxxxx_add_sponsor_reference_to_promo_codes.rb
# rails g migration AddSponsorReferenceToPromoCodes sponsor:references
# Running migration adds a sponsor_id field to promo_codes table.
class AddSponsorReferenceToPromoCodes < ActiveRecord::Migration
def change
add_reference :promo_codes, :sponsor, index: true
end
end
Does this make sense? I'm under the impression that I have to use belongs_to in my Promo Codes model, but I have no basis for this, just that I've haven't seen a has_many with has_one example yet.

In Rails 5, belongs_to is defined as required by default. To make it optional use the 'optional' option :)
class User
belongs_to :company, optional: true
end
Source: https://github.com/rails/rails/issues/18233

This looks like a simple has_many and belongs_to relationship:
# app/models/sponsor.rb
class Sponsor < ActiveRecord::Base
has_many :promo_codes # Zero or more.
end
# app/models/promo_code.rb
#table has sponsor_id field
class PromoCode < ActiveRecord::Base
belongs_to :sponsor # Zero or one.
end
has_one isn't appropriate here, as it would replace has_many: ie, you either have "has_many" and "belongs_to" OR "has_one" and "belongs_to". has_one isn't generally used much: usually it is used when you already have a has_many relationship that you want to change to has_one, and don't want to restructure the existing tables.

Unless you specify validation, relationships are optional by default.
The belongs_to is to tell rails the other half of the relationship between those two objects so you can also call #promo_code.sponsor and, vice versa, #sponsor.promo_codes.

Related

Rails 4 Accept nested attributes with has_one association

I have a question about Rails Nested Attributes.
I'm using Rails 4 and have this model:
model Location
has_one parking_photo
has_many cod_photos
accepts_nested_attributes_for :parking_photo
accepts_nested_attributes_for :cod_photos
end
When I use for example:
Location.find(100).update(cod_photo_ids: [1,2,3]) it works.
But Location.find(100).update(parking_photo_id: 1) doesn't works.
I don't know what difference between nested attributes has_one and has_many.
Or do we have any solution for my case, when I already have child object and want to link the parent to the child and don't want to use child update.
Thank you.
The problem has nothing to do with nested attributes. In fact you're not even using nested attributes at all in these examples.
In this example:
Location.find(100).update(cod_photo_ids: [1,2,3])
This will work even if you comment out accepts_nested_attributes_for :cod_photos as the cod_photo_ids= setter is created by has_many :cod_photos.
In the other example you're using has_one where you should be using belongs_to or are just generally confused about how you should be modeling the association. has_one places the foreign key on the parking_photos table.
If you want to place the parking_photo_id on the locations table you would use belongs_to:
class Location < ActiveRecord::Base
belongs_to :parking_photo
# ...
end
class ParkingPhoto < ActiveRecord::Base
has_one :location # references locations.parking_photo_id
end
Of course you also need a migration to actually add the locations.parking_photo_id column. I would really suggest you forget about nested attributes for the moment and just figure out the basics of how assocations work in Rails.
If you really want to have the inverse relationship and put location_id on parking_photos you would set it up like so:
class Location < ActiveRecord::Base
has_one :parking_photo
# ...
end
class ParkingPhoto < ActiveRecord::Base
belongs_to :location
validates_uniqueness_of :location_id
end
And you could reassign a photo by:
Location.find(100).parking_photo.update(location_id: 1)

rails build associations from has_many through has_many raise error

I have a very special cases. I understand maybe db design is not very awesome, but I cannot change that.
class Employer < ApplicationRecord
has_many :contract_employers
has_many :contracts, through: :contract_employers
has_many :crm_contacts, through: :contract_employers
# typical join table, with key: contract_id and employer_id
class ContractEmployer < ApplicationRecord
belongs_to :contract
belongs_to :employer
has_many :crm_contacts
# CrmContact table has key: contract_employer_id
class CrmContact < ApplicationRecord
belongs_to :contract_employer
has_one :employer, through: :contract_employer
Given
employer = Employer.create
I have no issue to run
employer.contracts.create
However, if I try to run
employer.crm_contacts.create
It raise error
ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection: Cannot modify association 'Employer#crm_contacts' because the source reflection class 'CrmContact' is associated to 'ContractEmployer' via :has_many.
I checked the rails source code, basically it states very clearly
# Construct attributes for :through pointing to owner and associate. This is used by the
# methods which create and delete records on the association.
#
# We only support indirectly modifying through associations which have a belongs_to source.
# This is the "has_many :tags, through: :taggings" situation, where the join model
# typically has a belongs_to on both side. In other words, associations which could also
# be represented as has_and_belongs_to_many associations.
#
# We do not support creating/deleting records on the association where the source has
# some other type, because this opens up a whole can of worms, and in basically any
# situation it is more natural for the user to just create or modify their join records
# directly as required.
So only typical join table supports model.associations.create? Any suggestion for my user case?
Take my case for example, even rail is willing to do the job. How could employer.crm_contacts.create create middle table record ContractEmployer? Yes, it knows employer.id, but it has no clue what contract.id is, right?
Rails can not create middle table record in this case, but you can.
And I am completely agree with this (comments in rails source code /activerecord/lib/active_record/associations/through_association.rb):
in basically any situation it is more natural for the user to just
create or modify their join records directly as required
I don't see a problem here.
class Employer < ApplicationRecord
# ...
def create_crm_contact
ActiveRecord::Base.transaction do
contract = contracts.create # will create both `contract` and associated `contract_employer`
# find the `contract_employer` that has been just created
contract_employer = contract_employers.find_by(contract_id: contract.id)
contract_employer.crm_contacts.create
end
end

Rails 4 Associations: Help setting up database

I need some assistance with my Rails 4 associations. I have the following 4 models:
class User < ActiveRecord::Base
has_many :check_ins
has_many :weigh_ins, :through => :check_ins
has_many :repositionings, :through => :check_ins
end
class CheckIn < ActiveRecord::Base
belongs_to :user
has_one :weigh_in
has_one :repositioning
end
class Repositioning < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
class WeighIn < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
Question: If I am setup this way, how would I input repositionings and weigh_ins separately, but still have them linked through a single check in?
You would have to retain one of the other association's ID in order to make it work.
For example, let's say:
You have created a CheckIn.
You now add a Repositioning to that check in.
Store the ID of the repositioning object
When adding your WeighIn object, you would simply reference the correct CheckIn record: correct_checkin_record = CheckIn.where(repositioning: the_repositioning_id)
You can then add the WeighIn object to that particular record.
An alternative (and simpler) method would be to access the CheckIn directly through the User: correct_checkin_record = #user.checkin -- This would pull in the correct CheckIn every time.
I've included both options to help visualize exactly what is going on in the relation.
Do you want to have users input weigh_ins and repositionings on different pages?
Having weigh_ins and repositionings inputted separately but still be part of a single checkin is fine with that setup. Its just matter of getting the same check_in object and make the associations to that object, which can be done through the controller by passing in check_in ID params and do CheckIn.find(params[:id])

rails association limit record based on an attribute

As you can see in the schema below, a user can create courses and submit a time and a video (like youtube) for it, via course_completion.
What I would like to do is to limit to 1 a course completion for a user, a given course and based one the attribute "pov" (point of view)
For instance for the course "high altitude race" a user can only have one course_completion with pov=true and/or one with pov=false
That mean when creating course completion I have to check if it already exist or not, and when updating I have to check it also and destroy the previous record (or update it).
I don't know if I'm clear enough on what I want to do, it may be because I have no idea how to do it properly with rails 4 (unless using tons of lines of codes avec useless checks).
I was thinking of putting everything in only one course_completion (normal_time, pov_time, normal_video, pov_video) but I don't really like the idea :/
Can someone help me on this ?
Thanks for any help !
Here are my classes:
class CourseCompletion < ActiveRecord::Base
belongs_to :course
belongs_to :user
belongs_to :video_info
# attribute pov
# attribute time
end
class Course < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :courses
has_many :course_completions
end
You could use validates uniqueness with scoping Rails - Validations .
class CourseCompletion < ActiveRecord::Base
belongs_to :course
belongs_to :user
belongs_to :video_info
validates :course, uniqueness: { scope: :pov, message: "only one course per pov" }
# attribute pov
# attribute time
end

Passing a child to container of parent type

I have a parent class Individual and child classes Student and Professor in my rails application.
Inheritance is handled with a gem called 'acts_as_relation' which simulates multiple table inheritance.
In addition, I have an action within which a student instance is appended to a list of individuals. Normally I would have expected this to go through without any problems but I get this error:
ActiveRecord::AssociationTypeMismatch: Individual(#70220161296060) expected, got Student(#70220161349360)
Here is a glance at my model:
class Individual < ActiveRecord::Base
acts_as_superclass
end
class Student < ActiveRecord::Base
acts_as :individual
end
class Professor < ActiveRecord::Base
acts_as :individual
end
I've not used this gem, but to give you some help, here's what I've found and this:
They both mention that you're calling an object through your relation, which will have confusion over polymorphism or similar. The two posts could not fix the issue, and I presume that is because they could find the correct object for their relationship
Looking at this further, I found this tutorial on the gem homepage:
acts_as_relation uses a polymorphic has_one association to simulate
multiple-table inheritance. For the e-commerce example you would
declare the product as a supermodel and all types of it as acts_as
:product (if you prefer you can use their aliases is_a and
is_a_superclass)
class Product < ActiveRecord::Base
acts_as_superclass
end
class Pen < ActiveRecord::Base
acts_as :product
end
class Book < ActiveRecord::Base
acts_as :product
end
To make this work, you need to declare both a foreign key column and a
type column in the model that declares superclass. To do this you can
set :as_relation_superclass option to true on products create_table
(or pass it name of the association):
create_table :products, :as_relation_superclass => true do |t|
# ...
end
Or declare them as you do on a polymorphic belongs_to association, it
this case you must pass name to acts_as in :as option:
change_table :products do |t|
t.integer :producible_id
t.string :producible_type
end
class Pen < ActiveRecord::Base
acts_as :product, :as => :producible
end
class Book < ActiveRecord::Base
acts_as :product, :as => :producible
end
Are you sure you've got your datatables set up correctly?
The way I've solve this in my projects, is using instance_ofsome_class.individuals << student_instance.individual.
The thing here is that is not a real MTI, so your collection of individuals would accept only individuals instances. If you call some_student_instance.individual or some_professor_instance.individual, you'll get an individual instance which is related with your specific instance.
Then working with that collection, if you want a Student or Professor all you need to do is call individual_in_collection.specific. For example:
p = Professor.create
a_model.individuals << p.individual
puts "#{a_model.individuals.first.class.name}"
=> Individual
puts "#{a_model.individuals.first.specific.class.name}"
=> Professor

Resources