Querying or iterating join tables in ActiveRecord/Rails - ruby-on-rails

I have two tables:
Venues
has_many :venue_intel_maps
VenueIntelMap
belongs_to :venue
In the venue_intel_map table there's a column called :tag_label that I want to grab for each particular venue that has a venue_intel_map
In my controller below this returns an array of each venue where then each venue has a venue_id
def show
#venues = Venue.where(parent_venue_id: current_admin.parent_venue_id)
end
So in my views, I can do this for particular venue.
- #venues.each do |venue|
=venue.name
=venue.address
=venue.location
But because I want to get to venue_intel_maps table so I can call a column called :tag_label I wanted to avoided a nested each statement that might look something like this
- #venues.each do |venues|
- venues.venue_intel_maps.each do |intel_maps|
= intel_maps.tag_label
Is there anyway I can clean this up or any suggestions? I was trying to play around with .joins or .selects in active record but didn't know how to successfuly do that so I can somehow put the logic in my Venues model.
EDIT
Would it also be better somehow to define in the controller my venue doing this? In the views I'm going to be pulling things from my venue_intel_maps table so would this serve any advantage?
#venues = Venue.joins(:venue_intel_maps).where(parent_venue_id: current_admin.parent_venue_id)

depending on your Rails version, try:
#venues.venue_intel_maps.pluck(:tag_label)
#venues.venue_intel_maps.map(&:tag_label)
see: http://rubyinrails.com/2014/06/rails-pluck-vs-select-map-collect/

You could preload (Rails 3+) the venue_intel_maps in the controller
def show
#venues = Venue.where(
parent_venue_id: current_admin.parent_venue_id
).preload(:venue_intel_maps)
end
Then in the view, use the iteration you suggested
- #venues.each do |venue|
= venue.name
= venue.address
= venue.location
= venue.venue_intel_maps.each do |intel_maps|
= intel_maps.tag_label

Related

Count total bookings by category - Rails

My BookingGroup has_many Booking. Booking contains column category where the data can be "adult" or "child_infant" or child_normal.
Now I want to count all total %child% and display it in my index view table
I was'nt sure whether this could be done in one line or I have to use a scope, this is where I stucked.
BookingGroup model
def search_by_category
bookings.visible.map(&:category).inject(:+)
end
Assuming category is a string column, you should be able to count it like that :
bookings.visible.where("category LIKE ?", "child%").count
bookings.visible.where(category: ["child_infant", "child_normal"]).count
We can use LIKE just as in SQL with active record
In your BookingGroup model
def search_by_category
bookings.visible.where('category LIKE ?', '%child%').size
end
But, if you do so for many booking_groups, your code will have N+1 queries issue. You can use eager load in your controller
#booking_groups = BookingGroup.joins(:bookings).select('booking_groups.*', 'count(*) as total_bookings').where('bookings.category LIKE ?', '%child%').group(:id)
Then you can
#booking_groups.first.total_bookings

Rails append the correct model object from has_many association to an array of Models

companies = Company.all
companies.each do |company|
company.locations = current_user.locations.where(company_id: company.id)
end
return companies
Is there a better way of doing what I'm trying to do above, preferably without looping through all companies?
You need to use includes, given you did set up relationships properly:
companies = Company.all.includes(:locations)
I would be concerned here because you dont use any pagination, could lead to tons of data.
There are many articles explaining in depth like this
After your edit, I'd say
companies = Company.all
locations_index = current_user.locations.group_by &:company_id
# no need for where(company_id: companies.map(&:id)) because you query all companies anyway
companies.each do |company|
company.locations = locations_index.fetch(company.id, [])
end
This way, you have 2 queries max.

active_admin config.sort for two columns

I have an active_admin table called shows that acts of a list of rsvps for bike riders and bike shows that the riders will compete in. The following code correctly sorts the table alphabetically by rider_last_name:
config.sort_order = 'rider_last_name_asc'
Now when a rider is attending multiple bike shows, I want the table to first sort by rider_last_name and then within that rider sort by an attribute of shows called start_time. start_time is a DateTime. According to this stackoverflow article, the following should work:
config.sort_order = 'rider_last_name_asc, start_time_asc'
but it doesn't. In fact, it undoes the sorting by rider_last_name. How do I sort by both columns?
You may try refine apply_sorting method for collections, like this:
controller do
def apply_sorting(chain)
params[:order] ? chain : chain.reorder(rider_last_name: :asc, start_time: :asc)
end
end
ActiveAdmin suggests overwrite the find_collection method, which returns an ActiveRecord::Relation. But I prefer add the order to it's output.
ActiveAdmin.register Show do
controller do
def find_collection(options = {})
super.reorder(rider_last_name: :asc, start_time: :asc)
end
end
...
end
Although it works, this overwrite the user option of click on a column.
Alternative
You can do the same with scoped_collection, which is called at the start of find_collection, but it does not work unless you add active_admin_config.sort_order = '' to the controller. This way:
controller do
active_admin_config.sort_order = ''
def scoped_collection
super.reorder(rider_last_name: :asc, start_time: :asc)
end
end
Now, if we want to reorder before and, after that take care of the user params (and do not overwrite them). This is the way.
Note: I did use active_admin_config.sort_order and not config.sort_order.
Also, the sort_order option, ends as a param of OrderClause.new call, which expect for only one sort field, see here and here.
you can try to pass an array to it as such:
config.sort_order = [:rider_last_name_asc, :start_time_asc]

Summing child objects rails 4.0

I have a model Task Orders that has_many Invoices. One of my Task Order attributes is "total invoiced". One of my Invoice attributes is "amount". I want a relationship where Total Invoiced = sum of "Amount". I want this to show up in my task_order/index page. Here is my task order index controller:
def index
#task_orders = TaskOrder.all
#invoices = #task_order.invoices
#task_order.invoicedAmount = #task_order.invoices.sum(:amount)
end
I am getting the error undefined method `invoices' for nil:NilClass
I do want to mention that my code in task_order/show works:
def show
#invoices = #task_order.invoices
#task_order.invoicedAmount = #invoices.sum(:amount)
end
As a follow up question, I am much more familiar with SQL queries than I am using Active Record queries. Can someone point me to a guide on how to render the results of a pure SQL query?
Thank you!!
Your index method is not going to work, because you're getting #invoices from #task_order.invoices, but you declare #task_orders instead. Note the singular vs. plural difference.

Handle many-to-many relationship in rails

I have a many-to-many relationship in a db I use with rails.
Say I have a schema that looks like this :
Cinemas
- cinema_id
- name
Movies
- movie_id
- name
Showtimes
- showtime_id
- timestamp
- movie_id
- cinema_id
So basicaly : [Cinemas] 1 -- N [Showtimes] N -- 1 [Movies]
In one of my pages I have a cinema id and want to show the movies based on the cinema_id and group the showtimes per movie. I am not exactly sure on how to handle this properly. In .Net I would use a SelectMany linq operator and create a collection of movies but this does not seem to exists in ruby/rails.
One way to do this I think would be to do something like:
#showtimes = Showtime.where(:cinema_id, cinema_id)
#showtimes.each do |st|
#movies.add(st.Movie) unless #movies.include? st.Movie
end
That would work ... but it seems ... ugly or it is just me ?
Based on your schema, each showtime record will have exactly one movie and one cinema. You can easily get separate lists this way:
#showtimes = Showtime.where(:cinema_id, cinema_id).order(:showing_at)
#movies = #showtimes.map {|s| s.movie }
You might also want to look at the .group and .includes methods in ActiveRecord.
If you want to go straight to a list of movies showing at a certain cinema, you could also do this:
#movies = Movie.joins(:showtimes).where('showtimes.cinema_id = ?', cinema_id)
With that, you can loop over the movies and show each one's showtimes.
If you have your associations setup correctly in your model, you should be able to do something like this:
Controller:
#showtimes = Showtime.where(:cinema_id, cinema_id)
View:
<%= #showtimes.each do |showtime| %>
<p><%= showtime.movie.name %></p>
<% end %>

Resources