How to join on nested belongs_to in Ruby on Rails - ruby-on-rails

I have three models, Application, which belongs to a Board, which belongs to a User.
class User < ActiveRecord::Base
has_many :boards
end
class Board < ActiveRecord::Base
belongs_to :user
has_many :applications
end
class Application < ActiveRecord::Base
belongs_to :board
end
I'm always only ever going to want to show the boards or applications for the current user. How can I say "show every application for the current board for the current user"? Basically how to query for something for specific parent id values.

You should provide the current user id and board id at first.
user = User.find(user_id) #get current user
board = user.boards.find(board_id) #get current board
board.applications #get every application
You can get more info from Rails Guide--Active Record Associations

show every application for the current board for current_user
The power of ActiveRecord should make this relatively simple:
board = current_user.boards.find params[:board_id]
board.applications #-> collection of Application objects for board
This assumes you're using devise, and thus have access to the current_user method.
If not, you'll be able to use something like the following:
#config/routes.rb
resources :users do
resources :applications #-> url.com/users/:user_id/applications
end
#app/controllers/applications_controller.rb
class ApplicationsController < ApplicationController
def index
#user = User.find params[:user_id]
#boards = #user.boards
end
end
#app/views/applications/index.html.erb
<% #user.boards.each do |board| %>
<%= board.name %>
<% board.applications.each do |application| %>
<%= application.name %>
<% end %>
<% end %>
<% end %>
ORM
I'll give you some context to this (hopefully it will help you in the future).
Firstly, you must understand that Rails is built on top of a relational database - or at least it should be. Relational databases use foreign_keys to give you access to associative data objects:
Whilst this might not seem important, it explains the entire functionality of the ActiveRecord system.
ActiveRecord is what's known as an ORM (Object Relationship Mapper)... in short, gives you the ability to populate objects with associative data from your relational database.
In the case of Ruby/Rails, the object orientated nature of ActiveRecord is a direct result of Ruby. That is, Ruby is an object orientated language; everything you do in it revolves around objects:
With Rails, those objects are built in the models.
Thus, if you want to understand how Rails works - you really need to get down to the model level -- you have to understand that calling Model.find x will build an object around that model, populating it with data from your database.
It's very clever.
The associations are provided by ActiveRecord, and pulled through the relational database infrastructure. This means that you can call the likes of #board = #user.boards.first and populate with the correct data.
Here's a good demonstration of how it works:

I'd share about how running query with ActiveRecord::Relation. To know about query, no matter you want to show data that far away from the current table but when those have associations, those could be connected.
Step by step to do query:
Determine all relations tables
Here, you have to determine tables that related to its table associations. In this case: users, boards, and applications.
Determine the condition
You can put current_user is a condition. You need users, boards, applications
So the condition is:
User.joins(boards: :applications).where("users.id = ?", current_user.id)
NOTE:
I would try to explain. User joins boards because user has many boards. Next boards has many applications so we have to join boards with application into boards: :applications.
This is good explain for query has many through associations. activerecord-query-through-multiple-joins

Related

Accessing the associated join model when iterating through a has_many :through association

I have a feeling this is a pretty basic question, but for some reason I'm stumped by it (Rails newbie) and can't seem to find the answer (which may be I'm not searching properly).
So I have a basic has_many :through relationship like this:
class User < ApplicationRecord
has_many :contacts, through :user_contacts
class Contact < ApplicationRecord
has_many :users, through :user_contacts
In users/show.html.erb I'm iterating through a single user's contacts, like:
<% #user.contacts.each do |c| %>
<%= c.name %>
<% end %>
Now inside of that each loop, I want to access the user_contact join model that's associated with the given user and contact in order to display the created_at timestamp that indicates when the user <--> contact relationship was made.
I know I could just do a UserContact.find call to look up the model in the database by the user_id and contact_id but somehow this feels superfluous. If I understand correctly how this works (it's entirely possible I don't) the user_contact model should have already been loaded when I loaded the given user and its contacts from the database already. I just don't know how to properly access the correct model. Can someone help with the correct syntax?
Actually the join model will not have been loaded yet: ActiveRecord takes the through specification to build its SQL JOIN statements for querying the correct Contact records but effectively will only instantiate those.
Assuming you have a UserContact model, you could do sth like this:
#user.user_contacts.includes(:contact).find_each do |uc|
# now you can access both join model and contact without additional queries to the DB
end
If you want to keep things readable without cluttering your code with uc.contact.something, you can set up delegations inside the UserContact model that delegate some properties to contact or user respectively. For example this
class UserContact < ActiveRecord::Base
belongs_to :user
belongs_to :contact
delegate :name, to: :contact, prefix: true
end
would allow you to write
uc.contact_name
First of all, the has_many :things, through: :other_things clause is going to look for the other_things relationship to find :things.
Think of it as a method call of sorts with magic built in to make it performant in SQL queries. So by using a through clause you're more or less doing something like:
def contacts
user_contacts.map { |user_contact| user_contact.contacts }.flatten
end
The context of the user_contacts is completely lost.
Since it looks like user_contacts is a one-to-one join. It would be easier to do something like this:
<% #user.user_contacts.each do |user_contact| %>
<%= user_contact.contact.name %>
<% end %>
Also since you're new to Rails it's worth mentioning that to load those records without an N+1 query you can do something like this in your controller:
#user = User.includes(user_contacts: [:contacts]).find(params[:id])
Use .joins and .select in this way:
#contacts = current_user.contacts.joins(user_contacts: :users).select('contacts.*, user_contacts.user_contact_attribute_name as user_contact_attribute_name')
Now, inside #contacts.each do |contact| loop, you can call contact.user_contact_attribute_name.
It looks weird because contact doesn't have that user_contact_attribute_name, only UserContact does, but the .select portion of the query will make that magically available to you on each contact instance.
The contacts.* portion is what tells the query to make all contact's attributes available as well.

Ruby on Rails 4 find object by id

I have the following show-view, where i display basic information about Product and display other User's Products.
<h1>Book <%= #product.name %></h1>
<% #products.each do |product| %>
<ul>
<%= product.name %>
<%= link_to "Make offer", {controller: "offers", :action => 'create', id: product.id } %>
</ul>
Controller
def show
#product = current_user.products.find(params[:id])
#products = Product.all
end
My goal is to make Offer between two Products.
I created Offer model and methods for making Offers:
class Offer < ActiveRecord::Base
belongs_to :product
belongs_to :exchanger, class_name: "Product", foreign_key: "exchanger_id"
validates :product_id, :exchanger_id, presence: true
def self.request(product, exchanger)
unless product == exchanger or Offer.exists?(product, exchanger)
transaction do
create(product: product, exchanger: exchanger, status: "oczekujace")
create(product: exchanger, exchanger: product, status: "oferta")
end
end
#other methods
end
Making offers is working, because I checked it in Console.
My problem is in OffersController:
class OffersController < ApplicationController
before_filter :setup_products
def create
Offer.request(#prod, #exchanger)
redirect_to root_path
end
private
def setup_products
#prod = current_user.products.find(1)
#exchanger = Product.find_by_id(params[:id])
end
end
Problem is with a following line (using link in show-page for products with different id's than 1 works):
#prod = current_user.products.find(1)
But I don't know how to find object in Db for actual product which my show-page shows. Not only for id = 1.
I don't know how to find this object in database.
I don't know the specific answer to your question, but perhaps if I explain what you need to look at, your solution will arise:
Find
Rails isn't magic - it uses ActiveRecord (which is an ORM - Object-Relation Mapper), which means every time you fire a query (through find), your ORM (ActiveRecord) will search the relevant database data for you
The problem you have is that although you're using the correct syntax for your lookup, you may not have a record with an id of 1 in your db.
current_user.products.find(1) tells ActiveRecord to scope the query around the current user, and their products. So you'll get something like like this:
SELECT * FROM 'products' WHERE user_id = '15' AND id = '1'
Objects
Further, you have to remember that Ruby (and Rails by virtue of being built on Ruby) is an object orientated language. This means that everything you load / interact with in the language should be based on an object
The problem you have is you're not associating your object to your Rails framework correctly. What I mean here is described below, but essentially, if you build your Rails framework correctly, it will give you the ability to associate your objects with each other, allowing you to call the various products you need from your offer
This is a simplistic way of looking at it, of course. You'll want to look at this diagram to see how it works:
Bottom line - try treating your application like a series of objects, rather than a logical flow. This will help you appreciate the various associations etc that you need to get it moving forward
Resources
You mention you can't show the product on your show page for an id other than one. I think the problem is really about how to get your show action to work.
If this is the case, let me explain...
Rails is resource-based, meaning that everything you do / create needs to be centred around a resource (object) of some sort. The problem is many people don't know this, and consequently complicate their controller structure for no reason:
Above is the typical "CRUD" routing structure for Rails-based resources. This should demonstrate the way that Rails will typically be constructed -- around resources
--
Further, Rails is built on the MVC programming pattern - meaning you need to use your controller to populate a series of data objects for use in your application.
To this end, if you load a resource, and want to populate it with resourceful information of another object - you need to make sure you have set up the data objects in a way to ensure you can look them up correctly, which either means passing the data through your routes or using a persistent data-type, such as cookies or sessions
The problem you have is you need to pass the product id to your controller somehow. How I'd do that is as follows (using nested resources):
#config/routes.rb
resources :offers do
resources :products #-> domain.com/offers/2/products
end
This will give you the ability to load the products controller with the variables params[:id] for the product, and params[:offer_id] for your Offer made available:
#app/controllers/products_controller.rb
Class ProductsController < ApplicationController
def show
#offer = Offer.find params[:offer_id]
#product = Product.find params[:id]
end
end

Find multiple database objects by attribute in Rails?

I have a Track table and a Section table. A track has many sections. Sections are connected to their respective task by the Section's :track_id, which corresponds to the Track's :id attribute.
<% #track = Track.find(params[:id]) %>
<% #sections = Section.find_by_track_id(#track.id) %>
In the code above I'm trying to find multiple sections that share the same :track_id attribute, but find_by_track_id() only returns the first. What's the best way to get all of them?
Thanks!
If your tracks and sections are related in this way, then the best way to relate them is by using the methods that come automatically from Rails' associations.
in this case, I expect in your model files, you have the following:
class Track < ActiveRecord::Base
has_many :sections
end
class Section < ActiveRecord::Base
belongs_to :track
end
Then you can get the sections for a track like this:
#track = Track.find(params[:id])
#sections = #track.sections
You're looking for where, which finds all records where a specific set of conditions are met.
#sections = Section.where(track_id: #track.id)
This is unrelated to your question, but you should set #sections and #track in your controller. As it seems like you're new to Rails, I'd highly recommend reading through the Rails Guides. They will help you immensely on your journey.
EDIT: I was solving for the general question of "Find multiple database objects by attribute in Rails?", which is how to find multiple database objects in the general case. #TarynEast's method is the way to go to find all of the sections for a track, or more generally, all of the objects that belong to the desired object. For the specific case you're asking for above, go with #TarynEast's solution.
Association
To extend Taryn East's answer, you need to look into ActiveRecord Associations.
In your model, if you have the following has_many relationship:
#app/models/track.rb
Class Track < ActiveRecord::Base
has_many :sections
end
#app/models/section.rb
Class Section < ActiveRecord::Base
belongs_to :track
end
This will set up a relational database association between your tracks and sections datatables.
--
Associative Data
The magic of Rails comes into play here
When you call the "parent" object, you'll be able to locate it using its primary key (typically the ID). The magic happens when Rails automatically uses this primary_key as a foreign_key of the child model - allowing you to call all its data as an append to the parent object:
#track = Track.find params[:id] #-> find single Track by primary key
#sections = #track.sections #-> automagically finds sections using the track primary key
This means if you call the following, it will work exactly how you want:
#sections.each do |section|
section.name
end
Where
Finally, if you wanted to look up more than one record at a time, you should identify which ActiveRecord method you should use:
find is to locate a single record by id
finy_by key: "value" is to locate a single record by your defined key/column
where is to return multiple items using your own conditions
So to answer your base line question, you'll want to use where:
#sections = Section.where track_id: params[:id]
This is not the right answer, but it should help you
<% #sections=#track.sections%>
Use find when you are looking for one specific element identified by it's id.
Model.find is using the primary key column. Therefore there is always exactly one or no result.

Check if a User has a Game

I've done some searching here and I've not been able to find anything that quite answers what I'm looking for. If I failed in my search I apologize.
Moving on, I am new to Rails and I'm working on an application to test the waters if you will. I'm using Devise for authentication and it's proving quite useful. That said, I've run into a big of a road block with where a certain check for data would go, and how I would go about it.
I have three tables: users, games, and users_games (I read that this was an acceptable naming convention for relational tables, correct me if I'm wrong). On a Games page I would like to display a certain message if the currently logged in User has this Game added to their account (in users_games). I'm unsure of where to perform this check, or if it even matters at all.
As for the actual checking, my initial idea would be something along the lines of:
games_controller.rb
class GamesController < ApplicationController
def index
#games = Game.all
end
def show
#game = Game.find(params[:id])
#user_owns = UsersGames.where(:game_id => #game.id, :user_id => current_user.id)
end
end
Then on the view checking if #user_owns has a value or not.
Thanks in advance for any insight or wisdom you can offer.
What about this way, may be you don't need users_games
if game has_many users and user belongs_to game
def show
#game = Game.find_by_user_id(current_user.id)
end
Then on the view checking if #game has a value or not.
If your Users<->Games relationship is a simple HABTM with no additional attributes on the join table, i.e.
class User < AR::Base
has_and_belongs_to_many :games
class Game < AR::Base
has_and_belongs_to_many :users
you don't need to have a separate model for the join table, provided that you follow the Rails naming convention that requires you to follow the lexicographical order when naming your join table, i.e. in your case it would be games_users, not the other way around like you have it now.
Going back to your original question, I think it can be as simple as this:
def show
#game = Game.find(params[:id])
#game_owned = current_user.games.include? #game
end
You can also make this a method on your User model:
class User < AR::Base
...
def owns_game?(game)
self.games.include?(game)
end
end
and then call current_user.owns_game?(#game) in your controllers/views.

Scoping the find method and nested assocations

I have run into an issue regarding how to identify which user owns particular resources so that I can prevent inappropriate access to them.
I have the following nested associations:
User has many
Profiles has one
SamplePage has many
Subjects
Once they become nested this deep it's become very unwieldy to access the user object via the associations and then compare that to current user e.g.:
#subject.sample_page.profile.user == current_user
I've read that a better way of restricting access is to scope the retrieval of a model to the current user. e.g:
#profile = current_user.profiles.find(params[:id])
That makes a lot of sense to me but how would I do a similar thing to get a Subject back? I've not found any examples that used nested associations.
not sure to understand what you want to do, and not sure i can help you since i'm a huge noob, but i would try something like this (assumed that current_user returns a User):
class Profile < ActiveRecord::Base
has_many :subjects, :through => :sample_pages
end
and in your controller:
#subject = current_user.profiles.subjects.find(params[:id])
more handy this way:
class User < ActiveRecord::Base
def subjects
profiles.subjects
end
end
#subject = current_user.subjects.find(params[:id])
all of this should be lazy loaded, as explained here : http://asciicasts.com/episodes/202-active-record-queries-in-rails-3
however, if it is a frequent operation, you may want to redesign things a bit, as long chains of associations mean heavy queries (lots of joins).

Resources