I was wondering if someone could explain how to use will_paginate on an array of objects?
For example, on my site I have an opinion section where users can rate the opinions. Here's a method I wrote to gather the users who have rated the opinion:
def agree_list
list = OpinionRating.find_all_by_opinion_id(params[:id])
#agree_list = []
list.each do |r|
user = Profile.find(r.profile_id)
#agree_list << user
end
end
Thank you
will_paginate 3.0 is designed to take advantage of the new ActiveRecord::Relation in Rails 3, so it defines paginate only on relations by default. It can still work with an array, but you have to tell rails to require that part.
In a file in your config/initializers (I used will_paginate_array_fix.rb), add this
require 'will_paginate/array'
Then you can use on arrays
my_array.paginate(:page => x, :per_page => y)
You could use Array#from to simulate pagination, but the real problem here is that you shouldn't be using Array at all.
This is what ActiveRecord Associations are made for. You should read that guide carefully, there is a lot of useful stuff you will need to know if you're developing Rails applications.
Let me show you a better way of doing the same thing:
class Profile < ActiveRecord::Base
has_many :opinion_ratings
has_many :opinions, :through => :opinion_ratings
end
class Opinion < ActiveRecord::Base
has_many :opinion_ratings
end
class OpinionRating < ActiveRecord::Base
belongs_to :opinion
belongs_to :profile
end
It's important that your database schema is following the proper naming conventions or all this will break. Make sure you're creating your tables with Database Migrations instead of doing it by hand.
These associations will create helpers on your models to make searching much easier. Instead of iterating a list of OpinionRatings and collecting the users manually, you can make Rails do this for you with the use of named_scope or scope depending on whether you're using Rails 2.3 or 3.0. Since you didn't specify, I'll give both examples. Add this to your OpinionRating class:
2.3
named_scope :for, lambda {|id|
{
:joins => :opinion,
:conditions => {
:opinion => { :id => id }
}
}
}
named_scope :agreed, :conditions => { :agree => true }
named_scope :with_profiles, :includes => :profile
3.0
scope :agreed, where(:agree => true)
def self.for(id)
joins(:opinion).where(:opinion => { :id => id })
end
In either case you can call for(id) on the OpinionRatings model and pass it an id:
2.3
#ratings = OpinionRating.agreed.for(params[:id]).with_profiles
#profiles = #ratings.collect(&:profile)
3.0
#ratings = OpinionRating.agreed.for(params[:id]).includes(:profile)
#profiles = #ratings.collect(&:profile)
The upshot of all this is that you can now easily paginate:
#ratings = #ratings.paginate(:page => params[:page])
Update for Rails 4.x: more or less the same:
scope :agreed, ->{ where agreed: true }
def self.for(id)
joins(:opinion).where(opinion: { id: id })
end
Although for newer Rails my preference is kaminari for pagination:
#ratings = #ratings.page(params[:page])
The gem will_paginate will paginate both ActiveRecord queries and arrays.
list = OpinionRating.where(:opinion_id => params[:id]).includes(:profile).paginate(:page => params[:page])
#agree_list = list.map(&:profile)
If you don't want to use the config file or are having trouble with it, you can also just ensure you return an ActiveRecord::Relation instead of an array. For instance, change the agree_list to be a list of user ids instead, then do an IN on those ids to return a Relation.
def agree_list
list = OpinionRating.find_all_by_opinion_id(params[:id])
#agree_id_list = []
list.each do |r|
user = Profile.find(r.profile_id)
#agree_id_list << user.id
end
#agree_list = User.where(:id => #agree_id_list)
end
This is inefficient from a database perspective, but it's an option for anybody having issues with the will_paginate config file.
I took advantage of rails associations, and came up with a new method:
def agree_list
o = Opinion.find(params[:id])
#agree_list = o.opinion_ratings(:conditions => {:agree => true}, :order => 'created_at DESC').paginate :page => params[:page]
rescue ActiveRecord::RecordNotFound
redirect_to(profile_opinion_path(session[:user]))
end
In my view I looked up the profile like so:
<% #agree_list.each do |rating| %>
<% user = Profile.find(rating.profile_id) %>
<% end %>
Please post up if there's a better way to do this. I tried to use the named_scope helper in the OpinionRating model with no luck. Here's an example of what I tried, but doesn't work:
named_scope :with_profile, lambda {|id| { :joins => [:profile], :conditions => ['profile_id = ?', id] } }
That seemed like the same as using the find method though.
Thanks for all the help.
I am using rails 3 ruby 1.9.2. Also, I am just starting app, so no css or styles included.
Install will_paginate:
gem install will_paginate
Add to Gemfile and run bundle.
Controller
class DashboardController < ApplicationController
include StructHelper
def show
#myData =structHelperGet.paginate(:page => params[:page])
end
end
module StructHelper queries a service, not a database.
structHelperGet() returns an array of records.
Not sure if a more sophisticated solution would be to fake a model, or to grab the data every so often and recreate a sqllite table once in a while and have a real model to query. Just creating my first rails app ever.
View
<div id="Data">
<%= will_paginate #myData%>
<table>
<thead>
<tr>
<th>col 1</th>
<th>Col 2</th>
<th>Col 3</th>
<th>Col 4</th>
</tr>
</thead>
</tbody>
<% #myData.each do |app| %>
<tr>
<td><%=app[:col1]%> </td>
<td><%=app[:col2]%> </td>
<td><%=app[:col3]%> </td>
<td><%=app[:col4]%> </td>
</tr>
<% end %>
</tbody>
</table>
<%= will_paginate #myData%>
</div>
This will give you pagnation of the default 30 rows per page.
If you have not read http://railstutorial.org yet, start reading it now.
You can implement pagination even without any gem.I saw this How do I paginate an Array?. Simple implementation in kaminari gems doc. Please see the below example which i got from kaminari gems doc
arr = (1..100).to_a
page, per_page = 1, 10
arr[((page - 1) * per_page)...(page * per_page)] #=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
page, per_page = 2, 10
arr[((page - 1) * per_page)...(page * per_page)] #=> [11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
Related
Today i am trying to present the items from a list that were paginated:
users = User.order('name').paginate(:per_page => 10, :page => 1).map{|u| UserPresenter.new(e)}
The problem is that users is no longer (after mapping to my presenter) an array wrapped with will_paginate magic, so it does not contains such things like total_entries, per_page or page, then the will_paginate helper is not working in my view :S .
How should i proceed to paginate a list of post-processed objects?
I've tried doing the other way around but then if i have a huge amount of records in my table it will be really painful because it will paginate from a big result set that is already retrieved:
users = User.order('name').map{|u| UserPresenter.new(u)}.paginate(:per_page => 10, :page => 1)
Thanks in advance
Maybe I would try to create a UserCollectionPresenter and a UserPresenter this way
class UserCollectionPresenter
include Enumerable
delegate :paginate, to: :collection
attr_reader :collection
def initialize(collection)
#collection = collection
#_presentable_collection = collection.map { |user| UserPresenter.new(user) }
end
def each(&block)
#_presentable_collection.each(&block)
end
def total_count
#collection.limit(nil).offset(nil).count
end
end
And
class UserPresenter
def initialize(user)
#user = user
end
private
attr_reader :user
end
In the controller u should send the paginated AR collection and later u can use paginate. Not sure which methods u need from will_paginate but i would think of either delegating, "overriding" them or include them from will_paginate directly.
I would suggest that you still use users for will_paginate and add another instance variables for presenters.
#users = User.order('name').paginate(:per_page => 10, :page => 1)
#presenters = #users.map { |u| UserPresenter.new(u) }
No hair left on my head (and I have had lots :) ), I have been pulling out my hair and for the life of me I can't figure this out.
I have a one to many relations between 2 tables. I have installed the Datagrid Gem for reporting. I need to get the report from one model based on the other one.
Please have a look at my code.
reports_grid.rb
class ReportsGrid
include Datagrid
scope do
Land.includes(:estate)
end
filter(:estate, :enum, :select => proc { Estate.group("title").select("title").map {|c| [c.title] }})
column(:id, :header => "Land ID")
column(:current_stage, :header => "Stage")
column(:price)
column(:status)
end
reports_controller.rb
class ReportsController < ApplicationController
def index
#grid = ReportsGrid.new(params[:reports_grid]) do |scope|
if params[:reports_grid].present?
if params[:reports_grid][:estate].present?
scope.joins(:estate).where("estates.title = ? ",params[:reports_grid][:estate]).page(params[:page])
**# when I get the #grid.assets here all good and return correct number of rows**
else
scope.page(params[:page])
end
else
scope.page(params[:page])
end
end
end
end
Land.rb
belongs_to :estate
estate.rb
has_many :lands
Now when I go to /reports and try to run the filter I get the following error
PG::UndefinedColumn: ERROR: column lands.estate does not exist LINE 1: ..._id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."e... ^ : SELECT COUNT(*) FROM "lands" INNER JOIN "estates" ON "estates"."id" = "lands"."estate_id" WHERE (estates.title = 'Olive Gardens' ) AND "lands"."estate" = 'Olive Gardens'
Why is the Gem tries to add "lands"."estate" = 'Olive Gardens' to the query when I have defined it at the instance.
Please let me know if you need me to add anything. Thank you in advance.
Edit:
This is what I have done and worked in the Filter:
I have done this:
filter(:estate_id, :enum,
:select => lambda {Estate.all.map {|p| [p.title, p.id]}},
:multiple => false,
:include_blank => true
) do |value|
self.where(:lands => {:estate_id => value})
end
Do you it is a good approach?
I guess in the scope I could say Land.joins(:estate) then use the scope.all.map... in the query.
Datagrid filter designed to filter data but not to just be by default.
If you have some reason why estate should not filter data by itself then add :dummy => true option:
filter(:estate, :enum, :select => ..., :dummy => true)
But I'would recommend it. Do this instead and your hair will start growing instantly:
filter(:estate, :enum, :select => ...) do |scope, value|
scope.joins(:estate).where("estates.title = ? ", value)
end
It seems obvious from documentation here:
https://github.com/bogdan/datagrid/wiki/Filters#filter-block
Try using references
Land.includes(:estate).references(:estates)
I would like this before filter to run every time the page is loaded (for now) to check if an item is over 7 days old or not and if so, run some actions on it to update its attributes.
I have before_filter :update_it in the application controller. update_it is defined below that in the same controller as:
def update_it
#books = Book.all
#books.each do |book|
book.update_queue
end
end
Then update_queue is defined in the book model. Here's everything in the model that pertains to this:
scope :my_books, lambda {|user_id|
{:conditions => {:user_id => user_id}}
}
scope :reading_books, lambda {
{:conditions => {:reading => 1}}
}
scope :latest_first, lambda {
{:order => "created_at DESC"}
}
def move_from_queue_to_reading
self.update_attributes(:queued => false, :reading => 1);
end
def move_from_reading_to_list
self.update_attributes(:reading => 0);
end
def update_queue
days_gone = (Date.today - Date.parse(Book.where(:reading => 1).last.created_at.to_s)).to_i
# If been 7 days since last 'currently reading' book created
if days_gone >= 7
# If there's a queued book, move it to 'currently reading'
if Book.my_books(user_id).where(:queued => true)
new_book = Book.my_books(user_id).latest_first.where(:queued => true).last
new_book.move_from_queue_to_reading
currently_reading = Book.my_books(user_id).reading_books.last
currently_reading.move_from_reading_to_list
# Otherwise, create a new one
else
Book.my_books(user_id).create(:title => "Sample book", :reading => 1)
end
end
end
My relationship is that a book belongs_to a user and a user has_many books. I'm showing these books in the view through the user show view, not that it matters though.
So the errors I keep getting are that move_from_queue_to_reading and move_from_reading_to_list are undefined methods. How can this be? I'm clearly defining them and then calling them below. I really am at a loss and would greatly appreciate some insight into what I'm doing wrong. I'm a beginner here, so any structured criticism would be great :)
EDIT
The exact error message I get and stack trace is as follows:
NoMethodError in UsersController#show
undefined method `move_from_queue_to_reading' for nil:NilClass
app/models/book.rb:41:in `update_queue'
app/controllers/application_controller.rb:22:in `block in update_it'
app/controllers/application_controller.rb:21:in `each'
app/controllers/application_controller.rb:21:in `update_it'
I suspect that the collection returned is an empty array (which is still 'truthy' when tested). So calling .last is returning nil to the new_book and currently_reading local variables. Try changing:
if Book.my_books(user_id).where(:queued => true)
to:
if Book.my_books(user_id).where(:queued => true).exists?
Additionally, you are modifying the scope when finding currently_reading. This can potentially cause the query to again return no results. Change:
currently_reading.move_from_reading_to_list
to:
currently_reading.move_from_reading_to_list if currently_reading
I have this model:
class Survey < ActiveRecord::Base
attr_accessor :csvFile_file_name
has_attached_file :csvFile, :path => ":rails_root/public/:class/:attachment/:id/:style_:basename.:extension"
serialize :content, Hash
#after_save :do_cvs_process
def do_csv_process
product = {}
FasterCSV.foreach(self.csvFile.path, :headers => true, :col_sep => ",") do |row|
row.to_hash.each do |key, value|
product[key.underscore.to_sym] = value
end
end
self.update_column(:content, {:first => product})
end
end
I have several problems:
Because of standard browser security, I have to upload file and save it before processing it with csv to assign it as a hash to my :content attribute... That's why I'm using update_column to avoid callbacks. Is there a clever way to do it?
It does not work! When back to the view <%= #survey.content %> rails tells me that it found an array when it expected a hash.
def self.import_csv_file(iFileName)
c = CSV.open iFileName
header= c.first.map{ |i| i.to_s.strip.downcase; }
c.each { |row| import_hash( Hash[*header.zip(row).flatten] ); }
c.close
end
My product class has an import_hash method that looks for lowercase/spacefree headers and matches them to fields in the product.
product.name = hash['productname'] || hash['name'] #for example.
use faster_csv gem. Here are some quick links:
[SOURCE] https://github.com/JEG2/faster_csv
[DOCS] http://fastercsv.rubyforge.org/
[CHEATSHEET]http://cheat.errtheblog.com/s/faster_csv/
Please do some R&D on GitHub before pasting a questions, these questions are already there.
How can I insert multiple records into a database using rails syntax.
INSERT INTO users (email,name) VALUES ('a#ao.in','a'),('b#ao.in','b'),
('c#ao.in','c');
This is how we do it in MySQL. How is this done in Rails?
Check out this blog post: http://www.igvita.com/2007/07/11/efficient-updates-data-import-in-rails/
widgets = [ Widget.new(:title => 'gizmo', :price => 5),
Widget.new(:title => 'super-gizmo', :price => 10)]
Widget.import widgets
Depending on your version of rails, use activerecord-import 0.2.6 (for Rails 3) and ar-extensions 0.9.4 (for Rails 2)
From the author: http://www.continuousthinking.com/tags/arext
While you cannot get the exact SQL that you have there, you can insert multiple records by passing create or new on an array of hashes:
new_records = [
{:column => 'value', :column2 => 'value'},
{:column => 'value', :column2 => 'value'}
]
MyModel.create(new_records)
I use following in my project but it is not proper for sql injection.
if you are not using user input in this query it may work for you
user_string = " ('a#ao.in','a'), ('b#ao.in','b')"
User.connection.insert("INSERT INTO users (email, name) VALUES"+user_string)
Just a use activerecord-import gem for rails 3 or ar-extensions for rails 2
https://github.com/zdennis/activerecord-import/wiki
In Gemfile:
gem "activerecord-import"
In model:
import "activerecord-import"
In controller:
books = []
10.times do |i|
books << Book.new(:name => "book #{i}")
end
Book.import books
This code import 10 records by one query ;)
or
##messages = ActiveSupport::JSON.decode(#content)
#messages = JSON(#content)
#prepare data for insert by one insert
fields = [:field1, :field2]
items = []
#messages.each do |m|
items << [m["field1"], m["field2"]]
end
Message.import fields, items
You can use Fast Seeder to do multiple insert.
In People_controller.rb
# POST people
NAMES = ["Sokly","Nary","Mealea"]
def create
Person.transaction do
NAMES.each do |name|
#name = Person.create(:name => name)
#name.save
end
end
end
Just pass an array of hashs to the create method like this:
User.create([{:email => "foo#com", :name => "foo"}, {:email => "bar#com", :name => "bar"}])