Deleting first occurrence from many to many collection entry at rails? - ruby-on-rails

In many to many fields delete method is deleting all the occurrence of collection. Say I have:
class user < ActiveRecord::Base
has_and_belongs_to_many :cars
end
class car < ActiveRecord::Base
has_and_belongs_to_many :users
end
users and cars are many to many relationship, I have defined my users_cars table. Now user can have repetitive car entry as relation. For example:
Car: A,B,C
User: U1,U2,U3
U1=[A,B,C,A,A,A,B]
Which can be implemented using many to many relationship, the way I have implemented. BUT, at the time when I want to delete one of the car entries of user the problem occurs.
User.cars.delete(car) #deletes all occurrence of car
User.cars.delete_at(User.cars.find_index(video_card)) #delete_at does not exist
Now how to resolve this?

First of all, you can't call User.cars unless you have defined a class level method cars in your User model, but in this way, you would return all cars, and that - in no way - would make sense.
Second, delete_at is a method that works on Array objects, and expects an integer to be passed in. So as a little hack, you can turn your ActiveRecord::Associations object into an array, and then call delete_at method.
user = User.first
user.cars.to_a.delete_at(Car.last.id) # assuming that the last car belongs
# to the first user, something you would never do in actual
# production code.
Edit:
You can also try the following to achieve the same functionality:
user = User.first
user.cars.where("cars.id = ?", Car.first.id).first.delete
Edit 2:
For what you asked in comment, you can have a model for the table cars_users.
rails g model CarUser
class Car < ActiveRecord::Base
has_many :cars_users
has_many :users, through: car_users
end
class User < ActiveRecord::Base
has_many :cars_users
has_many :cars, through: car_users
end
class CarUser < ActiveRecord::Base
belongs_to :car
belongs_to :user
end
And now, you can do:
CarUser.where("car_id = ? AND user_id = ?", Car.first.id, User.first.id).first.delete

Related

Create Rails scope comparing fields on two tables

I have a number of associated tables in an application
class Listing < ActiveRecord::Base
belongs_to :house
belongs_to :multiple_listing_service
end
class House < ActiveRecord::Base
has_one :zip_code
has_one :primary_mls, through: :zip_code
end
I wanted to create a scope that produces all the Listings that are related to the Primary MLS for the associated House. Put another way, the scope should produce all the Listings where the multiple_listing_service_id = primary_mls.id for the associated house.
I've tried dozens of nested joins scopes, and none seem to work. At best they just return all the Listings, and normally they fail out.
Any ideas?
If I understand correctly, I'm not sure a pure scope would be the way to go. Assuming you have:
class MultipleListingService < ActiveRecord::Base
has_many :listings
has_many :zip_codes
end
I would go for something like:
class House < ActiveRecord::Base
...
def associated_listings
primary_mls.listings
end
end
Update 1
If your goal is to just get the primary listing then I would add an is_primary field to the Listing. This would be the most efficient. The alternative is a 3 table join which can work but is hard to optimize well:
class Listing < ActiveRecord::Base
...
scope :primary, -> { joins(:houses => [:zip_codes])
.where('zip_codes.multiple_listing_service_id = listings.multiple_listing_service_id') }

Overriding a has_many association getter

A user can have several cars -
User: has_many :cars
Car: belongs_to :user
Every time I call #user.cars it returns the list of cars in default search order.
If I wanted the association sorted on some arbitrary field, I could do
class User < ActiveRecord::Base
has_many :cars, -> { order :num_wheels }
end
But let's say my ordering logic is complex and I want to just override the association getter to implement my own logic
I try something something like -
class User < ActiveRecord::Base
has_many :cars
def cars
# Pretend this is complex logic
cars.order(:num_wheels)
end
end
However that obviously fails because you can't reference the original cars from inside the overridden cars method without it looping infinitely.
Is there a way to reference the "original" getter from inside my overridden getter?
Thanks!
Use super:
class User < ActiveRecord::Base
has_many :cars
def cars
# Pretend this is complex logic
super.order(:num_wheels)
end
end
when you use a macro like has_many, Rails dynamically creates a module(which could be accessed by User.generated_association_methods).In your case, define the accessors and readers(such as "cars" in your case, which could be accessed by User.generated_association_methods.instance_methods). This module becomes the ancestor of your User class, so you can access the reader method(cars) by "super" in your own cars method.
With my understanding I believe what has_many is essentially doing is:
Class User < ActiveRecord::Base
has_many :cars
# is essentially
def cars
Car.where(user_id: self.id)
end
end
So when a user wants to list all the cars it would still be User.cars. When using ActiveRecord the has_many is assuming both the method name of cars and the foreign keys associated.
Try this:
class User < ActiveRecord::Base
has_many :cars
def cars
Car.where(user_id: id).order(:num_wheels)
end
end

Rails: Size is not working on a has_many relationship

I have two models, Student and Absence, with the relationship:
class Student < ActiveRecord::Base
has_many :absences
class Absence < ActiveRecord::Base
belongs_to :student
I want to see how many absences have been logged for each student, and show that total on the student's #show page. In the console, I'm able to query this by calling:
a = Student.find(1)
a.absences.size
However, I can't seem to get it to work in the app itself. Would the preferred way of querying this in the app be in the model or the controller?
This will never work, since you are calling association on the model, not instance. At first you should fetch a student and then count associated records:
Student.find(<id here>).absences.size
To aggregate this information for all students, you can add absence_count attribute accessor in Student model:
attr_accessor :absence_count
and then do something like this:
Student.includes(:absences).each{|s| s.absence_count = s.absences.size}
If you just need to output it in the view, then you can try the following:
Student.includes(:absences).each do |student|
puts student.absences.size
end

How to fetch data through "has_many" associations?

I have the following structure of models:
class User < ActiveRecord::Base
has_many :favorites
end
class Favorite < ActiveRecord::Base
belongs_to :user
belongs_to :company
end
class Color < ActiveRecord::Base
belongs_to :company
end
class Company < ActiveRecord::Base
has_many :colors
has_many :favorities
end
It means that a company has many colors. Every user can favorite a company (and I can print out then every color that the respective company offers).
But I am trying to display all colors that companies I've favorited offers.
I've tried it something like this:
favorited_colors = current_user.favorites.colors
undefined method `colors' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Favorite:0x007fe037da01f0>
and
favorited_colors = current_user.favorites.companies.colors
undefined method `companies' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Favorite:0x007fe038ce8db0>
Is there any other way how to get a list of all colors from favorited companies than to iterated via each loops through all favorited companies and save all colors into an array?
Thank you
This should work from rails 3.1 on:
class User
has_many :favorites
has_many :companies, :through => :favorites
has_many :favorit_colors, :through => :companies
end
And then
favorited_colors = current_user.favorit_colors
This is not tested, though.
Collections
The problem you have is that you're calling the companies method on a collection object
The issue here is that you can only call methods on single instances of objects. This is demonstrated quite well with the error you're seeing:
undefined method `companies' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Favorite:0x007fe038ce8db0>
The way you should attempt to get this to work is to call the following:
current_user.favorites.each do |favourite|
= favourite.colors
end
I understand what you're looking to achieve, so let me give you some ideas:
Association Extensions
There's a functionality of Rails called ActiveRecord Association Extensions, which could give you the ability to provide what you're seeking:
#app/models/user.rb
class User < ActiveRecord::Base
has_many :favorites do
def colors
# Business logic for colours here
end
end
end
Now this is totally untested. However, I have experience with this deep part of Rails, and I therefore know that you'll have the following data-sets to work with:
proxy_association.owner returns the object that the association is a part of.
proxy_association.reflection returns the reflection object that describes the association.
proxy_association.target returns the associated object for belongs_to or has_one, or the collection of associated objects for
has_many or has_and_belongs_to_many.
From this, you'll be able to construct an array / object based off the colors you wish to show:
has_many :favorites do
def colors #totally untested
colors = {}
favorites = proxy_association.target
favorites.each do |favorite|
favorite.company.colors.each do |color|
colors << color
end
end
return colors
end
end

One relation for two models

I was trying to find answer on my question, but didn't success with it.
I have models Event, participants, participation_form, invitation, user.
Event has_many participants
User has_many invitations
User has_many participation_form
For Participant I want to have field like "based_on" and it will be references with invitation or participation_form.
I have one idea about it - make two fields and one model method that will be check which field contains value and return "based_on"
My question is - is there any way to reference one model to two models with pair of fields - class (model name) and value (id) so I will add another type if I need it in future.
You could use polymorphic associations for that: (http://guides.rubyonrails.org/association_basics.html#polymorphic-associations
Could you tell more about models relations so I can write some example? Why do you need Participant model?
As mentioned byKuba Ploskonka, you'll probably benefit from a polymorphic association here:
--
Setup
For Participant I want to have field like "based_on" and it will be references with invitation or participation_form.
As per your specifications, you'll want to use the following:
#app/models/participation.rb
Class Participation < ActiveRecord::Base
belongs_to :participle, polymorphic: true
end
#app/models/invitation.rb
Class Invitation < ActiveRecord::Base
has_many :participations, as: :participle
end
#app/models/participation_form.rb
Class ParticipationForm < ActiveRecord::Base
has_many :participations, as: :participle
end
This will give you the ability to save your objects as follows:
#app/controllers/invitations_controller.rb
Class InvitationsController < ApplicationController
def create
#invitation = Invitation.new invitation_params
#invitation.participations.build #-> will save a blank "Participation" object
#inviation.save
end
end

Resources