will_paginate - Associations - ruby-on-rails

I'm trying to paginate an Association but i'm missing something.
This is where i need the pagination with .paginate(:page => params[:page], :per_page => 25). If i understood correctly i have to make a variable in my controller to fetch the towns ?
<% #alliance.players.each do |p| %>
<% p.towns.each do |t| %>
...
<% end %>
<% end %>
What i'm calling:
Alliance ->
Players ->
Towns <--
Basically i'm stuck on how to paginate the Association in a 2nd level loop.
Maybe there is a better way of doing this.
The associations:
class Alliance < ActiveRecord::Base
# Primary Key
self.primary_key = 'grepo_id'
# Associations
has_many :players
end
class Player < ActiveRecord::Base
# Primary Key
self.primary_key = 'grepo_id'
# Associations
has_many :towns
belongs_to :alliance
end
class Town < ActiveRecord::Base
# Primary Key
self.primary_key = 'grepo_id'
# Associations
belongs_to :player, :foreign_key => :player_id
end
I've tried and read a lot but haven't found any solution.
I tried to make a variable in my Controller:
#alliance_towns = #alliance.players.towns.order("rank ASC").paginate(:page => params[:page], :per_page => 25)
so i can call #alliance_towns.each do {} but on this i'm getting
undefined method `towns' for #<ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Player:0x007f9268d91348>
What am i missing ?

You should use a join. Something like this:
#alliance_towns = Town.joins(:player).where('players.alliance_id = ?', params[:id]).order('rank ASC').paginate(:page => params[:page], :per_page => 25)

Related

Rails Active query return most associated products and then the rest without associations - will pagination

I'm hoping this would return ALL products beginning with those with the most associations (some products don't have associations):
#products = Product.search(params[:search]).paginate(:per_page => 10, :page => params[:page]).most_popular
My models:
class Product < ActiveRecord::Base
has_many :associations
has_many :users, :through => :associations
scope :most_popular, -> { select("products.*, count(associations.product_id) AS count_popular").joins(:associations).group("products.id").order("count_popular DESC") | Product.all.order("id DESC")}
class Association < ActiveRecord::Base
belongs_to :user
belongs_to :product
attr_accessible :product_id
end
This seems to work when I list everything at once, but I get the error when also using will_pagination pagination:
NoMethodError - undefined method `total_pages' for #<Array:0x007fa5c5baef60>:
I solved the problem by doing this
require 'will_paginate/array'
#products = Product.search(params[:search]).most_popular.paginate(:per_page => 10, :page => params[:page])
Apparently will paginate is not good with arrays unless you tell it to.

Using sunspot_solr search array of values

Hi, I built a Ruby on Rails application with Sunspot Solr for searching.
#search = Answer.search do
with(:question_id, #question_ids)
paginate :page => 1, :per_page => Answer.count
end
return question_id
Here i want to search this Answer model using array of question_ids (ex: [1,2,3,4,5]).
How to do that? Kindly help me.
if your question and answer has association like
class Question < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :question
end
then you can add searchable to your questions model like this
class Question < ActiveRecord::Base
searchable do
text :title, :body
text :answers do
answers.map { |answer| answer.body }
end
integer :questions_ids, :multiple => true
end
// your another methods
end
And in your index action
#search = Answer.search do
with(:questions_ids, #question_ids)
paginate :page => 1, :per_page => Answer.count
end
return question_id
I think it will help you.

Better recursive loop in Ruby on Rails

Using Rails 3.2. Let's say I want 2 options:
Get all trip photos.
Get the first trip photo.
I have the following code:
# trip.rb
class Trip < ActiveRecord::Base
has_many :trip_days
def trip_photos
if (photos = trip_days.map(&:spots).flatten.map(&:photos).flatten.map)
photos.each do |photo|
photo.url(:picture_preview)
end
end
end
def trip_photo
trip_photos.first
end
end
# trip_day.rb
class TripDay < ActiveRecord::Base
belongs_to :trip
has_many :trip_day_spots
has_many :spots, :through => :trip_day_spots
end
# trip_day_spot.rb
class TripDaySpot < ActiveRecord::Base
belongs_to :trip_day
belongs_to :spot
end
#spot.rb
class Spot < ActiveRecord::Base
end
# trips_controller.rb
class TripsController < ApplicationController
def index
#trips = Trip.public.paginate(:page => params[:page], :per_page => 25)
end
end
As expected, the trip_photos method generates lots of SQL query. I wonder if there is any better way to do it?
It is because of N+1 queries. In this cases, we need to eager load all the associations of base object, so that when ever you call its associated object, it wont fire any queries for fetching them, simply it will get them from its cached object.
Hope this will work, but not tested. I assumed and wrote the following query.
def trip_photos
user_trip_days = trip_days.includes(:spots => :photos)
photos = user_trip_days.collect {|trip_day| trip_day.spots.map(&:photos).flatten}.flatten
photos.each do |photo|
photo.url(:picture_preview)
end if photos
end
Let me know if you get any errors.
For more info on eager loading associated objects in ActiveRecord, go through
Guides for Rails and Rails cast and Rails Tips
This might not be the most rails-y way, but if you truly wanted to get all the spots in one hit you could do something like:
def spots
Spot.joins("join trip_days_spots on spots.id = trip_days_spots.spot_id join trip_days on trip_days.id = trip_days_spots.trip_day_id join trips on trips.id = trip_days.trip_id").where("trips.id = ?", self.id)
end
then change your loop to:
def trip_photos
spots.map(&:photos).flatten.each do |photo|
photo.url(:picture_preview)
end
end
The code works fine, but to eager load, just add :include:
# trips_controller.rb
class TripsController < ApplicationController
def index
#trips = Trip.public.paginate(:include => [:trip_days => [:spots => :photos]], :page => params[:page], :per_page => 25)
end
end

Eager Loading with "has many through" -- do I need Arel?

I have three tables: users, members, projects. The middle is a join table expressing a has-many-through between the other two tables; and it has some attributes of interest, including join_code and activated.
More expansively:
class User < ActiveRecord::Base
has_many :members
has_many :projects, :through => :members
end
class Member < ActiveRecord::Base
belongs_to :user
belongs_to :project
# has a column called join_code
# has a column called activated
# Note that this class can be thought of as "membership"
end
class Project < ActiveRecord::Base
has_many :members
has_many :users, :through => :members
end
Goal: Given a particular user, I want a query that will get all the projects, and eager load only the member records that link those projects to the user.
So far I have this method in user.rb that does a query:
def live_projects
self.projects.order("projects.name").includes(:members).where(:members => {:join_code => nil, :activated => true})
end
But it's not enough. I'd like to then be able to do this in the view code:
<% current_user.live_projects.each do |project| %>
<li project_id="<%= project.id %>">
<% member = project.member %>
(Do something with that member record here)
<%= project.name %>
<% end %>
</li>
<% end %>
Here, normally, I'd have project.members, but in my context I'm only interested in that one member record that links back to the user.
Here is what I think the raw SQL should look like
select projects.*, members.*
from projects inner join members on projects.id = members.project_id
where members.user_id = X and members.join_code is null and members.activated = 't';
How to do that in Arel (or ActiveRecord)?
I may have something of an answer here, namely that the ActiveRecord code I wrote seems pretty reasonable. Again, here's that query:
def live_projects
self.projects.order("projects.name").includes(:members).where(:members => {:join_code => nil, :activated => true})
end
On a run through the UI with sample data it generates this output from Rails server:
Project Load (0.6ms) SELECT "projects".* FROM "projects" INNER JOIN "members" ON "projects".id = "members".project_id WHERE "members"."join_code" IS NULL AND "members"."activated" = 't' AND (("members".user_id = 3)) ORDER BY projects.name
Member Load (2.0ms) SELECT "members".* FROM "members" WHERE ("members".project_id IN (50,3,6,37,5,1))
Then later in the view code I can do this:
<% current_user.live_projects.each do |project| %>
<li project_id="<%= project.id %>" class="<%= 'active' if project == #project %>">
<% member = project.members.detect { |member| member.user_id == current_user.id } %>
(Do something with that member record here)
</li>
<% end %>
That expression to get the member record is pretty ugly in the view, but select is an Array method, not a query, and no extra DB hits other than the two shown above appear in the output from Rails server. Thus I guess my n+1 problem is solved.
Add an association called live_members on the Project class.
class Project < ActiveRecord::Base
has_many :live_members, :class_name => "Member",
:conditions => {:join_code => nil, :activated => true}
has_many :members
has_many :users, :through => :members
end
Add an association called live_projects on the User class.
class User < ActiveRecord::Base
has_many :members
has_many :projects, :through => :members
has_many :live_projects, :through => :members, :source => :project,
:include => :live_member, :order => "projects.name"
end
Now you can:
user.live_projects
It seems that you expect there to be at most one active member linking each user to a project. If this is the case the following should work:
In member.rb:
scope :live, where(:join_code => nil, :activated => true)
In user.rb:
def live_projects_with_members
members.live.includes(:project).group_by(&:project)
end
In your view:
<% current_user.live_projects_with_members.each do |project, members| %>
<% member = members.first %>
<li project_id="<%= project.id %>" class="<%= 'active' if project == #project %>">
(Do something with that member record here)
</li>
<% end %>
If you then want to add an extra join for your usage stats you can do this:
def live_projects_with_members
members.live.includes(:project, :stats).group_by(&:project)
end

Using has many :through

Using has_many => through association.
Here is what i have.
:planning model
has_many :acttypes
has_many :actcategories
has_many :acts, :through => :actcategories
:acts model
belongs_to :acttype
has_many :actcategories
has_many :plannings, :through => :actcategories
:actcategories model
named_scope :theacts, lambda { |my_id|
{:conditions => ['planning_id = ?', my_id] }}
belongs_to :act
belongs_to :planning
:acttype model
has_many :acts
My Problem Starts here. I need to show all Acts by each Act Type from Plannings that is part of the actcategories association
Right now i am getting all the acts and missing the actcategories association.
Planning Controller
def show
#planning = Planning.find(params[:id])
#acttypes = Acttype.find(:all, :include => :acts)
#acts = Actcategory.theacts(#planning)
end
Planning Show View
<% #acttypes.each do |acttype|%>
<%= acttype.name %>
<% #acts.each do |acts| %>
<li><%= link_to acts.act.name, myacts_path(acts.act, :planning => #planning.id) %></li>
<% end %>
<% end -%>
Thanks for any help.
I think the key thing you're missing is that finders and named scopes only return the Class that they're called on.
#acts = Actcategory.theacts(#planning)
#acts is all the Actcategories where actcategories.planning_id = #planning.id. They don't necessarily have the required act type.
Really, what I think you're looking for is this named scope:
class Act < ActiveRecord::Base
named_scope :with_planning, lambda do |planning_id|
{ :joins => :actcategories,
:conditions => {:actcategories => {:planning_id => planning_id}}
}
...
end
Which limits acts to those associated with the given planning. This can be called on an association to limit the linked acts to those associated with a specific planning.
Example: #acts contains acts of acttype, x, that are associated with planning, y.
#acts = Acttype.find(x).acts.with_planning(y)
With this named scope this code should accomplish what you were aiming for.
controller:
def show
#planning = Planning.find(params[:id])
#acttypes = Acttype.find(:all, :include => :acts)
end
view:
<% #acttypes.each do |acttype| %>
<h2> <%= acttype.name %><h2>
<% acttype.acts.with_planning(#planning) do |act| %>
This act belongs to acttype <%= acttype.name%> and
is associated to <%=#planning.name%> through
actcatgetories: <%=act.name%>
<%end%>
<%end%>

Resources