Custom Association Method - Can This Be Done - ruby-on-rails-3

I have three models: Wager, Race, RaceCard and WagerType. I've created a has_many association in Wagers and have added a custom association method (in_wager). The purposes of the method is to filter the correct races for each wager. (Some wagers span multiple races). I'd like to be able to do somethings like: Wager.first.races.in_wager and have the appropriate races returned.
class Wager < ActiveRecord::Base
belongs_to :wager_type
belongs_to :race_card
has_many :races, :through => :race_card do
def in_wager
where('races.race_nbr BETWEEN ? AND ?', a, b)
end
end
end
My custom method works fine if I hardcode the values for a and b, however, I need those values to be dynamic. Specifically, the value of b should equal the race_nbr attribute from the Wager model:
b = wagers.race_nbr
and the value of a should equal b minus the Number Of Race for the particular Wager type (know as Legs) plus 1:
a = b - Legs + 1
The value for legs is in the WagerType model. Note Wagers belong_to WagerType and WagerType has_many Wagers. Therefore, a could be expressed as:
a = (b - (select wager_types.legs where wagers_types.id = wagers.wager_type_id) + 1)
MY Question: Is it actually possible to do this with my in_wager association method. I've been banging my head on this for a couple of evening now and can't quite figure out how to assign the correct values to a and b. Also if you feel I'm coming at this the wrong way, I'd be happy to hear alternative approaches. Thanks for your help.
Note: I never really mentioned the RaceCard or Races models. They have the following associations:
class RaceCard < ActiveRecord::Base
has_many :races
end
class Races < ActiveRecord::Base
belongs_to :race_card
has_many :wagers, :through => :race_card
end
Update: I was reading Design Patterns in Ruby last night and came across the Proc. I'm going to see if I can use it within the Association method to calculate the values for a and b.

you can use self.proxy_association.owner to get the parent object inside of an association method. From there you can get the values you want.
If I understand your models correctly then the code should look something like this.
class Wager < ActiveRecord::Base
belongs_to :wager_type
belongs_to :race_card
has_many :races, :through => :race_card do
def in_wager
owner = self.proxy_association.owner
b = owner.race_nbr
a = b - owner.wager_type.legs + 1
where('races.race_nbr BETWEEN ? AND ?', a, b)
end
end
end
The I got this from the Rails api reference to Association Extensions (The reference to proxy_association is at the bottom of the section).

Do you absolutely need to use an has_many relation ? maybe you could just create a method in the Wager class
def races
b = self.race_nbr
a = b + self.race_card.legs
Races.find_by_race_card_id(self.id).where('race_nbr BETWEEN ? AND ?', a, b)
end
I don't really understand your model, so the request is certainly wrong, but you get the idea...

Related

Creating a "joins" query 2 associations up

I have a situation where a CourseSession belongs to a Course, which belongs to a Program. In my controller I'd like to get the name of the program from the CourseSession. I've been looking around and have been seeing a lot of people suggest using the joins method. Unfortunately for me this didn't work in my case. I get this error:
Can't join 'CourseSession' to association named 'program'; perhaps you misspelled it?
What am I doing wrong?
#sessions = if params[:program]
CourseSession.joins(:course).joins(:program).where("program.name = params[:program]")
else
CourseSession.all
end
class Program < ApplicationRecord
has_many :courses, dependent: :nullify
end
class Course < ApplicationRecord
has_many :sessions, class_name: "CourseSession", inverse_of: :course, dependent: :destroy
belongs_to :program
end
class CourseSession < ApplicationRecord
belongs_to :course
end
When you use CourseSession.joins(:course).joins(:program) (i.e. chaining joins), you're just joining course_sessions table with courses table and programs table hence the error.
What you've asked for can be achieved using the following syntax:
CourseSession.joins(course: :program)
Here, courses and programs table are inner joined and course_sessions and courses table are inner joined.
Second issue is in your where method. The table names are plural by convention so you should be using programs.name instead of program.name. Try the following instead:
CourseSession.joins(course: :program).where("programs.name = ?", params[:program])
It's a little unclear what your trying to do. Your code suggests you want:
CourseSessions associated with the Program if params[:program] is present, and
All CourseSessions if params[:program] is not present.
In which case I believe you'd do something like:
#sessions = if params[:program]
CourseSession.where(course: Course.where(program: Program.find_by(name: params[:program])))
else
CourseSession.all
end

How to manually join two different table with different attribute name in Ruby on Rails controller

I am currently making a website that runs on Ruby on Rails. I am facing some issues while I was trying to join two tables, Rates and Locations, that I have with two different attributes name.
Rates: id rater_id rateable_id (and a few more attributes in this table)
Locations: id title body user_id (and a few more attributes in this table)
Here is the query that I am trying to do in SQL.
SELECT *
FROM rates, locations
WHERE rates.rater_id = locations.user_id AND rates.rateable_id = locations.id
I have read the official active record documents that provided by rubyonrails.org. I have tried doing these, but it does not work. Here is the code that I am trying to implant in app\controllers\users_controller.rb
#join_rating = Rate.joins(:locations).where("rates.rateable_id = locations.id AND rates.rater_id = locations.id")
#all_rating = #all_rating.where(rater_id: #user)
#count_all_rating = #all_rating.count
#join_rating, is trying to join the attributes with different names.
#all_rating, is trying to filter which location to show using the user ID
#join_rating, is trying to calculate the total numbers of locations that are rated by the user
Assume that everything is setup correctly and the only error is in the query that I am trying to do, how should I rewrite the statement so that I am able to show the locations that the user has rated using #all_rating.
Thank you!
A few points:
When in ActiveRecord you're starting a statement with the Rate class, it means the result is going to be a collection of Rate objects. So if you're trying to show locations, you should start with a Location class.
#locations_user_rated = Location.joins('INNER JOIN rates ON
rates.rateable_id = locations.id').where('rates.rater_id' => #user)
And if your ActiveRecord associations are well defined, you could simply do:
#locations_user_rated = Location.joins(:rates).where('rates.rater_id' => #user)
"Well defined" simply means you'll need to do something like the following. Note that I am not sure I understand your model relationships correctly. I assume below that every location has multiple rates, and that the reason your Rate model has the field called rateable_id instead of a location_id is because you want :rateable to be polymorphic. This means you probably also have a rateable_type field in rates table.
class Location < ActiveRecord::Base
has_many :rates, as: :rateable
end
class Rate < ActiveRecord::Base
belongs_to :rateable, polymorphic: true
end
If this polymorphism is not the case, things should actually be simpler, and I highly recommend that you follow Rails's conventions and simply name the relationship field location_id on your Rate model instead of rateable_id. Then you can do:
class Location < ActiveRecord::Base
has_many :rates
end
class Rate < ActiveRecord::Base
belongs_to :location
end
If still you are not convinced about the field name, you can customize things and do:
class Location < ActiveRecord::Base
has_many :rates, foreign_key: :rateable_id
end
class Rate < ActiveRecord::Base
belongs_to :location, foreign_key: :rateable_id
end
You can find more about how to customize associations here, and here.
I highly recommend taking advantage of ActiveRecord's has_many, belongs_to, and has_many through: functionality.
If you set up a model for each of these tables, with the correct relationships:
class User < ActiveRecord::Base
has_many :ratings, foreign_key: :rater_id
has_many :rated_locations, through: ratings, class_name: Location.name, source: :rater
end
class Rating < ActiveRecord::Base
belongs_to :rater, class_name: User.name
belongs_to :location
end
class Location < ActiveRecord::Base
has_many :ratings
end
Then to access the locaitons that a user has rated, you just call
user.rated_locations

Rails 3 has_many :through accessing attributes

I am working with a has_many through for the first time, and despite a lot of reading here and in the guide I am not understanding the correct way to access attributes on the through table. My tables are the same as this example from another post.
class Product < ActiveRecord::Base
has_many :collaborators
has_many :users, :through => :collaborators
end
class User < ActiveRecord::Base
has_many :collaborators
has_many :products, :through => :collaborators
end
class Collaborator < ActiveRecord::Base
belongs_to :product
belongs_to :user
end
Assuming that the collaborators table has additional attributes, say hours_spent, what is the correct way to find the hours_spent from the collaborator table for a particular user and product?
When I have found my users via the product, and am iterating over them as in
#product.users.each do |user|
This seems to work
user.collaborator[0].hours_spent
I get the correct value, but since there should only be one collaborator record for each User/Product pair, the index is throwing me off, making me think I’m doing something wrong.
Thank you for reading!
EDIT
Perhaps I am not getting the has_many through concept. Maybe a MySQL example would help.
What I was thinking is that if I did
SELECT * FROM collaborators where user_id = 1;
I would expect a set (zero or more) as the result. Similarly
SELECT * FROM collaborators where product_id = 1;
would also give me a set, but
SELECT * FROM collaborators where user_id = 1 and product_id = 1;
would give at most 1 row.
If I am understanding properly, all 3 queries return a set. So I guess I need some kind of uniqueness constraint, but that would have to be a compound key of sorts, on both of the belongs to keys. Is that even possible? Is there a structure that better models this?
Thanks so much for the quick and helpful responses!
There may be a single database row per pair, but when considering a single user, that user can be associated to many products, so a user can have many rows in the collaborators table. Similarly, when considering a single product, that product can be associated to many users, so a product can have many rows in the collaborators table.
Also, instead of using user.collaborators[0].hours_spent, use user.collaborators.first.try(:hours_spent) (which may return null), if you only want the first collaborator's hours spent.
If a single user can only have one single product and a single product can only have a single user, then switch the has_many's to has_one's for everything.
Update: The preceding is the answer to the original question which has since been clarified via comments. See comments for detail and see comments on other answer by Peter.
Perhaps you should use has_and_belongs_to_many. If your Collaborator is used only to make link between User and Product without having more fields.
class Product < ActiveRecord::Base
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
has_and_belongs_to_many :products
end
The beetween migration would be:
class CreateUsersProducts < ActiveRecord::Migration
def change
create_table "users_products", :id => false do |t|
t.integer :user_id
t.integer :product_id
end
end
end
After implementing this, what I found was that I think I had the correct relationships setup, I had to use the has_many :though as users could have many products, and it needed to be :through because there are additional attributes on the collaborator table. The sticking point was how to get there to only be a single Collaborator record for each user/product pair, and then how do I guarantee I got it. And to this point the answer I've found is it has to be done in code.
To make sure there is only a single record for each pair, I used
class Collaborator < ActiveRecord::Base
validates :product_id, :presence => true, :uniqueness => {:scope => [:user_id], :message => "This is a duplicate join"}
And then to make doubly sure I'm finding the right record, I have a scope
scope :collaboration_instance, lambda {|p_id, u_id| where("collaborations.product_id = ? && collaborations.user_id = ?", p_id, u_id)}
If someone has a more elegant solution, or just wants to improve this one, please post and I will change yours to the selected answer.

Querying for rows without matching ID in associated table

I have a very standard app backed by an SQL database with a User model, a Problem model, and a CompletedProblem model acting as a join table between the two.
I'm trying to create a method that returns all problems not solved by a particular user. I have run into a wall, however, and I would appreciate pointers on what my method should look like.
Below are the models as well as my latest (incorrect) pass at creating this method.
class User < ActiveRecord::Base
has_many :completed_problems
has_many :problems, :through => :completed_problems
def unsolved_problems
Problem.includes({:wall => :gym}, :completed_problems).
where('completed_problems.user_id != ? OR completed_problems.user_id IS NULL)', self.id)
end
end
class Problem < ActiveRecord::Base
has_many :completed_problems
has_many :users, :through => :completed_problems
end
class CompletedProblem < ActiveRecord::Base
belongs_to :user
belongs_to :problem
end
(For the curious: this method does work so long as there is only one user marking problems as solved. As soon as you add a second, each user starts to return only those problems that have been solved by other users, instead of those not solved by herself.)
Via a friend:
select * from problems where id not in (select problem_id from completed_problems where user_id = USER_ID))
Although I'd still be interested in hearing if there's a way in ActiveRecord to do this.
I think something like this will do it:
Problem.where(["id NOT IN (?)", self.problems.all.map(&:id)])

Filter based on model attribute has_many relationship through, rails 3?

I have a simple question, but can't seem to find any solution, though I have found things that are similar, but just not exactly what I am looking for.
I have an application where a User has many Assets through the class UserAsset. I want to be able to do current_user.user_assets , but I only want to return records that have an Asset with a specified field value of "active".
This post is similar but I need to use the main model not the join model as a filter.
class UserAsset < ActiveRecord::Base
belongs_to :asset
belongs_to :user
end
class Asset < ActiveRecord::Base
has_many :user_assets
has_many :users, :through => :user_assets
end
class User < ActiveRecord::Base
has_many :user_assets
has_many :assets, :through => :user_assets
end
I tried setting the default scope on Asset, and also some conditions on the has many (user_assets) relationship, but rails is failing to consider the join on the Assets table. ie Unknown column 'asset.live' in 'where clause'. Trying to achieve the following:
#active_user_assets = current_user.user_assets #only where assets.active = true
So how do I use conditions or scopes to achieve this? I need the user_asset object because it contains info about the relationship that is relevant.
Thanks in advance!
You want current_user.assets, then your scopes should work.
Oh, but you want the user_assets. Hmm. I think you need the :include clause to find() but where to put it, I can't be arsed to think of right now.
Perhaps
current_user.user_assets.find(:all, :include => :assets).where('asset.live=?', true)
(I'm not on Rails 3 yet, so that's going to be mangled)
Are you using :through when you really want a HABTM?