Has Many Limitation - ruby-on-rails

I would like to set a limit to a has_many association. For example, a car has 4 tires. Therefore the Car table should have 4 foreign keys to records in the Tire table.. (note that in this case, each tire can have different priorities and it is for this reason that I need 4 keys)
Anyway I can specify a the number of tire keys in a car record when declaring an association please?

I don't think has_many association has such an option but you can have a before_create validation in your tires model. Assuming that you create tires independently and not via cars using nested forms, below is an example code for your RAILS_APP/app/models/tire.rb.
Class Tire < ActiveRecord::Base
belongs_to :car
before_create :four_tires_per_car
private
def four_tires_per_car
# can't create more tires if the car in question already has four tires
errors[:base] << "A car can have a maximum of four tires" if car.tires.count == 4
end
end
If you are creating tires via car using nested forms, you can modify this example code accordingly.

No afaik, but you can use the cancan gem to achieve that. For example inside the ability.rb
can :create, Car do |car|
car.tires.count <= 4
end

Related

Rails association, nested forms and STI

Here are my simplified models
class Offer < ApplicationRecord
has_many :rooms
end
class Room < ApplicationRecord
belongs_to :offer
end
class Kitchen < Room
end
I'm using STI for Kitchen, cause it seemed the right way to express what I wanted to do (I'm probably wrong).
I want to be able to create an Offer with Rooms in it. I have 'regular' rooms that are instances of Room directly, and more specified rooms such as a Kitchen which can have an extra attribute.
I'm using cocoon to create nested form, it works great to create an offer and add regular rooms. But how can I add kitchens ?
Maybe it's an architecture issue more than an implementation issue. How would you manage to do something like this ?
One solution would be to add JSONB column data to your rooms table. Then you can use jsonb_accessor gem to elegantly manage STI.
I assume you have type column already so you can do something like:
Kitchen.create(offer_id: 1, name: "Great Kitchen", description: "It is great!")
Notice: description would be attribute of JSONB data column. In addition you can create indices for JSONB attributes and even do queries. From my experience, you want to keep JSONB as place to store additional info and less for heavy queries.
I hope this helps.

How to actually use has_many :through and set up relationships

So many tutorials on how to set up a has_many :through but not enough on how to actually do it!
I have a Inventories and Requests table joined by Bookings. Example: there could be 3 lenders who have tents in inventory, each of which is requested by 3 other borrowers. What I want to do is for each of the 3 tents in inventory, show that lender the list of 3 borrowers who requested the tent. Then the lender can pick who s/he wants to be the ultimate borrower.
I have thoughts on how this should work, but no idea if it's right, so please give advice on the below! The action is driven all by the Requests controller. Let's run through an example where the Inventories table already has 3 tents, ids [1, 2, 3]. Let's say Borrower Pat submits a Request_ID 1 for a tent.
Am I then supposed to create 3 new Bookings all with Request_ID 1 and then Inventory_ID [1, 2, 3] to get all the conceivable combinations? Something like
Inventory.where(name: "tent").each { |inventory| #request.bookings.create(inventory_id: inventory.id) }
And then is it right to use the Bookings primary key as the foreign key in both the Request and Inventory? Which means that after Borrower Pat submits his request, the bookings_id will be blank until say Lender 2 accepts, at which point bookings_id equals the id that matches the combination of Request_ID 1 and Inventory_ID 2
Now let's say when a Request is posted and a Bookings is made, I email the lender. However, I realized I don't want to bother Lender Taylor if 3 borrowers want her tent over the same time period. I'll just email her the first time, and then the subsequent ones she'll find out about when she logs in to say yes or no. In this situation is it OK to just query the Bookings table in the create action, something like (expanding off above)
-
Inventory.where(name: "tent").each do |inventory|
if !Bookings.find_by_inventory_id(inventory.id).exists?
# If there are no other bookings for this inventory, then create the booking and send an email
#request.bookings.create(inventory_id: inventory.id)
AlertMail.mail_to_lender(inventory).deliver
else
# If there are other bookings for this inventory, do any of those bookings have a request ID where the requested time overlaps with this new request's requested time? If so then just create a booking, don't bother with another email
if Bookings.where(inventory_id: inventory.id).select { |bookings_id| Bookings.find_by_id(bookings_id).request.time overlaps_with current_request.time }.count > 0
#request.bookings.create(inventory_id: inventory.id)
# If there are other bookings for this inventory but without overlapping times, go ahead and send an new email
else
#request.bookings.create(inventory_id: inventory.id)
AlertMail.mail_to_lender(inventory).deliver
end
end
end
Code above is probably flawed, I just want to know the theory of how this is supposed to be working.
Join Table
Firstly, has_many :through works by using a join table - a central table used to identify two different foreign_keys for your other tables. This is what provides the through functionality:
Some trivia for you:
has_and_belongs_to_many tables are called [plural_model_1]_[plural_model_2] and the models need to be in alphabetical order (entries_users)
has_many :through join tables can be called anything, but are typically called [alphabetical_model_1_singular]_[alphabetical_model_2_plural]
--
Models
The has_many :through models are generally constructed as such:
#app/models/inventory.rb
Class Inventory < ActiveRecord::Base
has_many :bookings
has_many :requests, through: :bookings
end
#app/models/booking.rb
Class Booking < ActiveRecord::Base
belongs_to :inventory
belongs_to :request
end
#app/models/request.rb
Class Request < ActiveRecord::Base
has_many :bookings
has_many :requests, through: :bookings
end
--
Code
Your code is really quite bloated - you'll be much better doing something like this:
#app/controllers/inventories_controller.rb
Class InventoriesController < ApplicationController
def action
#tents = Inventory.where name: "tent"
#tents.each do |tent|
booking = Booking.find_or_create_by inventory_id: tend.id
AlertMail.mail_to_lender(tent).deliver if booking.is_past_due?
end
end
end
#app/models/booking.rb
Class Booking < ActiveRecord::Base
def is_past_due?
...logic here for instance method
end
end
Used find_or_create_by
You should only be referencing things once - it's called DRY (don't repeat yourself)
I did a poor job of asking this question. What I wanted to know was how to create the actual associations once everything is set up in the DB and Model files.
If you want to create a record of B that is in a many-to-many relationship with an existing record of A, it's the same syntax of A.Bs.create. What was more important for me, was how to link an A and B that already existed, in which case the answer was A.B_ids += B_id.
Two other things:
More obvious: if you created/ linked something one way, was the other way automatic? And yes, of course. In a many-to-many relationship, if you've done say A.B_ids += B_id, you no longer have to do 'B.A_ids += A_id`.
Less obvious: if A and B are joined by table AB, the primary key of table AB doesn't need to be added to A or B. Rails wants you to worry about the AB table as less as possible, so searches, builds, etc. can all be done by A.B or B.A instead of A.AB.B or B.AB.A

What type of relationship should these models have to each other?

I have two models: player and team.
A player has one team
Team has 5 fields on it (in addition to its name and location), opponent_week_1, opponent_week_2, etc.
I would like to be able to say something like Player.Team.opponent_week_1
How should I relate the models to each other? Player has_one team?
How do I set the team's opponents? I don't want the team to have_many opponents, because there will only be those 5, and I want to be able to say opponent_week_1, opponent_week_2, etc.
I am using Ruby 2 and Rails 4. Thanks!
jackerman09,
As with many things in Rails, there's a few ways of going about it. #phgrey pointed out how to fix the players and teams.
Regarding opponent_week_1, 2, etc.:
I think the best way is if you actually do have a has_may :opponent_week association from the Team model, like this:
class Team < ActiveRecord::Base
...
has_many :fields
...
end
You'd then have to limit insertion of opponent weeks to each Team to only 5 through validations and/or through the forms. Since users will be entering those opponent weeks through forms, that would be an easy way to go about it at first. You have control of the forms, so just limit how many opponent_weeks they put in for each Team through forms.
How you'd go about about calling them opponent_week_1, opponent_week_2 etc.: there are a few ways. I would try putting a a method_missing method in your model (google to see how to do it) then parse the name of the method that you called. Something like this:
def method_missing( method_name )
if method_name.starts_with?( "opponent_week_" )
# get the number at the end, then call
opponent_weeks[ num_of_week - 1 ]
else
super
end
end
All the best and let me know if you need clarifications.
Take a look here - http://guides.rubyonrails.org/association_basics.html
1. Players <=> Team
class Player < ActiveRecord::Base
...
belongs_to :team
...
end
class Team < ActiveRecord::Base
...
has_many :players
...
end
Be sure that migration create_players has field
t.references :team_id
2. Oppenents
Youre lead the worsest way. Better take a look at HABTM (has_and_belongs_to_many) relations between commands

Scope of the rails find_by_id method

Suppose I have a rails app with two models Person and House. Each Person object has a House_id property.
I would like to define the following method inside of my Person model:
def locate_house
current_house_id = house.find_by_id(person)
end
But I am getting an undefined variable error for house, how can I ensure that this is within scope?
You are trying to rewrite something already built into rails. Use a belongs_to relationship:
class Person < ActiveRecord::Base
belongs_to :house
end
Then you can just do:
person.house
To get the associated house.
Your model--House--is a ruby constant that requires capitalization
def locate_house
current_house_id = House.find_by_id(person)
end
House is a constant and needs a capital letter like someone else said, look at Rails Guides regarding relationships between Active Record Models. There are many possible realtionships, has many is probably what you are looking for. Since, in reality, a person can have multiple houses.

Traversing HABTM relationships on ActiveRecord

I'm working on a project for my school on rails (don't worry this is not graded on code) and I'm looking for a clean way to traverse relationships in ActiveRecord.
I have ActiveRecord classes called Users, Groups and Assignments. Users and Groups have a HABTM relationship as well as Groups and Assignments. Now what I need is a User function get_group(aid) where "given a user, find its group given an assignment".
The easy route would be:
def get_group(aid)
group = nil
groups.each { |g| group = g if g.assignment.find(aid).id == aid }
return group
end
Is there a cleaner implementation that takes advantage of the HABTM relationship between Groups and Assignments rather than just iterating? One thing I've also tried is the :include option for find(), like this:
def get_group(aid)
user.groups.find(:first,
:include => :assignments,
:conditions => ["assignments.id = ?", aid])
end
But this doesn't seem to work. Any ideas?
First off, be careful. Since you are using has_and_belongs_to_many for both relationships, then there might be more than one Group for a given User and Assignment. So I'm going to implement a method that returns an array of Groups.
Second, the name of the method User#get_group that takes an assignment id is pretty misleading and un-Ruby-like.
Here is a clean way to get all of the common groups using Ruby's Array#&, the intersection operator. I gave the method a much more revealing name and put it on Group since it is returning Group instances. Note, however, that it loads Groups that are related to one but not the other:
class Group < ActiveRecord::Base
has_and_belongs_to_many :assignments
has_and_belongs_to_many :users
# Use the array intersection operator to find all groups associated with both the User and Assignment
# instances that were passed in
def self.find_all_by_user_and_assignment(user, assignment)
user.groups & assignment.groups
end
end
Then if you really needed a User#get_groups method, you could define it like this:
class User < ActiveRecord::Base
has_and_belongs_to_many :groups
def get_groups(assignment_id)
Group.find_all_by_user_and_assignment(self, Assignment.find(assignment_id))
end
end
Although I'd probably name it User#groups_by_assignment_id instead.
My Assignment model is simply:
class Assignment < ActiveRecord::Base
has_and_belongs_to_many :groups
end

Resources