Rails - get all possible values in a has_many through association - ruby-on-rails

I have a very simple data model, as follows:
class Object < ActiveRecord::Base
has_many :object_tags
has_many :tags, through: :object_tags
end
class Tag < ActiveRecord::Base
has_many :object_tags
has_many :objects, through: :object_tags
end
class ObjectTag < ActiveRecord::Base
belongs_to :object
belongs_to :tag
end
Both Object and Tag have a name attribute.
What I want to do is, for a defined set of objects #objects, get an array of all the possible tags names that my set of objects can take. I would like to be able to do something like that (which is wrong, but just for illustration purpose) : #objects.pluck(:tags.name).uniq
I have tried quite a lot of things, with includes and joins, but nothing gets me to my result. The closest I got was #objects.includes(:tags).pluck(:tags), but it only gets my the array of tags, then I can't retrieve the name.
This might be a very straightforward question, but I am a beginner in Rails and could not find anything in my research.
Thanks for you help!

You can do it the following way:
#object.tags.uniq.pluck(:name)

Related

ActiveRecord: has_many choices limited to has_many of another model

I would like to achieve something as follows where PersonSubject has many topics, but the choices of these topics are limited to the the selection of topics through another model (ie: through the associated subject):
class Topic < ApplicationRecord
belongs_to :subject
end
class Subject < ApplicationRecord
has_many :topics
end
class PersonSubject < ApplicationRecord
belongs_to :person
belongs_to :subject
has_many :topics # where the choices are limited to the subject.skills
end
I would then like if any person_subject.subject.topics are deleted (or association removed), it would automatically update the person_subject.topics to no longer "point" to the Topic(s) that were deleted.
Is this possible?
You can use a lambda to put arbitrary filters on an association. See What is the equivalent of the has_many 'conditions' option in Rails 4?
has_many :topics, -> { where(skill: subject.skills) }
I don't know that this is exact code will work without seeing your schema (what is the data type of subject.skills, and how do you join this with topic?). But hopefully this gets you on the right track
edit
in response to your comment, I think
has_many :topics, through: :skills
would work

Rails 4: Turn complicated filter into a scope

I have a many-to-many / has-many-through relationship in my connecting my recipe model to my tag model such that:
class Tag < ActiveRecord::Base
has_many :taggings
has_many :recipes, through: :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :tag
belongs_to :recipe
end
class Recipe < ActiveRecord::Base
has_many :taggings
has_many :tags, through: :taggings
end
...is there any way to filter for recipes with the same tag through a scope? I'm new to scopes, but I find them much more useful than methods, and I can only achieve searching and filtering by tag name through a method.
For example this will get me all recipes tagged with a given name:
def self.tagged_with(name)
Tag.find_by_name!(name).recipes
end
You can basically convert most association-method-chains (though not all*) into a scope
eg I'd try this (note: not tested for bugs) and see how it turned out
scope :tagged_with, ->(name) { find_by_name!(name).recipes }
If that doesn't work, I'd try something like:
scope :tagged_with, ->(name) { where(:name => name).first.recipes }
[*] The big issue with using scopes over method-chaining is that find/first can sometimes do weird things if it doesn't find one... the code in Rails literally defaults to the all scope in some cases (this is really weird behaviour that I think shouldn't happen) so for scopes that find just a single item, I will often not bother with a scope and use a class-method as you have originally.

how to handle "synonymns" in a has_many relationship

This is a bit of a sticky issue. I have a Rails association but need to add functionality to handle some special cases.
class Item < ActiveRecord::Base
has_many :notes
end
class Note < ActiveRecord::Base
belongs_to :item
end
The issue is that you could have something like this in db (identical items that might have a difference in sizing or quantity but are part of the desc and can't really be broken out due to accounting software):
items
id desc
1 a glass something
..
10 a bottle of something
..
20 a case of bottles of something
notes
id note item_id
3 "a note about something" 1
What I want to do is create a linke so that items 1,10, and 20, when they load notes, will all load notes with an id of 1
For example:
item1=Item.find(1)
item1.notes[0].id # 3
item10=Item.find(10)
item10.notes[0].id # 3
item20=Item.find(20)
item20.notes[0].id # 3
I feel like there should be a really basic way of doing this and was looking for suggestions. This is hacky but might work would be to write a dash separated list of other_ids into the notes table so that a notes.other_ids="-10--20-"
class Item < ActiveRecord::Base
has_many :notes
def sym_items
Note.where('other_ids like ?',"%-#{self.id}-%")
end
end
We really would only have to deal with this in a single scenario so a hacky sol'n woudl be ok but obviously would like better. Possibly do a has_many :through. I'm not sure about the latter - perhaps too much added complexity. Any help or advice would be appreciated.
thx
So I'm not sure I fully understand the situation. It seems like you might want something like this:
class Item < ActiveRecord::Base
has_many :notes
has_many :other_item_notes
has_many :other_notes, class_name: "Note", through: :other_item_notes, source: :note
has_many :other_items, class_name: "Item", through: :notes, source: :other_items
end
class Note < ActiveRecord::Base
belongs_to :item
has_many :other_item_notes
has_many :other_items, class_name: "Item", through: :other_item_notes, source: :item
end
class OtherItemNote < ActiveRecord::Base
belongs_to :item
belongs_to :note
end
The trick here is that you have a many-to-many association between items and notes (I assume you know how/why those other associations are supposed to work). Then we use the associations of those other items that we can now access to access their associated items. There may be complications with N+1 queries and such, but some of that can be dealt with by using inverse_of on your associations.
If you do it this way, you are querying against indexed id columns in your database searches, rather than a string comparison query like in your example above.

How can we do genuine rails 3 polymorphic has_many :through?

I want to have the following:
class Foo < AR
has_many :taggings
has_many :tags, :through => :taggings
end
class Bar < AR
has_many :taggings
has_many :tags, :through => :taggings
end
class Tagging < AR
belongs_to :tag
belongs_to :taggable, :polymorphic => true
belongs_to :user # etc
end
class Tag < AR
has_many :taggings
has_many :taggables, :through => :taggings
end
Tag.first.taggables # => [Bar:43, Foo:52, Foo:59, Bar:59, Foo:123, ...]
In particular, I don't want to have to specify Tag.foo_taggables, Tag.bar_taggables, etc. — that's the :source/:source_type method referred to in related questions, and it sucks.
I want it to just work properly with an array of disparate objects. Yes, I realize that they won't all have the same properties; that should be fine (I should be able to just rely on whatever interface all taggables do share, without caring about which kind I'm dealing with).
In particular e.g. tag.taggable_ids will probably have to be an array of id/type tuples, not just an array of int ids.
I'm using tags here just as an example — my actual problem has a different scenario but the same essential issue of polymorphic has_many :through.
In Rails 2, there was a plugin has_many_polymorphs that accomplished this, but it's defunct. Kronn's fork doesn't seem to work. Is there a functioning method to get this in Rails 3?
If you don't need #build, eager-loading it using .include or anything fancy like that, then you can use a simple method instead of an association:
class Tag < AR
has_many :taggings
def taggables
taggings.includes(:taggables).flat_map(&:taggables).uniq
end
end

Proper Rails Association to use

I am trying to create an association between two tables. A student table and a computer table.
A computer can only ever be assigned to one student (at any one time) but a student can be assigned to multiple computers.
This is what I currently have in mind. Setting up a has-many through relationship and modifying it a bit.
class Student < ActiveRecord::Base
has_many :assignemnts
has_many :computers, :through => :assignments
end
class Computer < ActiveRecord::Base
has_one :assignment
has_one :student, :through => :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :student
belongs_to :computer
end
Does this seem like the best way to handle this problem? Or something better sound out quickly to the experts here. Thanks!
You need first to decide if a simple one-to many relationship is enough for you.
If yes, it gets a lot easier, because you can get rid of the Assignment-class and table.
Your database-table "computers" then needs a student_id column, with a non-unique index
Your models should look like this:
class Computer < ActiveRecord::Base
belongs_to :student
end
class Student < ActiveRecord::Base
has_many :computers, :dependent => :nullify
end
"dependent nullify" because you don't want to delete a computer when a student is deleted, but instead mark it as free.
Each of your computers can only be assigned to a single student, but you can reassign it to a different student, for example in the next year.
Actually your approach is fine, as one offered by #alexkv. It is more discussion, than question.
Another thing if you want to use mapping table for some other purposes, like storing additional fields - then your approach is the best thing. In has_many :through table for the join model has a primary key and can contain attributes just like any other model.
From api.rubyonrails.org:
Choosing which way to build a many-to-many relationship is not always
simple. If you need to work with the relationship model as its own
entity, use has_many :through. Use has_and_belongs_to_many when
working with legacy schemas or when you never work directly with the
relationship itself.
I can advise you read this, to understand what approach better to choose in your situation:
http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
http://blog.hasmanythrough.com/2006/4/20/many-to-many-dance-off
You can also use has_and_belongs_to_many method. In your case it will be:
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers, :join_table => 'assignments',
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student, :join_table => 'assignments',
end
or you can rename assignments table to computers_students and remove join_table
class Student < ActiveRecord::Base
has_many :assignemnts
has_and_belongs_to_many :computers
end
class Computer < ActiveRecord::Base
has_one :assignment
has_and_belongs_to_many :student
end

Resources