has_one through has_many relation - ruby-on-rails

I have this has_many association
has_many :devices, through: :vehicles, foreign_key: :meid
I need last_device to be implemented as a has_one relation to use it with ransack gem and it can be implemented using a method like this
def last_device
devices.last
end
I've tried a few variants but I haven't managed to get it working
has_one :last_device, class_name: 'Device'

Actually I've done it by splitting it into two pieces
has_one :last_vehicle, -> { order('vehicles.created_at DESC') }, class_name: 'Vehicle'
has_one :last_device, through: :last_vehicle, source: :device, class_name: 'Device'

Don't think it's possible to do it the way you propose, 'cause you wouldn't have a correct setting method on that association.
The only thing I can think of (if you insist on working with ransack), is adding a last_device_id column to your model and then adding a relation like this:
has_one :last_device, class_name: Device
Though you'll need to update that column when a new device is added to a user's vehicle or the last one is removed.

Related

How to use ActiveRecord's usual has_many methods with polymorphic associations?

I'm trying to re-build some of my models to include polymorphic associations, but I'm running into quite a few hurdles.
Even something as simple as Tag.images << #crop_image returns an error:
ActiveModel::UnknownAttributeError - unknown attribute 'tag_id' for ImageTag.
Are there modifications I need to make to the collection<< method in order for it to properly use the :taggable_id and :taggable_type columns that are used with a polymorphic association?
Or are there mistakes in how I'm trying to set up the models? Rails 5 Way talks about specifying source and source_type options within the models and I've tried implementing those recommendations here.
image.rb
has_many :image_tags
has_many :tags, through: :image_tags, source: taggable, source_type: 'Tag'
image_tag.rb
belongs_to :image
belongs_to :taggable, polymorphic: true
def build_taggable(params)
self.taggable = taggable_type.constantize.new(params)
end
tag.rb
has_many :image_tags
has_many :images, through: :image_tags, source: :taggable, source_type: 'Image'
Aha! Turns out I was not configuring the has_many associations correctly. The as: :taggable option should be placed on the join table.
It should read:
image.rb
has_many :image_tags, as: :taggable
has_many :tags, through: :image_tags
tag.rb
has_many :image_tags, as: :taggable
has_many :images, through: :image_tags
I may still need to specify the source and source_type options later on, but this fix got back into the fray!
UPDATE 20200317:
Okay, so I feel like I understand the situation a bit better now. The use of a polymorphic with a through table kind of loses its luster a bit because each of the associated methods needs to be set up individually. And this is where the source and source_type options come into play.
So with the above, if I want to allow the user to be able to call image.tags I need to give the model more information so it knows what a .tags is actually meant to encompass. It looks something like this:
has_many :tags, through: :taggable, source_type: 'Tag'
Which is all well and good, but what if the user wants to call ALL of the different types of taggables in one fell swoop? As of right now, I don't know if this is possible. Maybe you could 'read-in' the unique values present in the column, then call them each in turn some how?
--OR!--
Maybe you bypass the activerecord query method stuff altogether and simply root through the join table with SQL queries? That may be a better approach...
But that may be beside the point for now... The only way I know to do this currently is to set up custom query methods for each type. So you'd end up with something like
has_many :people, through: :taggable, source_type: 'Person'
has_many :businesses, through: :taggable, source_type: 'Business'
And then you'd have to call them each individually then aggregate them together into one giant array like structure.

I have a questions about using :source when building associations in rails

I have a problem when setting up associations in my rails project and wonder if anyone can help me with that??
I have three models: user, comment and event. And there are two kinds of users: organization and volunteer. I have problem when I tried to make event.volunteers and volunteer.joined_events work...
Here are how the models set up:
class Comment < ApplicationRecord
    belongs_to :organization, class_name: "User"
    belongs_to :volunteer, class_name: "User"
    belongs_to :event
end
class User < ApplicationRecord
    has_many :organized_events, foreign_key: "organization_id", class_name: "Event"
    has_many :joined_events, through: :being_commented_comments, :source => :event
    has_many :commented_comments, foreign_key: "organization_id", class_name: "Comment"
    has_many :being_commented_comments, foreign_key: "volunteer_id", class_name: "Comment"
end
class Event < ApplicationRecord
    belongs_to :organization, class_name: "User"
    has_many :volunteers, through: :comments, source: "Volunteer"
    has_many :comments
end
And I keep getting errors like:
ActiveRecord::HasManyThroughSourceAssociationNotFoundError (Could not find the source association(s) "Event" in model Comment. Try 'has_many :joined_events, :through => :being_commented_comments, :source => <name>'. Is it one of organization, volunteer, or event?)
or
ActiveRecord::HasManyThroughOrderError (Cannot have a has_many :through association 'User#joined_events' which goes through 'User#being_commented_comments' before the through association is defined.)
and I think the problem happens because I am not familiar enough with :source...Any suggestions would be super appreciated! Thanks!
When using has_many through: you must declare the association you are going through before the indirect association:
class User < ApplicationRecord
has_many :organized_events, foreign_key: "organization_id", class_name: "Event"
has_many :being_commented_comments, foreign_key: "volunteer_id", class_name: "Comment"
has_many :joined_events, through: :being_commented_comments, source: :event
has_many :commented_comments, foreign_key: "organization_id", class_name: "Comment"
end

Rails cloning with amoeba gem

I'm having issues with the referencing of a specific model when being cloned. I've tried using multiple approaches but none have gotten the correct result, and I believe it has to do with me not understanding how the logic of cloning works.
This involves multiple models, but the error raises in an interdependency between the last 2 models in the hierarchy. For simplicity, I'll try to explain this with 3 models to get an idea of how everything's working: Page, PageElement and PageElementDependency
A Page has many PageElements
A PageElement has many dependent_page_element_dependencies and has_many dependee_page_element_dependencies, where both of these relations are PageElementDependency. Through both of these dependencies I've setup the following has_many through relationships: has_many :dependees, through: :dependent_page_element_dependencies and has_many :dependents, through: :dependee_page_element_dependencies. Summarizing everything looks like this:
#models/page.rb
has_many :page_elements, -> { order(position: :asc) }, inverse_of: :page, dependent: :destroy
amoeba do
enable
clone [:page_elements]
end
#models/page_element.rb
has_many :dependent_page_element_dependencies, foreign_key: :dependent_id, inverse_of: :dependent, class_name: "PageElementDependency", dependent: :destroy
has_many :dependees, through: :dependent_page_element_dependencies
has_many :dependee_page_element_dependencies, foreign_key: :dependee_id, inverse_of: :dependee, class_name: "PageElementDependency", dependent: :destroy
has_many :dependents, through: :dependee_page_element_dependencies
amoeba do
enable
clone [:dependee_page_element_dependencies, :dependent_page_element_dependencies]
end
#models/page_element_dependency.rb
belongs_to :dependent, class_name: "PageElement", inverse_of: :dependent_page_element_dependencies, foreign_key: :dependent_id
belongs_to :dependee, class_name: "PageElement", inverse_of: :dependee_page_element_dependencies, foreign_key: :dependee_id
Let's say I'm trying to clone Page#3, with PageElement#4 and PageElement#5, where there's a PageElementDependency, where the dependent is PageElement#4 and the dependee (who is depended upon) is PageElement#5.
So basically here we'd be able to access PageElement.find(5).dependees => PageElement#6, and if I do PageElement.find(6).dependents => PageElement#5.
So, when trying to clone Page#3, I'd expected to get a new Page#4 with PageElement#6 and PageElement#7, and here's the important part, a PageElementDependency where the dependent is PageElement#6 and the dependee is PageElement#7.
However, when I try cloning the PageElementDependencies, only one of the columns is getting a new value, but the other column is keeping the old PageElement's value. So in the example given, I'd get a PageElementDependency where the dependee is correct: PageElement#6, but the dependent is not update and stays as PageElement#5.
I'm not sure how to use a remapper in order to get the related "new" and "old" object of the dependencies, so I'm not really sure how to proceed.
I've also posted this as an issue in github: https://github.com/amoeba-rb/amoeba/issues/84

Rails activerecord joins update_all attribute of joined table

I have the following relations:
reservation.rb
has_many :room_requests, dependent: :destroy, inverse_of: :reservation
accepts_nested_attributes_for :room_requests, allow_destroy: true
has_many :rooms, through: :room_requests
room_request.rb
belongs_to :reservation
belongs_to :room
room.rb
has_many :room_requests
has_many :reservations, through: :room_requests
And I'm trying update the attribute 'status' from rooms that belong to certain reservations. Something like:
Reservation.joins(:rooms).update_all('rooms.status': 'to_clean')
But evidently it doesn't work like this. I want to do it in a single query but I can't quite grasp it. What am I missing?
In the end I couldn't do the query that way because what I needed to update was on the Room model, and the query was being done in the Reservation model. I needed to reverse it, and came up with this:
Room.where(id: RoomRequest.where(reservation: Reservation.checked_in).select(:room_id)).update_all(status: Room.statuses[:to_clean])
You should link your Reservations model also with Rooms via a has_many, through: room_requests..
Once done you can easily go with:
Reservations.rooms.status.update_all('rooms.status': 'to_clean')
However, make sure that your room_requests are available and that reservations are thus assigned to rooms via the room_requests.
Cheers,
T

Default sort by associated field with reverse_relationship

A miniature may have many contents.
class Miniature < ActiveRecord::Base
has_many :contents, foreign_key: "setmini_id", dependent: :destroy
has_many :minisets, :through => :contents, source: :miniset
has_many :reverse_contents, foreign_key: "miniset_id", class_name: "Content", dependent: :destroy
has_many :setminis, :through => :reverse_contents, source: :set mini
On a miniature's show view I currently list it's contents. What I want to do is add a default sort scope to my Contents model so that it sorts by name.
class Content < ActiveRecord::Base
default_scope { order('name ASC') }
belongs_to :miniset, class_name: "Miniature"
belongs_to :setmini, class_name: "Miniature"
My attempt here fails and complains "No such column name:".
With a normal has_many_through relationship this would work but I'm guessing because I'm using the join table in two directions and declaring class_name: "Miniature" this doesn't work here.
Is there a way I can get this list to default sort by name?
I've tried "content.name" and "setmini.name" to no avail.
You'll need the name of the database table in your order clause (not the model or association name). If you're following Rails' conventions for naming, this would be:
order('contents.name ASC')
After looking at some other answers to similar questions I eventually got it working with the following:
default_scope joins(:setmini).order('miniatures.name ASC')

Resources