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.
Related
I think this is a lot simpler than the title probably lets on. Here are my three models with the associations:
Update: associations were incorrect previously. Here are the corrected associations:
#app/models/call_service.category.rb
class CallServiceCategory < ActiveRecord::Base
has_many :call_services
end
#app/models/call_service.rb
class CallService < ActiveRecord::Base
belongs_to :call_service_category
has_many :calls
end
#app/models/call.rb
class Call < ActiveRecord::Base
belongs_to :call_service
end
So I have a group of call_ids for the calls I want:
#call_ids = [1,2,3,4]
Initial step which works:
What I want to do is grab only the calls with the ids specified in #call_ids. Then, I want to eager load only the associated call_services for those grabbed calls. The following does this perfectly:
#call_ids = [1,2,3,4]
#calls_by_service = CallService.includes(:calls).where("calls.id IN (?)", #call_ids).references(:calls)
This is great. Now I can iterate through only those selected calls' call_services, and I can even list all of those selected calls per service like so:
<% #calls_by_service.each do |call_service| %>
<p> <%= call_service.description %> </p>
<% call_service.calls.each do |call| %>
<%= call.name %><br>
<% end %>
<% end %>
What is great about this too is that #calls_by_service does not contain ALL of the call_services, but instead only those call_service records associated with the calls specified in #call_ids. Exactly what I want at this level.
One Level Deeper which is where I am having trouble:
This is great but I need to go one level deeper: I want to display only the associated call_service_categories for the associated call_services of those selected calls specified by #call_ids.
In other words: I want to grab only the calls with the ids specified in #call_ids. Then: I want to eager load only the associated call_services for those grabbed calls. Then: I want to eager load only the associated call_service_categories for those grabbed calls.
A visual of the structure is like this:
So I want to be able to iterate through those associated call_service_categories (ex: 'Emergency Relief', 'Employment'), and then iterate through the associated call_services of those calls specified in the #call_ids, and then display those calls per service.
I figured out one level (by call_service), now I just need to figure out one level deeper (by call_service_category).
In the rails guides, I attempted looking at the section on specifying conditions on eager loaded associations. I was not having success, but I think the answer is in that section.
Any help is much appreciated. Thanks!
One of the belongs_to associations (in CallService or Call) should be actually a has_one (one-to-one relationship – belongs_to on the one side and has_one on the other). Apart from that, you can try the following code to produce a chained query with left joins and retrieve fields from all 3 tables:
CallServiceCategory.includes(call_services: :calls)
.where(calls: {id: #call_ids})
.references(:call_services, :calls)
I've noticed that you have a through association on your CallServiceCategory model, but as there would be no :call_services in includes, you can't reference fields from CallService model in references (you can, but they just won't appear in the actual sql query).
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
I am in a situation where I need products and its accessories in the same object. I got the products with following code:
#products = Product.all
This is fine. For getting accessories of each product in the same object, I did following:
#products.each do |product|
#product.accessories = Product.find_by_parent_id(product.id)
end
I tried this but when I call #products, I have only products, no accessories.
In the model, I have called
attr_accessor :accessories
def accessories=(test)
accessories = test
end
I am not getting any error but unfortunately I am not getting accessories as well. Is this the right way to get accessories? or is there any other way to achieve this? If I am doing it right way, then what mistake am I doing?
Thanks in advance.
I think you need to rely more on the database because Rails is just made for that and provides so many methods out of the box for it, methods that you can avoid reinventing.
Since each one of your products references other products, it's like a self join. Its accessories are just other products.
So first you need to add a product_id to the product model table (with a migration). This product_id will be used to reference "parents".
Then try putting something like this in your Product model:
has_many :accessories, class_name: "Product", foreign_key: :product_id
belongs_to :parent_product, class_name: "Product", foreign_key: :product_id
So then when you have a product in your hand, if it's an accessory, you just go #product.parent_product to get the parent, and if you have a "parent" in your hand, you just go #product.accessories and it will return an array of them.
Since you might be loading accessories pretty often from the parent product, add an includes statement to it to eager load the accessories automatically.
Learn more about the "out of the box" methods here. These include easy and straight forward ways of adding and removing "accessories" from a "parent", etc. (like #product.acceesories = [array of accessories])
Actually, when you define attr_accessor in a Ruby class you don't need to override set method for an attribute unless you have a specific behavior. So attr_accessor :accessories would be enough, it will create both: get and set method.
But your mistake, I guess, is that you have to write data to #accessories variable instead of accessories as the attributes' values are stored in a instance variable which starts with #
I've been looking, and can't find a good answer for how to delete records in a HABTM table. I assume a lot of people have this same requirement.
Simply, I have Students, Classes, and Classes_Students
I want a student to be able to drop a class, or delete the HABTM record that has signed that student up for that class.
There must be a simple answer to this. Does anyone know what it is?
The reason why .destroy or .delete does not work on this situation is due to the missing primary key in the middle table. However, our parent objects have this really cool method called {other_obj}_ids. It is a collection of ids on the left table object, of the right table object. This information is of course populated from our middle table.
So with that in mind, we have 2 object classes (Student, and Classes). Active record magic can generally figure out the middle table if you are not doing anything fancy, but it is recommended to use has_many :through.
class Student < ActiveRecord::Base
has_and_belongs_to_many :classes
end
class Classes < ActiveRecord::Base
has_and_belongs_to_many :students
end
What we can now do in terms of the middle table with this setup...
student = Student.find_by(1)
student.classes # List of class objects this student currently has.
student.class_ids # array of class object ids this student currently has
# how to remove a course from the middle table pragmatically
course = Course.find_by({:name => 'Math 101'})
# if this is actually a real course...
unless course.nil?
# check to see if the student actually has the course...
if student.class_ids.include?(course.id)
# update the list of ids in the array. This triggers a database update
student.class_ids = student.class_ids - [course.id]
end
end
I know this is a little late to answer this, but I just went through this exact situation tonight and wanted to share the solution here.
Now, if you want this deleted by the form, since you can now see how it is handled pragmatically, simply make sure the form input is nested such that it has something to the effect of:
What kind of trouble are you having? Do you have the appropriate :dependent=>:destroy and :inverse_of=>[foo] on your relations?
Let's say a class had a course title. You can do:
student.classes.find_by_course_title("Science").delete
So the proper answer here is to do something like this in your view:
<%= link_to 'Remove', cycle_cycles_group_path(#cycle, cycle), method: :delete %><br />
cycle is from a block the above code is within.
#cycle is an instance variable from the join models controller.
cycle_cycles_group_path is the nested join table "cycles_groups" under the model "Cycle" in the routes.rb file:
resources :cycles do
resources :cycles_groups do
end
end
and the join model controller looks like this:
def destroy
#cycles_group = CyclesGroup.find(params[:id])
#cycle = #cycles_group.cycle
#cycles_group.destroy
puts "cycle: #{#cycle}"
respond_to do |format|
format.html {redirect_to cycle_path(#cycle), notice: 'Training Week was successfully removed!'}
end
end
I am feeling a bit slow when it comes to rails and the Active Record associations... I have two tables.
Table = Rings
Table = Variations with foreign_key => "ring_id".
class Ring < ActiveRecord::Base
has_many :variations
end
class Variation < ActiveRecord::Base
belongs_to :ring
end
So in my "index/list" view i want to display all the rings, and both the variations, and i was thinking it would be possible to do this through one SQL query... however, i have tried the join and the include methods and i think i am just not understanding how they work properly.
So my question is, how would i write a query in my controller, that would pull my "title" and "value" column values from the "variations" and combine them into one simple object for easy looping? Or do i have to loop through all rings and look up the variation values during the loop?
thanks
In your controller:
#rings = Ring.includes(:variations).all
In index.html.erb:
#rings.each do |ring|
...
ring.variations.each do |variation|
...
end
end
The includes portion of the query will prevent Rails from repeatedly querying the database as you loop through and render your rings and variations in the view.
You need to use the includes method: Ring.inclues(:variations). Then the variation will be loaded along with the rings in a single SQL query.
For more info: http://guides.rubyonrails.org/active_record_querying.html#eager-loading-associations