Trying to teach myself Rails and have come across something that was easy in PHP but have got confused and stuck on this one area.
I have a form with a number of drop downs. I have managed to get the drop downs to display a value and store the id of that value into another table. Now I would like to view the new record but instead of displaying the ID that I saved I would like to collect the value from the original table and display that.
I hope that makes sense.
Below is my current code.
Finding Model
class Finding < ActiveRecord::Base
belongs_to :skill
Skill Model
class Skill < ActiveRecord::Base
attr_accessible :skill_desc, :skill_level
has_many :findings
show.html.erb
<td>Finding Skill</td>
<td><%= #finding.skill_id %> </td>
I guess my question is what do I need to change the #finding.skill_id too, to show the skill_desc field from the skills model?
<%= #finding.skill.skill_desc %>
#finding.skill returns the Skill object associated with that finding, upon which you can call skill_desc to retrieve said value.
#finding.skill.skill_desc
If you wanted to find the findings of a skill, you would need to use
#skill.findings to return an array of the findings objects associated with that skill object.
In sql, it might look something like this
SELECT * FROM findings WHERE ID EQ 23 JOIN skills ON findings.skill_id = Skills.id
Related
First of all, I am still new to ROR and I am trying to come up with more efficient way to do a database query for my datatables.
My Model association
class Store < ActiveRecord::Base
has_many :surveys
has_many :customers
end
...
class Survey < ActiveRecord::Base
belongs_to :store
end
...
class Customer < ActiveRecord::Base
belongs_to :store
end
In my DataTables
<tbody>
<% #store.surveys.each do |survey| %>
<tr class="th-container table-fixer">
<td><%= find_customer_id(survey.email).nil? ? survey.first_name : link_to(survey.first_name, store_customer_path(#store, find_customer_id(survey.email))) %></td>
<td><%= get_ltv(survey) %></td>
</tr>
<% end %>
</tbody>
find_customer_id and get_ltv method as follows
def find_customer_id(customer_email)
BwCustomer.find_by(email: customer_email)
end
The problem with the code is that, currently I have over 1000 active record objects that I loop through, when find_customer_id method is hit it will find customer with the given email address and the query takes over 15 sec to process.
In my situation, what would be the best way to approach this?
solution that I have though about:
1. join the tables to so that I don't have to call another table
2. lazy load, only load the objects when needed
Some suggestion will be greatly appreciated
thank you
Your query by email ID should not take so much time.
Add index for the email column in Customers table (Refer this for adding indexes through Active record migrations - http://apidock.com/rails/v4.2.1/ActiveRecord/ConnectionAdapters/SchemaStatements/add_index)
Your code shows calling the find_customer_id twice. Do that once so only 1 database query is fired
You need not write a wrapper method - Customer.find_by_email(customer_email) also works
To optimize further, you can collect all the customer IDs you need to check for existence in database in one loop, and fire a single database query:
Customer.where(email: [list of customer emails])
The main problem is that you're missing an association between customer and survey. You could make one by joining on the email
class Survey < ActiveRecord::Base
belongs_to :customer, primary_key: :email, foreign_key: :email
end
but this is a somewhat sketchy approach. Does you application know the ID of the customer when they fill a survey? Or can these surveys be filled by anyone and you make the link if someone claims to have the same email as a customer?
In any case you need both the email columns to be indexed and if you make an association between the two you will be able to write the following in your controller code.
#store = Store.includes(surveys: :customer).find(params[store_id])
This will make a database query which eager loads all the surveys and customers that you're about to display so that inside the loop you can use survey.customer without calling a new query for each row.
I'm considering this an add-on question of sorts to the thread below:
Using join tables in ruby on rails
So we have 'Student' and 'Course' scaffolds joined by a many-to-many association, but in addition there is also a 'Semester' scaffold and what I wish to do is, for each student that is added to a course, for the application to search for previous iterations of the same course through past semesters, to that it's known how many times a student has taken that class before. I'm kind of mixed up at the moment as to how to implement this, so I was hoping someone could help me pin down the logic and code I should be operating by.
Some underlying assumptions I have so far:
'Course' and 'Semester' should, like 'Student' and 'Course', be joined
by a many-to-many association (many courses are taught per semester,
and a course is taught for more than one semester).
There should be an action (let's say get_student) within the course
controller to locate the student via student_id. This would be the main area I'm scratching my head as to what to do. What would this method look like in code?
Within the student-course join table I should have an attribute
'attempts' which increments each time get_student finds this
student_id combined with the course_id that calls the method.This
would be the mechanism that actually tells how many times the course
had been attempted by the student.
I initially wondered if there should be a 'semester' controller
action to activate get_student across all semesters, but now I'm
thinking that get_student should work fine without that.
Appreciate any help I can get on this. Thanks.
This is not a good answer, just a comment.
I would comment, but hear will be more clear. I ll update for the other points. This is just an ongoing feedback/discussion, not an answer.
class Semester < ApplicationRecord
has_many :courses
end
class Course < ApplicationRecord
has_many :students
end
And
semester.courses[0].students => outputs the students array for that
This could be the method to calculate the number of student that did that course:
def studentForCourse
#input_params.course_id => course id you are selecting
semester = Semester.find(input_params)
semester.courses.each do |course|
if course.id = input_params.course_id
nstudents = course.students.size
end
end
This is probably a really simple question, but I've been searching the web for probably around an hour and I can't really find an answer to my problem. It should be clear by what follows that I am very new to Rails, so my terminology and explanation might be a bit confusing.
Let's say that I were making a social media app on Rails, where one of the models is User. I want to make a many-to-many relationship called "friends", which links two users together. Let's say in this situation I also wanted to make a many-to-many between two users called "enemies".
This is all completely hypothetical, but the idea is the same one that I want to use for something I'm working on.
Because a user can have many friends and enemies, but also be many friends and enemies, I would use:
class User < ActiveRecord::Base
has_and_belongs_to_many :users #this should be the friends association
has_and_belongs_to_many :users #this should be the enemies association
end
Now I'm guessing I can't just do that, because I would have to have two tables both named users_users. So, then I switch to:
class User < ActiveRecord::Base
has_and_belongs_to_many(:users, join_table: 'friends',
foreign_key: 'user_id', associate_foreign_key: 'friend_id')
end
With a similar statement for the enemies table. Now, my problem is that I want to have a form that the user can use when they sign up, where they can input their information (this is the User object details), and also list their friends and enemies.
Because the user won't have the database id key for their friends or enemies, they'll have to input the users' names. This is fine, though because the name is also a unique key, guaranteed by the validation.
However, if the user types in the name of a friend, I can't join the two if the friend happens to not exist. So, I use a custom validation class that looks something like this:
class FriendValidator < ActiveModel::Validator
def validate(object)
#lookup user and throw error if not found.
end
end
which will access the variable (object.friends) and (object.enemies)
With something similar for enemies. So therefore, above my has_and_belongs_to_many statements, I have lines that say:
attr_accessor :friends, :enemies #these are attrs because they don't exist within the model's db
validates_with FriendValidator
When I create the form with erb, I have the standard form_for block
<%= form_for(#user) do |f| %>
It seems to me that I can't just stick
<%= f.text_area :friends %>
because friends isn't actually something that will get passed to the User object, but rather a separate table. (Can I, though? Because the attr_accessor is declared in the user's model class?)
So now, we have my main problem. I have two many-to-many tables with a model to its own model class, and I don't know how to ensure that the validation class will take the two attributes, lookup and throw necessary errors, and then add a row to the join tables using the id of the user, rather than the string inputted. What form fields should I use to pass the input to the right place? Where do I change the controller methods so that the input gets sent to the join table rather than the user object?
This definitely seems like a pretty specific situation, so I can't really find an answer in the Rails documentation, which I've been learning from.
My initial impression of this problem has to do with your associations. To me, a user has_many enemies and has_many friends.
friends belong_to user
enemies belong_to user
Not sure if a many to many relationship makes sense in this case. Maybe that's why you are having such a hard time finding an answer online. Just my two cents.
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 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.