ActiveRecord select all columns from multiple tables after joins - ruby-on-rails

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.

Related

How to select 6 random records from table

My portfolio_controller.rb has an index method like this:
def index
#portfolio = PortfolioItem.all
end
How can I specify in the condition that the code in this block should be executed 6 times? In other words, how can I access exactly 6 values from the #portfolio object in my view, using a loop? This is what I have so far:
<% #portfolio.shuffle.each do |portfo| %>
Using all, followed by shuffle, is a bad solution for two reasons.
A slight improvement would be to use sample(6) instead of shuffle.first(6), as this removes a step from the process.
However, the bigger issue here is that Portfolio.all.<something> (where the <something> method requires converting the data into a ruby Array) will fetch all of the data into memory - which is a bad idea. As the table grows, this will become a bigger performance issue.
A better idea is to perform the "random selection" in SQL (with the order and limit methods), rather than in ruby. This avoids the need to fetch other data into memory.
The exact solution is database-specific, unfortunately. For PostgreSQL and SQLite, use:
Portfolio.order('RANDOM()').limit(6).each do |portfolio|
Or for MySQL, use:
Portfolio.order('RAND()').limit(6).each do |portfolio|
You could define this as a helper in the Portfolio model - for example:
class Portfolio < ApplicationRecord
# ...
scope :random_sample, ->(n) { order('RANDOM()').limit(n) }
# ...
end
And then in your view:
#portfolio.random_sample(6).each do |portfolio|
You can something like this :
<%(1..6).each do |i| %>
<% #your statements %>
<%end%>
<% #portfolio.shuffle.each_with_index do |portfo, index| %>
<%if index <= 6%>
<p><%= portfo.title %></p>
<%end%>
<% end %>
Or You can do it as
<% #portfolio.shuffle.take(6).each do |portfo| %>
<p><%= portfo.title %></p>
<% end %>

Rails Single Results from Multiple Tables

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

Ruby on Rails - Show field from has_many table

I've got two tables: Ships and Voyages. A ship can have many voyages, but a voyage may only have one ship.
In RoR the ship model has a has_many :voyages and the Voyage model is set to belongs_to: ship
I can display all of the fields from the ship table in the view without any problems using code similar to this:
<%= #ship_data.id %>
I'm now trying to show a piece of information from the voyage table in the view.
If I do:
<%= #ship_data.voyages %>
I'm able to pull up the ActiveRecord entry i.e.:
#<Voyage::ActiveRecord_Associations_CollectionProxy:0x00000006639a10>
If I append .to_json I can pull up a json file with all the data.
How would I go about displaying a specific field in my view, things I've tried include:
<%= #ship_data.voyages.id %>
and
<%= #ship_data.voyages, :id %>
But both error out on me. Note: While the relationship is one ship to many voyages - currently each ship only has one voyage.
I will admit to being a bit of a RoR novice!
#ship_data.voyages is an ActiveRecord relation. It's like an array of items. You can get an array of ids of every item:
<%= #ship_data.voyages.pluck(:id) %>
Or loop through the array:
<% #ship_data.voyages.each do |voyage| %>
<%= voyage.id %>
<% end %>
Or get only first voyage:
<%= #ship_data.voyages.first.id %>

Rails output polymorphic associations

I want to implement a search functionality in my Rails app by using the pg_search gem. I've set up everything like it says in the documentation. Then I've set up a search controller with a show action:
def show
#pg_search_documents = PgSearch.multisearch(search_params)
end
The search itself works but I have a really annoying problem in my view. Whatever I do, it always outputs an array of PgSearch::Document objects. Even when I only write this in my view:
<%= #pg_search_documents.each do |document| %>
<% end %>
I get this (I've shortened it):
[#<PgSearch::Document id: 2, content: "…", searchable_id: 28, searchable_type: "Vessel">, #<PgSearch::Document id: 3, content: "…", searchable_id: 27, searchable_type: "Vessel">]
I know that pg_search sets up a polymorphic association which I've never dealt with before — could that be the problem?
Thanks in advance
<%= #pg_search_documents.each do |document| %>
<% end %>
This is a classic error, one I remember being puzzled over when I first started learning Rails. The mistake is using <%= %> with each. The return value of each is the array that you're iterating over (in this case, #pg_search_documents), and by using <%=, you're telling Rails to create a string from that array and insert it into your view. That generally isn't what you want: you want the view to be generated by the code inside the block you're passing to each.
Use <% #pg_search_documents.each do |document| %> instead (omitting the =) and you'll avoid the dump of the array's content.
You may also need to use .searchable as #blelump suggests, but I wanted to answer the other half of your question, as it's a common pitfall.
To get back to the original source model, searchable call is needed on these search result records, e.g:
<% #pg_search_documents.each do |document| %>
<%= document.searchable %>
<% end %>
You can also switch back to the source model within your controller, e.g:
#pg_search_documents = PgSearch.multisearch(search_params).collect(&:searchable)
Then, the #pg_search_documents will contain Vessel elements.

Rails 3.1 adding an object to an array returned by find_by_sql / can't show in erb file

I'm porting a php app to rails so I have a set of sql statements that I'm converting to find_by_sql's. I see that it returns a collection of objects of the type that I called on it. What I'd like to do is then iterate through this collection (presumably an array) and add an instance of a specific object like this:
#class is GlobalList
#sql is simplified - really joining across 3 tables
def self.common_items user_ids
items=find_by_sql(["select gl.global_id, count(gl.global_id) as global_count from main_table gl group by global_id"])
#each of these items is a GlobalList and want to add a location to the Array
items.each_with_index do |value,index|
tmp_item=Location.find_by_global_id(value['global_id'])
#not sure if this is possible
items[index]['location']=tmp_item
end
return items
end
controller
#controller
#common_items=GlobalList.common_items user_ids
view
#view code - the third line doesn't work
<% #common_items.each_with_index do |value,key| %>
<%=debug(value.location) %> <!-- works -->
global_id:<%=value.location.global_id %> <!-- ### doesn't work but this is an attribute of this object-->
<% end %>
So I have 3 questions:
1. is items an Array? It says it is via a call to .class but not sure
2. I am able to add location these GlobalList items. However in the view code, I cannot access the attributes of location. How would I access this?
3. I know that this is pretty ugly - is there a better pattern to implement this?
I would get any data you need from locations in the sql query, that way you avoid the n+1 problem
#items=find_by_sql(["select locations.foo as location_foo,
gl.global_id as global_id,
count(gl.global_id) as global_count
from main_table gl
inner join locations on locations.global_id = gl.global_id
group by global_id"])
Then in the view:
<% #items.each do |gl| %>
<%= gl.location_foo %> <-- select any fields you want in controller -->
<%= gl.global_count %>
<%= gl.global_id %>
<% end %>
If you want to keep it close to as is, I would:
def self.common_items user_ids
items=find_by_sql(["select gl.global_id, count(gl.global_id) as global_count
from main_table gl group by global_id"])
items.map do |value|
[value, Location.find_by_global_id(value.global_id)]
end
end
Then in the view:
<% #common_items.each do |value,location| %>
<%=debug(location) %>
global_id:<%=value.global_id %>
<% end %>

Resources