What new method does Rails' "has_many :through" generate? - ruby-on-rails

I am establishing an associated relationship between models for house buying and selling. User will have plural Housing agencies and plural Houses, and conversely, House will also have plural Housing agencies and plural Users. The specific models are as follows:
model User
has_many :agencies
has_many :houses through: :agencies
model Agency
belongs_to :user
belongs_to :house
model House
has_many :agencies
has_many :users through: :agencies
I want to know, if I want to query the users who have joined the agency, can I do this
House.where(id: 10).agency_users
That is, will the agency_users method be generated? If yes, where is the official teaching document written? If not, how should it be fixed?
thanks

To start with you have an error in the definition of your assocation - through is a keyword argument so you need to add a colon:
has_many :houses through: :agencies
What new method does Rails' "has_many :through" generate?
It generates a whole bunch of methods:
| | | has_many
generated methods | habtm | has_many | :through
----------------------------------+-------+----------+----------
others | X | X | X
others=(other,other,...) | X | X | X
other_ids | X | X | X
other_ids=(id,id,...) | X | X | X
others<< | X | X | X
# ...
I don't know where you got agency_users from but its definately not going to be generated from any of the code in your question as its based on the name argument (the first one).
You're also making a classic rookie misstake and calling an assocation method on an ActiveRecord assocation. where returns multiple records while the assocations create instance methods. Use find(10) or find_by(id: 10) (if it should be allowed to be nil).
If yes, where is the official teaching document written? If not, how should it be fixed?
I'm not sure what you're actually asking here or what "official teaching document" is supposed to mean. Rails has both API documentation and guides.
If you're talking about documenting your own code it depends on if you're using RDoc or Yard. Neither will actually automatically pick up the methods generated by the assocation macros as far as I know but you can do it manually in Yard with:
# #!method foo
I don't know if RDoc has a similiar feature so you might to just document the assocations in the overall class description.
These assocations are not what you really want
The main problem here is that you're putting house_id on the agencies table which means that it can only ever record a single value per agency! Thats not going to be good for buisness.
What you actually want is something more like
class Listing
belongs_to :user
belongs_to :agency
end
class Agency
has_many :listings
has_many :clients,
through: :listings,
source: :user
end
class User
has_many :listings
has_many :agencies,
through: :listings
end
I would generally avoid the term House and go with the domain specific listing which can cover many different kinds of properties.

Related

Issue constructing a has_many relation with scope using multiple join tables

I have a tag system on multiple models that are linked together.
The system works like this:
A Top has many Middles
A Middle has many Lows
Tops, Middles and Lows have many Tags
A tag associated to Top level is supposed to qualify every Middle and Low associated to it.
Same goes for a tag that would be associated to a Middle, every Low associated to it would 'inherit' from the tags.
This mechanic is not on a database level, in the end in what concerns the database, Tops, Middles and Lows all have their own tag collection, and i initially implemented instance methods on each model so that when you call, for example, low_instance.all_tags, it concatenates the tag collections of it's parent Middles, and the one of its Top.
Here is what the models look like:
# ______________________________
# / \
# (1) (*)
# [Top] (1) __ (*) [Middle] (*) __ (*) [Low]
# (*) (*) (*)
# \_______________ | ______________/
# |
# *
# [Tags]
class Low < ApplicationRecord
has_many :low_tags, dependent: :destroy
has_many :tags, through: :low_tags
has_many :middle_foos, dependent: :destroy
has_many :middles, through: :middle_foos
end
class Middle < ApplicationRecord
belongs_to :top
has_many :middle_tags, dependent: :destroy
has_many :tags, through: :middle_tags
has_many :middle_lows, dependent: :destroy
has_many :lows, through: :middle_lows
end
class Top < ApplicationRecord
has_many :middles, dependent: :destroy
has_many :lows, dependent: :destroy
has_many :top_tags, dependent: :destroy
has_many :tags, through: :top_tags
end
### Join tables
class MiddleLow < ApplicationRecord
belongs_to :middle
belongs_to :low
end
class LowTag < ApplicationRecord
belongs_to :low
belongs_to :tag
end
class MiddleTag < ApplicationRecord
belongs_to :middle
belongs_to :tag
end
class TopTag < ApplicationRecord
belongs_to :top
belongs_to :tag
end
That actually works like a charm. The issue is that i want to be able to search my Lows with the awesome Ransack gem and using the full tag collection of a Low (its self tags, plus the ones inherited from the parent Middles and Top)
Problem: Ransack only works with ActiveRecord::Relations. So from Ransack's point of view, i can only search my Lows using their self-tags and not the full inherited collection as this does not exist on the database level.
The initial solution to this problem i wanted to implement is to add a "copy" full tag collection on the database level that updates with the rest and that i could use to search with Ransack.
But I'm sure i don't have to add anything to the database as all the info is already here in the join tables and i kind of don't want to duplicate that info which is not super cool i think and would make the code base less understandable.
I have seen potential solutions using has many with scopes like so:
has_many :all_tags, ->(low) {
unscope(.........).
left_joins(..........).
where(.........)
# Returs self tags (Low) + tags from associated Middles + tags from the Top
}
I'm pretty sure this would be the best solution, but I'm really not good when it comes to database querying especially with so much models and join tables. I get confused and can't seem to find what to put in that scope so that i get this full collection of tags.
So if anybody has a clue about that, any help would be greatly appreciated!
By the way, using Rails 6.1 and Ruby 2.7
So found the solution to the query I wanted to construct in the end.
The scope looks like following:
has_many :full_tags, lambda { |low|
where_clause = 'top_tags.top_id = ? or low_tags.low_id = ?'
where_args = [low.top_id, low.id]
if low.middles.any?
where_clause += ' or middle_tags.zone_id IN ?'
where_args << low.middle_ids
end
unscope(where: :low_id)
.left_joins(:middle_tags, :top_tags, :low_tags)
.where(where_clause, *where_args).distinct
}, class_name: 'Tag'
Calling xxx.full_tags on any instance of low returns the whole collection of tags from every middle it belongs to , plus those from the top it belongs to, plus its own tags, and the distinct makes it a unique collection.
That being said, that didn't fully fixed my problem because the whole purpose was to pass this scoped has_many relation as an attribute used by the Ransack gem to filter out my Low models, out of their full inherited collection of tags.
Big disappointment it was when i discovered that Ransack performs eager loading when it comes to search on associations:
Rails does not support Eager Loading on scoped associations
So i ended up implementing a whole other solution for my tagging system. But hey, i learned a lot.

How to relate User and Client Portfolio models in Active Record?

I'm writing rails client phone-book for a company that provides audit services. The company has a client portfolio and all these clients are divided among all the employees (managers, seniors and assistants). See the following diagram, for instance:
Manager
40 customers
|
________________________________
| |
Senior Senior
20 customers 20 customers
| |
__________________ _________________
| | | |
Assistant Assistant Assistant Assistant
10 customers 10 customers 10 customers 10 customers
*Total customer portfolio: 40 customers
The application will show to the user a list of phone numbers belonging to the client the user has assigned, for which I need to associate the User model with the Client model, the question is: which one of all the Active Record associations provides the best solution for this problem?
After reading the association basics described in Ruby on Rails Guides, I think I could use the has_many :through with a third model (Assignment), given that every client in the portfolio needs to have a manager, a senior and an assistant assigned to it (as it happens away from the keyboard).
My solution is the following:
class User < ActiveRecord::Base
has_many :assignments
has_many :clients, through: :assignments
end
class Assignment < ActiveRecord::Base
belongs_to :user
belongs_to :client
end
class Client < ActiveRecord::Base
has_many :assignments
has_many :users, through: :assignments
end
What do you think of the solution above? or which one would be a more elegant solution?
Create a table called Relationship (or something) with two foreign_ids concerning what you want the User and client to be able to do with one another (example to be able to "Follow" one another => Follower_id and Following_id). Define methods in your Models related to those ids and then call those methods in your views to display the relationship.

How to structure these models associations?

I am basically trying to create an app which allows a user to track what they are working on. So each user will have many projects.
Now each project can have a certain number of types e.g. videos, websites, articles, books etc. with each of these types of projects having many objects (i.e. a videos project would list all the videos they were working on, books project may have a list of books they read).
Each of these objects would have different variables (books might have author and length and websites may have URL, rank etc.)
How would I set up these models in rails?
So basically would I separate my project model from my type model (so project has_one type and type belongs_to project) and then have 4 separate object models (i.e. objectsite) for each of the different types?
Or is there a better way to design this.
Modelling Many Rails Associations
I read this question and I think this may be similar to what I want to do but am not entirely sure as I don't full understand it.
UPDATE**
So far I have:
Projects
has_one type
Type
belongs_to projects
has_many objects
object
belongs_to type
But I am not to sure if this is the best way to go about it because like I said each object for each type will be different
From what I could bring myself to read, I'd recommend this:
Models
So each user will have many projects.
#app/models/project.rb
Class Project < ActiveRecord::Base
belongs_to :user
end
#app/models/user.rb
Class User < ActiveRecord::Base
has_many :projects
end
Vars
Each of these objects would have different variables (books might have
author and length and websites may have URL, rank etc.)
There are two ways to interpret this
The first is if you know what details your different objects will require, you could include them as attributes in their respective datatables. If you don't know, you'll have to use another table to populate them.
I'll detail both approaches for you:
--
Known Attributes
As Pavan mentioned in the comments, you'll likely benefit from an STI (Single Table Inheritance) for this:
#app/models/project.rb
Class Project < ActiveRecord::Base
belongs_to :user
has_many :books, class_name: "Project::Books"
has_many :folders, class_name: "Project::Folders"
end
#app/models/object.rb
Class Object < ActiveRecord::Base
#fields - id | type | project_id | field1 | field2 | field3 | field4 | created_at | updated_at
belongs_to :project
end
#app/models/project/book.rb
Class Book < Object
... stuff in here
end
This will allow you to call:
project = Project.find(params[:id])
project.books.each do |book|
book.field1
end
--
Unknown Attributes
#app/models/project.rb
Class Project < ActiveRecord::Base
has_many :objects
end
#app/models/object.rb
Class Object < ActiveRecord::Base
belongs_to :project
has_many :options
end
#app/models/option.rb
Class Option < ActiveRecord::Base
#fields - id | object_id | name | value | created_at | updated_at
belongs_to :object
end
This will allow you to call:
project = Project.find(params[:id])
project.objects.first.options.each do |option|
option.name #-> outputs "name" of attribute (EG "length")
option.value #-> outputs "value" of attribute (EG "144")
end
This means you can use option to populate the various attributes your objects may require.

How to create the link for a many to many relation in Ruby on Rails?

I am trying to associate Rules and Records through a many to many association.
The linking model is called Validation.
I created the models as explained in Rails Cast #47.
In the Rules view, I list candidates Records (extracted through a query).
When I click on a Record, I want to create the Validation and have it initialized with rule.id and record.id.
How to define the link_to statement in the Record index (embedded in the Rule show page)?
How to define the new function of the validations_controller ?
Are are my models:
class Rule < ActiveRecord::Base
has_many :requests
has_many :records, through :requests
end
class Record < ActiveRecord::Base
has_many :requests
has_many :business_rules, through :requests
end
class Validation < ActiveRecord::Base
belongs_to :record
belongs_to :business_rule
end
The show view of the Rule looks like this:
RULE DEFINITION
Name:
Description:
Condition:
CANDIDATE RECORDS:
Identifier | Name | Description | Owner
1015 Ozone Molecule TEDE
1089 Lithium Atom RICO
110236 Dipeptide Molecule MARCO
...
So the link_to should be created on the Record identifier to create the Validation through the new function of the validations_controller. And the Validation form is to be displayed for the user to fill it.
I hope this is clear enough ...
Thanks for your help !
Best regards,
Fred
Im not sure I am understanding you 100% but Ill give it a shot. To get the resource ids for each of those you will want to nest the resources in your routes.rb file. This will allow you to construct complex URLs that contain the ids you need. Something like:
resources :rules do
resources :requests
end
Then if you run rake:routes you will see the url something like /rules/{id}/requests/{request_id}.

ActiveRecord, double belongs_to

I have 2 models: Link and User such as:
class Link < ActiveRecord::Base
belongs_to :src_user
belongs_to :dst_user
end
class User < ActiveRecord::Base
has_many :links
end
A schema could looking like:
+----------+ +------+
| Link | | User |
+----------+ |------+
| src_user |---->| |
| dst_user |---->| |
+----------+ +------+
My question is: how can I edit User model do in order to do this
#user.links # => [list of links]
(...which should query #user.src_users + #users.dst_users, with unicity if possible.)
Can we do this only using SQL inside ActiveRecord?
Many thanks.
(note: I'm on Rails 3.1.1)
You have to specify multiple relations inside the user model so it knows which specific association it will attach to.
class Link < ActiveRecord::Base
belongs_to :src_user, class_name: 'User'
belongs_to :dst_user, class_name: 'User'
end
class User < ActiveRecord::Base
has_many :src_links, class_name: 'Link', inverse_of: :src_user
has_many :dst_links, class_name: 'Link', inverse_of: :dst_user
end
The :class_name option must be specified since the association name is not simply :links. You may also need to specify the :inverse_of option in the Link model, but I can't be sure of that. It wouldn't hurt if you did, though.
In order to do your #user.links call, you'll have to do something like this:
class User < ActiveRecord::Base
def links
Link.where(['src_user = ? OR dst_user = ?', self.id, self.id])
end
end
… since ActiveRecord doesn't provide a way to merge two associations on the same model into one.
This sounds very similar to the child/mother/father problem, which has a solution. That is, a problem where an object of one type belongs_to more than one object of the same type (not necessarily the same type as the child, but the objects it belongs to are themselves the same type).
This solution is quite old, and may not be up to date with the latest style schemes in Rails3, but it should work fine, or with very little modification.
http://railsforum.com/viewtopic.php?id=1248

Resources