-
Notifications
You must be signed in to change notification settings - Fork 190
Has many through supports aliased names #329
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Has many through supports aliased names #329
Conversation
kbrock
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this looks like a nice change.
- potential shortening of has_many local variable name
- simplification of tests
- squashing the commits
(though you may want to squash commits, make my suggested change in a separate commit - in case we decide it doesn't make it any better)
| belongs_to :physician | ||
| belongs_to appointment_to_physician_association_name, | ||
| **appointment_to_physician_association_options |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to just define 2 different associations here instead of passing in these parameters/metadata? Or maybe pass a boolean and define one belongs_to vs the other?
Not sure if it is possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed there's room for improvement here, i'll poke at this a bit more.
lib/associations/associations.rb
Outdated
| active_hash_source_association_name = options[:source]&.to_s || association_id.to_s.singularize | ||
|
|
||
| through_klass = reflect_on_association(options[:through])&.klass | ||
| klass = through_klass&.reflect_on_association(active_hash_source_association_name)&.klass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, it reads better without the active_hash prefix. This is our code base, so for a method name, sure, but for local variables, it gets a little wordy. What do you think?
| active_hash_source_association_name = options[:source]&.to_s || association_id.to_s.singularize | |
| through_klass = reflect_on_association(options[:through])&.klass | |
| klass = through_klass&.reflect_on_association(active_hash_source_association_name)&.klass | |
| source_association_name = options[:source]&.to_s || association_id.to_s.singularize | |
| through_klass = reflect_on_association(options[:through])&.klass | |
| klass = through_klass&.reflect_on_association(source_association_name)&.klass |
join_model.send() would also need to be changed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ya, I'm down to make this change. My thinking was that there are two associations here in the has many through:, only one of which is to an active hash, so was trying to capture that. But, to your point, the existing code even in this same method uses local vars like klass not active_hash_klass. Makes sense to me to follow that.
c1cf6d5 to
a17e921
Compare
|
|
||
| describe "with the :source option" do | ||
| it "finds ActiveHash records through the join model" do | ||
| Patient.has_many :doctors, through: :appointments, source: :physician |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kbrock what do you think of this approach where we simply add the differently named associations and dont bother removing the "default" ones. I was kind of wanting to not have both associations present on the models in the spec contexts, it felt a bit nicer, but it's not quite necessary and doesn't really effect the functionality being tested.
One other thought is that I could take the approach here but also explicitly remove the associations and added methods, something like:
Appointment._reflections.delete('physician')
Appointment.class_eval do
define_method(:physician) { raise NoMethodError }
define_method(:physician=) { raise NoMethodError }
end
Appointment.belongs_to :doctor, class_name: 'Physician', foreign_key: :physician_id... but that seems like a lot for something that really doesn't "do anything"; specs will pass with or without it. Still, maybe worth it tho?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agreed. Just having a single association does feel cleaner.
I just worry that next year, developers will have to understand why this is being done every time reading this code.
Either way, a comment is our friend. Depending upon how you roll, something to this effect:
# NOTE: the physician association is handled by doctor and no longer a valid association. Removing it.
# NOTE: the physician association is handled by doctor and no longer a valid association. Please don't use in this testI think I prefer less code, but totally up to you.
| if options[:through] | ||
| klass_name = association_id.to_s.classify | ||
| klass = klass_name.safe_constantize | ||
| source_association_name = options[:source]&.to_s || association_id.to_s.singularize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not sure why, but I do feel this is much easier to read.
If you are having trouble with it, you can add back.
kbrock
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this.
Add a comment if that helps you.
Let me know when you are done and I'll merge
|
@kbrock I think I'm all set - thanks for shepherding this through!
I get this, but I ended up definitely going "more code" not less, and actually deleting the old associations. I liked that I could get an actual error messages in there easily enough which also kinda acts like a comment define_method(:physicians) { raise NoMethodError, "The #physicians association is removed in this spec, use #doctors" }But happy to change anything |
|
|
||
| describe "with the :source option" do | ||
| before do | ||
| # NOTE: Removing the Patient#physicians association and adding Patient#doctors |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
perfect
- Add i18n support active-hash#230 @ryu-sato - Add `column_names` method active-hash#311 @hatsu38 - Support Active Record 7.2 active-hash#317 @ashleyHutton - Support ruby 3.4 active-hash#328 @flavorjones - Add `:alias` to `has_many :through` active-hash#329 @alexgriff - Add Active Record 8.0 active-hash#324 @flavorjones - Fix Do not suppress load errors#309 @andreynering - Ensure `field_names` are all strings active-hash#312 @flavorjones - Hide private `add_default_value` active-hash#314 @kbrock - Fix `exists?(nil)` active-hash#320 @y-yagi - Enance Enum support active-hash#321 @hatsu38 - Updated docs active-hash#326 @y-yagi - Drop Active Record < 6.1. Ruby < 3.0 active-hash#324 @flavorjones
Added ===== - Add i18n support active-hash#230 @ryu-sato - Add `column_names` method active-hash#311 @hatsu38 - Support Active Record 7.2 active-hash#317 @ashleyHutton - Support ruby 3.4 active-hash#328 @flavorjones - Add `:alias` to `has_many :through` active-hash#329 @alexgriff - Add Active Record 8.0 active-hash#324 @flavorjones Fixed ===== - Fix Do not suppress load errors#309 @andreynering - Ensure `field_names` are all strings active-hash#312 @flavorjones - Hide private `add_default_value` active-hash#314 @kbrock - Fix `exists?(nil)` active-hash#320 @y-yagi - Enance Enum support active-hash#321 @hatsu38 - Updated docs active-hash#326 @y-yagi Removed ======= - Drop Active Record < 6.1. Ruby < 3.0 active-hash#324 @flavorjones
Bug/Problem
Currently, using a
has_many :throughto return a collection of ActiveHash records is limited to working successfully in the case where theassociation_idpassed tohas_manyexactly corresponds to the ActiveHash class name. It must be the pluralized version of the singularized class name.In this example,
Zoo#countriesworks as expected:But, If any of the association method names involved in the
has_many through:require some type of aliasing where the relationship to the ActiveHash class can't be deduced, the behavior inActiveHash::Associations::ActiveRecordExtensionswill not correctly define the ActiveHash version of the association reader method.Here,
Zoo#nationswould breakAnd here,
Zoo#countrieswould breakIt felt worth supporting these cases because it feels similar to how the
class_nameoption is correctly supported in abelongs_to_active_hashfor equivalent use cases around aliased association names.I made sure the use of
sourcematches how that option is passed in ActiveRecord itself (has_many throughs:usesource, and don't supportclass_name).Documentation
I looked for a place to document this new behavior in the README, but there wasn't an obvious place to add this without adding a whole new section or something like that. I can try to do so if that feels appropriate.
Specs
I added specs for the new supported cases. To reuse the same spec setup and ephemeral classes, but at the same time to be able to change the association definitions per spec context, I parameterized the
define_doctor_classesspec setup method. This adds some complexity in a way I don't love, but still felt better than alternate approaches. I'm game to rethink this, though, if it doesn't feel like the right solution.