ActiveRecord query without *_id - ruby-on-rails

I have 3 simple models:
class User < ActiveRecord::Base
has_many :subscriptions
end
class Product < ActiveRecord::Base
has_many :subscriptions
end
class Subscription < ActiveRecord::Base
belongs_to :user
belongs_to :product
end
I can do a_subscription.product = a_product and AR knows I mean product_id and everything works fine.
But If i do:
Subscription.where :product => a_product
It throws an error at me Unknown column 'subscriptions.product' - It knows in the first case that I mean product_id but it doesn't in the latter. I am just wondering if this is how it is suppose to be or am I missing something? I can get it to work by saying
Subscription.where :product_id => a_product
by do I have to specify _id?

Yes, right now you can't pass association to the where method. But you'll be able to do it in Rails 4. Here is a commit with this feature.

I don't think there's an elegant way around that (as of now, see #nash 's answer). However, if you have an instance of a_product and it has has_many on subscriptions, why not just turn it around and say:
subscriptions = a_product.subscriptions

Related

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 4 - Selecting associated models via collection (similar to .NET Linq's .SelectMany())

Forgive me if this has already been asked (as I believe it has), but I couldn't find this exact issue (and it's very likely I'm not searching properly).
I have the following models:
class Company < ActiveRecord::Base
has_many :jobs
end
class Job < ActiveRecord::Base
belongs_to :company
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :jobs
end
What I'm trying to accomplish is a list of tags by company, but I'm failing to figure out how (I should note that I'm pretty new to Ruby and Rails). I come from a .NET background, and with Linq I'd use something like Company.Jobs.SelectMany(j => j.Tags).
I tried to do Company.first.jobs.tags, which fails with NoMethodError: undefined method 'tags' for #<Job::ActiveRecord_Associations_CollectionProxy:0x892dca0>, but strangely enough, if I run Company.first.jobs.instance_methods on a rails console, there is a :tags method. And this is what I get when I use the console's autocomplete:
Any suggestions?
Thanks.
What you are looking for is called 'has many through'.
class Company < ActiveRecord::Base
has_many :jobs
has_many :tags, through: :jobs
end
This is how you can go through an association (in this case jobs) to get to that association's association.
This way you can run company.tags on an instance of a company.
The problem with your existing approach is that jobs is a collection, but you need to hit the method on individual instances. This would work with no adjustment:
Company.first.jobs.collect { |job| job.tags }

Can't validate children of a has_many through relation directly

I have the following setup:
class Round < ActiveRecord::Base
has_many :cards_rounds
has_many :cards, through: :cards_rounds
accepts_nested_attributes_for :cards_rounds
validate :round_validations
private
def round_validations
// pry
unless cards.map(&:id).uniq.size == 3
errors.add(:round, "Must have 3 unique cards")
end
unless cards.map(&:quality).uniq.size == 1
errors.add(:round, "Cards must be of the same rarity")
end
end
end
class CardsRound < ActiveRecord::Base
belongs_to :card
belongs_to :round
end
class Card < ActiveRecord::Base
has_many :cards_rounds
has_many :rounds, through: :cards_rounds
end
Round always fails to validate on creation. When I step in using pry, I can see that cards is nil, but cards_rounds is populated and I can call cards_rounds[0].card (for example).
Is this the expected behaviour? It seems odd to me that I can reference the cards through cards_rounds but not directly as a collection.
Rails version is 4.0.1
Yeah I feel your pain. I'm not sure if this is the expected behavior but I've came into this same problem some times.
The best think you can do is to open an issue on https://github.com/rails/rails
Not sure where the problem is, I did the following gist which the tests pass: https://gist.github.com/arthurnn/9607180. See that I changed the validation. The problem is that the validation cannot rely on mapping the ids from cards, as they dont have an id yet.

Specifying the default ordering for a has_many association using a join (Rails)?

I have a simple Customer model with a has many relationship with a Purchase model.
class Customer < ActiveRecord::Base
has_many :purchases
end
I am repeatedly finding that I need to order Customer.purchases in my views in the following way:
#customer.purchases.joins(:shop).order("shops.position").order(:position) #yes, two orders chained
In the interest of keeping things DRY, I'd like to put this somewhere centralized so I don't have to repeatedly do it. Ideally, I'd like to make it the default ordering for Customer.purchases. For example:
class Customer < ActiveRecord::Base
has_many :purchases, :order => joins(:shop).order("shops.position").order(:position)
end
Obviously the above doesn't work. How should I do this?
In your customer model you specified joins(:shop) is the value for the key :order. I think here is the problem, So you can use the joins as a key instead of order like below,
class Customer < ActiveRecord::Base
has_many :purchases, :joins => [:shop], :order => "shops.position"
end
I think it may work.
In your purchases model, you can create a class method:
Purchase.rb:
def self.order_by_position
joins(:shop).order("shops.position").order(:position)
end
Then you can say things like:
#customer.purchases.order_by_position
Purchase.order_by_position
You could create a method on Customer that returns ordered purchases:
class Customer < ActiveRecord::Base
has_many :purchases
def ordered_purchases
purchases.joins(:shop).order("shops.position").order(:position)
end
end
and call #customer.ordered_purchases from your views.

Rails - insert many random items on create with has_many_through relation

I want to create a random pack of 15 cards which should be invoked in the cardpacks_controller on create. I have the following models:
Card:
class Card < ActiveRecord::Base
# relations
has_many :cardpacks, through: :cardpackcards
belongs_to :cardset
end
Cardpack:
class Cardpack < ActiveRecord::Base
#relations
has_many :cards, through: :cardpackcards
belongs_to :cardset
# accept attributes
accepts_nested_attributes_for :cards
end
Cardpackcards:
class Cardpackcard < ActiveRecord::Base
#relations
belongs_to :card
belongs_to :cardpack
end
Cardsets:
class Cardset < ActiveRecord::Base
#relations
has_many :cards
has_many :cardsets
end
How can I create 15 Cardpackcards records with random card_id values and with the same cardpack_id (so they belong to the same pack)
I have watched the complex form series tutorial but it gives me no comprehension as how to tackle this problem.
I hope anyone can help me solve this problem and give me more insight in the rails language.
Thanks,
Erik
Depending on the database system you might be able to use an order random clause to find 15 random records. For example, in Postgres:
Model.order("RANDOM()").limit(15)
Given the random models, you can add a before_create method that will setup the associations.
If the Cardpackcard model doesn't do anything but provide a matching between cards and cardpacks, you could use a has_and_belongs_to_many association instead, which would simplify things a bit.
Without it, the controller code might look something like this:
cardset = Cardset.find(params[:cardset_id])
cardpack = Cardpack.create(:cardset => cardset)
15.times do
cardpack.cardpackcards.create(:card => Card.create(:cardset => cardset))
end

Resources