Using Rails 4.2
I have two models, suppliers and clients. Both models contain a name (string) and email (string). They do not have any relationship between them.
I would like to generate a list of all the names and emails from both suppliers and clients. In this list I would also like to know if the partner is a supplier or client.
Controller
#suppliers = Supplier.all
#clients = Client.all
#all_partners = (#suppliers + #clients).sort { |x, y| x.name <=> y.name }
View
<% #all_partners.each do |partner| %>
<%= partner.name %>, <%= partner.email %>, <%= partner.type %>
<!-- I need some way to know if the partner type is a supplier or client -->
<% end %>
How can I put in which type of partner it is? Is there a way to do this with one single AR call or query? This is basically how to use an SQL Union statement in Rails.
You could get the class name of the object I believe <%= partner.class.model_name.human %>
Thanks for the help all.
I ended up using the same controller as in the question, with some additional information in the view.
View
<% #all_partners.each do |partner| %>
<%= partner.name %>, <%= partner.email %>, <%= partner.try(:client_type) %>, <%= partner.class.model_name.human %>
<% end %>
Union in ActiveRecord works only within a single model. You could use union for two different tables using raw SQL, something like this:
Supplier.connection.execute("(SELECT id, ..., 'suppliers' as table FROM suppliers WHERE...) UNION (SELECT id,... 'clients' as table FROM clientsWHERE...)")
but the result would be of type PG::Result.
So the best way, unfortunately, is to use two ActiveRecord queries.
OR if clients and suppliers have similar fields, you could put them in one table
class Partner < ActiveRecord::Base
default_scope where(is_supplier: true)
scope :clients, -> { where(is_supplier: false) }
end
so Partner.all will output only suppliers, Partner.unscoped - all partners
Related
I have two models Article - :id, :name, :handle Comment - :id, :name, :article_id
My query looks like data = Article.select("articles.*, comments.*").joins("INNER JOIN comments on articles.id = comments.article_id")
Now both the models have conflicting fields. Ideally I would want to be able to do something like data.first.comments.name or data.first.articles.name.
Note I am aware of option of doming something like articles.name as article_name but I have some tables with around 20 columns. So don't want to do that.
The example you are showing is barely utilising the Rails framework at all. It seems you are thinking to much of the database structure instead of thinking of the result as Ruby Objects.
Here is what I would suggest you do to get access to the data (since you are using Rails, I assume it is for a webpage so my example is for rendering an html.erb template):
# In controller
#articles = Article.includes(:comments)
# In views
<% #articles.each do |article| %>
<h1><%= article.name %></h1>
<% article.comments.each do |comment| %>
<p><%= comment.name %></p>
<% end %>
<% end %>
But if you just want all the column data into a big array or arrays (database style), you could do it like this
rows = Article.includes(:comments).inject([]) { |arr, article|
article.comments.inject(arr) { |arr2, comment|
arr2 << (article.attributes.values + comment.attributes.values)
}
}
If you don't know how the inject method works, I really recommend to read up on it because it is a very useful tool.
So in my application I have the models People and Outfits. In my show controller for people, I get the list like this:
#people = Person.where("description LIKE ?", "%#{params[:description]}%")
And in my view I show the outfits of each person like this:
<% #people.each do |person| %>
<p> Name: <%= person.name %> </p>
<% #outfits = person.outfits %>
<% #outfits.each do |outfit|
<p> Name: <%= outfit.name %> </p>
<p> Description: <%= outfit.description %> </p>
<% end %>
<% end %>
But loading the outfits for each person, as I load many people on the page, takes too long. Is there some way I can inherit the outfits of each person so I don't have to wait so long for the page to load? Or is the only way to speed this up to make an index between outfits and people? Thanks for any help
Use a join to load the associated records:
#people = Person.eager_load(:outfits)
.where("description LIKE ?", "%#{params[:description]}%")
.limit(20) # optional
Otherwise you have what is called a N+1 query issue where each iteration through #people will cause a separate database query to fetch outfits.
And yes the outfits.person_id or whatever column that creates the association should have a foreign key index. Using the belongs_to or references macro in the migration will do this by default:
create_table :outfits do |t|
t.belongs_to :person, foreign_key: true
end
Active Record Query Interface - Eager Loading Associations
Making sense of ActiveRecord joins, includes, preload, and eager_load
you should set a limit like this:
#people = Person.where("description LIKE ?", "%#{params[:description]}%").limit(20)
change the number according to your preference.
You can use .joins or .includes
If you have a table full of Person and you use a :joins => outfits to pull in all the outfit information for sorting purposes, etc it will work fine and take less time than :include, but say you want to display the Person along with the outfit name, description, etc. To get the information using :joins, it will have to make separate SQL queries for each user it fetches, whereas if you used :include this information is ready for use.
Solution
Person.includes(:outfits).where("description LIKE ?", "%#{params[:description]}%")
Assuming I have this association
User have_many posts
Post belongs_to user
User Post
----------------
id id
name title
user_id
How to list only post title and username with includes/joins ?
(list of posts [title - username])
#posts = Post.includes(:user).select('........')
don't offer this
#posts = Post.all.each {|p| p.user.username}
__________________UP_____________________
It worked for joining 2 tables.
What if I want to use it for more complex example?
check out my prev question optimize sql query rails
#Humza's answer partly worked.
it might be something like this
#posts = Post.joins(:user, :category).paginate(:page => params[:page]).order("created_at DESC")
but It doesn't display posts that don't have category
I also need to display gravatar but I think I can just use user.email as usr_email and use gravatar_for (post.usr_email) but I'll have to customize gravatar helper for this.
posts_controller.rb
def index
#posts = Post.includes(:user).includes(:comments).paginate(:page => params[:page]).order("created_at DESC")
end
index.html.erb
<%= render #posts %>
_post.html.erb
<%= gravatar_for post.user, size:20 %>
<%= link_to "#{post.title}", post_path(post) %>
<%= time_ago_in_words(post.created_at) %>
<%= post.comments.count %>
<%= post.category.name if post.category %>
Take a look at pluck.
Post.joins(:user).pluck(:title, :name)
Note that it works in this case because there's no ambiguity regarding the name column, you might want to specify the table explicitly (pluck(:title, "users.name")).
includes is used in case of eager-loading. You need joins in this case.
posts = Post.joins(:user).select("posts.title AS title, users.name AS username")
You can access the values then in the following way:
post = posts.first
post.title # will give the title of the post
post.username # will give the name of the user that this post belongs to
If you can pluck multiple columns, then the following might be more useful to you:
posts = Post.joins(:user).pluck("posts.title", "users.name")
The result will be a 2D array, with each element being an array of the form [post_title, post_username]
Post.joins(:user, :category)
but It doesn't display posts that don't have category
That's because joins uses INNER JOIN to join the tables together. If you want to everything from Post even though the particular record doesn't have its counterpart in the other table, you need to use LEFT JOIN. Unfortunately ActiveRecord doesn't have a nice way of generating it and you will need to do that manually:
Post.joins("LEFT OUTER JOIN categories ON categories.post_id = posts.id")...
See A Visual Explanation of SQL Joins for more information.
You can call array methods on a scope so:
Post.includes(:user).map { |p| [p.title, p.user.name] }
will get the posts with included user and map each post to a tuple of the post title and the user name.
That may not entirely answer your question as I think you might want to restrict the results of the query to just the required fields in which case, I think you can add a .select('title', 'users.name') to the query. (Not in a position to test at the moment)
I have an application that currently fetches data from mySQL DB. And I have a Person table which contains columns: Name, Gender, Email, Hobby and etc.
I want to implement a "grouping-like" feature so that users can be categorized into a group by specific columns (e.g. Gender)
What I have is something like this:
What I want to implement is to create two groups Boys/Girls by their gender and with a little + sign so that we can expand it and see what people is in the group:
What would be the best way to do this?
UPDATE:
My way to implements this:
my_controller.rb:
def index
#people = Person.find_by_sql(*some sql stuff*)
#persons = #people.group_by { |t| t.gender }
end
then in view file
view.html.erb
<% #persons.sort.each do |gender, person_list| %>
<h2><%= gender %></h2>
<% for person in person_list %>
*some code here*
<% end %>
<% end %>
You can uses scopes. In your Person model file, add the following:
scope :boys, where(:gender => "Male")
scope :girls, where(:gender => "Female")
Then in your controller, you can create variables for each gender group.
#boys = Person.boys
#girls = Person.girls
Finally iterate over #boys and #girls in your view.
I am trying to create a conditional edit button, i.e. create if record does not exist, edit otherwise. I have somewhat accomplished this like so:
<% if !appl.hvac_environment.nil? %>
<%= link_to 'Review', edit_hvac_environment_path(HvacEnvironment.where("appliance_id = ?", :appl), :a => appl) %>
<% else%>
<%= link_to 'Review', new_hvac_environment_path(:a => appl)%></td>
<% end %>
appl is the appliance in my form, which has a 'hvac_environment' association. I am passing :a to retrieve the appl id to store in the hvac_env object's field.
My contoller then attempts to find the appropriate record to edit, but cannot find by id in 'edit' method because the id passed is an active record object: Couldn't find HvacEnvironment with id=#ActiveRecord::Relation:0x9fbe8f8>. Why does my form pass this and how can I pass the hvac_env id so I can edit the appropriate object.
This works when there is no record, so im sure the problem is with the second line, any thoughts?
From your code I assume that you have Appliance model that has has_one relationship with HvacEnvironment
In this case this should work
<% if !appl.hvac_environment.nil? %>
<%= link_to 'Review', edit_hvac_environment_path( appl.hvac_environment, :a => appl) %>
The ActiveRelation where clause converts the parameters directly to SQL, they don't take any of the actual relationships into account, you must use actual database column names and pass in primitive values.
So I'm guessing you need somethings like
<%= link_to 'Review', edit_hvac_environment_path(HvacEnvironment.where("appliance_id = ?", appl.id), :a => appl) %>
You get an ActiveRecord::Relation because where and friends return an object of this class not the results set, the reason is simple, these methods are chainable and in this way the SQL query is built and executed just one time when you actually access one of the results.
In this case you can simply access the first HvacEnvironment and you'll get the HvacEnvironment object you need:
HvacEnvironment.where("appliance_id = ?", :appl).first