Sort Array in Controller - ruby-on-rails

Hello I'd like to sort an array in a Rails Controller. I want to sort the array before I loop over it in the View
#projects = Project.all.sort #throws error
#and
#projects = Project.all
#projects.sort # throws error
throws this error: undefined method <=> for #<Project:0x101f70b28>
but when I query:
#projects.respond_to?('sort')
I get true
How can I sort the array? Should it be done in the View or in the Controller?
Any help is highly appreciated!

Ruby doesn't know how to sort your project. You must specify the field to use for the sort. Example for created_at:
#projects = Project.all.sort { |p1, p2| p1.created_at <=> p2.created_at }
or
#projects = Project.all.sort_by &:created_at
Alternatively, you can sort them at database level:
#projects = Project.find(:all, :order => 'created_at')

When you try and sort an array of objects, ruby needs to know how to decide which objects come first.
If your objects have an intrinsic order, e.g. They have a "number" to be sorted by, then implement a method in your Project like this:
def <=> other
number <=> other.number
end
The <=> method is used by ruby to compare two objects and determine which appears first. In this example we just delegate the sorting to the number attribute (strings and numbers both already have a built in order)
The alternative, if there may be many ways to sort your objects, is to specify at sort time how to sort. As True Soft explained there are a few ways to do that, my favourite being
#projects = Project.all.sort_by &:created_at
..to sort by the created_at field

The simplest way is to override <=> in Project:
def <=>(other_project)
self.some_comparable_field <=> other_project.some_comparable_field
# or otherwise return 1, 0 or -1 based on custom comparison rule
end
Then your original code will work.
See: http://ruby-doc.org/core/classes/Comparable.html

Related

Sort a returned object by its boolean parameters [Ruby]

i think i used the right terminology for what i need, i currently have a database call in my home_controller that is returning a call to my database with all the entries in that table specified, Freelancer.
There is an attribute on these records that has either a true or false value, which is "featured".
I need a way to call a sort method, or some other way, on that object with the true being first and then the false being afterwards, i tried using this code
def index
#freelancers = Freelancer.all
p 'below im outputting featured freelancer i hope'
#freelancers.sort_by { |row| [row.featured ? 0 : 1, row.id]}
p #freelancers
end
But unfortunately this did not work, can anyone advise me on a way to get this to work? Id rather have the sorted object returned as is, rather then assigning it to a new one. Just for future features of adding pagy and a filter by cost.
Use order method
def index
#freelancers = Freelancer.order(featured: :desc)
end

Nice array from pluck

I have a model and I love the pluck method I can use. If I do this:
#x = AwesomeModel.all.pluck(:column_one, :column_two)
then I get a multidimensional array: #x[][]. With my sad skills, I work with them using the numbers:
#x[0][1]
how can I can use pluck or a similar method to access the array something like this:
#x[0][:column_two]
If you are concerned about the structure of what you get back from the db, you should simply do:
#x = AwesomeModel.all.select(:column_one, :column_two)
Then you'd keep the fast db query advantage + have AwesomeModel instances, but with only column_one and column_two filled
Or if you desire to do it manually:
#x = AwesomeModel.all.pluck(:column_one, :column_two).map do |array|
OpenStruct.new({column_one: array[0], column_two: array[1] }) }
end
Then you can use it like a regular model:
#x[0].column_one
# or even
#x[0][:column_two]
You could do
class ActiveRecord::Base
def self.pluck_hash(*args)
plucked = pluck(*args)
plucked.map {|ary| Hash[args.zip ary]}
end
end
AwesomeModel.all.pluck_hash(:column_one, :column_two)
#=> [{:column_one => 'value', :column_two => 'value}, {...}, ... ]
First of all, don't use .all.pluck, because it returns an array of values, and that makes you loose all the advantages of ActiveRecord::Relation.
Instead use AwsomeModel.method directly, it would create the query but not run it until you need it, AwsomeModel.select(:column_1, :column_2) would create a
select (awesome_models.column_1, awsome_models.column_2)
query, and the result would be an array of ActiveRecord::Relation objects, which are still chainable, and values are still under keys of the column name eg:
AwsomeModel.select(:column_1, :column_2).first.column_1
Instead of
AwesomeModel.all.pluck(:column_1, :column_2).first[0] # or .first.first

How to paginate elements of an array after sorting on it?

I've got a controller method that sorts elements by a derived attribute. I can print to the view as long as I don't attempt to paginate. When I call #foos = #foos.page params[:page] I get the following error: undefined method 'page' for #<Array:...>
Can anyone provide some guidance here? Thanks.
Here's the whole controller method:
def index_by_capacity
if current_user.is_view_major?
#foos = Foo.major_attr(:name)
else
#foos = Foo.order(:name)
end
#foos = #foos.sort_by! {|a| a.capacity_available.to_i }
#total_foos = #foos.count
#foos = #foos.page params[:page]
respond_to do |format|
format.html
format.json { render json: #foos }
end
end
The .sort_by! method returns an Array, which has no method page (a ruby Array can't be paginated, you would have to implement it yourself or use an external lib).
The pagination works on ActiveRecord::Relation objects, returned by queries like .where or .order
In you case, you should do an order at the DB-level (faster than doing a sort via Ruby):
#foos.order(:capacity_available)
If the capacity_available attribute is a String (not an Integer), you can CAST it as an INT:
#foos.order('CAST(capacity_available AS INT) ASC')
(You may need to edit the CAST() function, it should work on PostGreSQL, not sure about MySQL)
If you want to continue using an array instead of an active record relation object, you can simply include WillPaginate::Collection.
From WillPaginate Documentation
Previously, all objects returned from paginate methods were of WillPaginate::Collection type. This is no longer true; in Active Record 3, the paginate and page methods return a Relation that is extended to look like a WillPaginate::Collection. Similarly, DataMapper returns a regular DataMapper::Collection that is also extended.
Both ActiveRecord::Relation and DataMapper::Collection are lazy arrays in the way that they don't execute any SQL queries until the point when data is needed. This makes them better than ordinary arrays.
The WillPaginate::Collection class is still available, however, and mostly unchanged.
The Array#paginate method still exists, too, but is not loaded by default. If you need to paginate static arrays, first require it in your code:
require 'will_paginate/array'

Verifying if an object is in an array of objects in Rails

I'm doing this:
#snippets = Snippet.find :all, :conditions => { :user_id => session[:user_id] }
#snippets.each do |snippet|
snippet.tags.each do |tag|
#tags.push tag
end
end
But if a snippets has the same tag two time, it'll push the object twice.
I want to do something like if #tags.in_object(tag)[...]
Would it be possible? Thanks!
I think there are 2 ways to go about it to get a faster result.
1) Add a condition to your find statement ( in MySQL DISTINCT ). This will return only unique result. DBs in general do much better jobs than regular code at getting results.
2) Instead if testing each time with include, why don't you do uniq after you populate your array.
here is example code
ar = []
data = []
#get some radom sample data
100.times do
data << ((rand*10).to_i)
end
# populate your result array
# 3 ways to do it.
# 1) you can modify your original array with
data.uniq!
# 2) you can populate another array with your unique data
# this doesn't modify your original array
ar.flatten << data.uniq
# 3) you can run a loop if you want to do some sort of additional processing
data.each do |i|
i = i.to_s + "some text" # do whatever you need here
ar << i
end
Depending on the situation you may use either.
But running include on each item in the loop is not the fastest thing IMHO
Good luck
Another way would be to simply concat the #tags and snippet.tags arrays and then strip it of duplicates.
#snippets.each do |snippet|
#tags.concat(snippet.tags)
end
#tags.uniq!
I'm assuming #tags is an Array instance.
Array#include? tests if an object is already included in an array. This uses the == operator, which in ActiveRecord tests for the same instance or another instance of the same type having the same id.
Alternatively, you may be able to use a Set instead of an Array. This will guarantee that no duplicates get added, but is unordered.
You can probably add a group to the query:
Snippet.find :all, :conditions => { :user_id => session[:user_id] }, :group => "tag.name"
Group will depend on how your tag data works, of course.
Or use uniq:
#tags << snippet.tags.uniq

Get two random elements from a RoR model

I'm trying to use RoR for something simple and I'm having some trouble picking up the basics. My closest background is ASP.NET MVC but I'm finding all of the RoR tutorials focus on what rails is really good at (scaffold stuff) but not how to make your own actions and get them to do stuff with parameters etc. (something trivial in ASP.NET MVC).
At the moment I am trying to get two random elements out of the model.
I think I'm dealing with an ActiveRecord collection of some sort?
I have read that there is a .rand method somewhere on collections/arrays, although other places suggest that rand is just a method for getting a random number up to a certain count. I can't even get the following code to work:
def index
#items = Array.new(Item[0], Item[0])
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #domain }
end
end
Anything that can help with this, and ideally help with further patching from ASP.NET MVC to RoR would be really appreciated.
To retrieve two random items from an ActiveRecord model:
#things = Thing.all(:order => 'RANDOM()', :limit => 2)
If you want 2 random items from the database, then ask the database for 2 random items:
#items = Item.find(:all, :limit => 2, :order => "RANDOM()")
There's no point loading all of the Items from your system if you're only using 2, that's a waste.
If you do already have an array from somewhere else that you need to get random values from, then Rails adds a rand method to the Array class:
#items = [my_arr.rand, my_arr.rand]
I don't know what you were trying to do with Item[0] but that doesn't do anything meaningful in Rails.
What does your model look like? I'm not sure what you're trying to do with Item[0] there. For randomizing your array you could do something like this:
#items = ["item1", "item2", "item3"].sort_by {rand}
then you could just do #items[0] and #items[1] to get 2 items of the randomized array.
As for params, you can get any form variables or request params from the query string by using the params hash:
params[:user]
The symbol name is just the name of the form field or param in the query string.
Rails controllers usually contain one or more restful actions (index, show, new, create, delete, edit, update) if you've routed it as a resource, but you adding your own actions involves just adding a new method to your controller, routing that action in the routes.rb, and creating a view with with the name of that action.
More info on your model & what you are trying to accomplish would help, but if you are trying to pull a random record from a database like sqlite, you can do something like:
#item = Items.find(:first, :order => 'RANDOM()')
Where Items is your model class. The 'RANDOM()' is just a string handed to the database to tell it how to sort, so you'll have to adjust to match whatever database you're using.
With a Mysql Database use RAND() and not RANDOM()
#items = Item.find(:all, :limit => 2, :order => "RAND()")

Resources