Rails: Using loops inside a scope - ruby-on-rails

I have this project I'm working on where I have a checklist. This checklist has an event_id, and then 2 fields for each task that much be done, one for the user_id that completed it, and one for the date. First what I do is grab all the checklists, which gives me an acitverecord::relation of all the checklists. Then I want to get only the completed checklists.
def index
#check_lists = CheckList.joins(:booking).joins(:contract)
#check_lists = #check_lists.complete if params[:status] == "complete"
#check_lists = #check_lists.incomplete if params[:status] == "incomplete"
end
My problem is once I call the #check_lists.complete, does it automatically give me the array in the scope in the model? Would I have to pass something in the method to get the array and run through it? Then once I have the activerecord::relation how do I run a loop through it?
scope :complete, { }

scope :complete, -> { where(complete: true) }
for example, would give you an ActiveRecord::Relation. This would be the same as CheckList.where(complete: true). You can convert this relation to an array of results using all.
CheckList.complete #=> ActiveRecord::Relation
CheckList.complete.all #=> Array
In Rails 4, all will return an ActiveRecord::Relation as well, so it will be necessary to use to_a to convert it to an array, but for now all will return an Array.

Related

Fetch ActiveRecord query result as an array of hashes with chosen attributes

The model User has first, last and login as attributes. It also has a method called name that joins first and last.
What I want is to iterate through the Users records and create an array of hashes with the attributes I want. Like so:
results = []
User.all.map do |user|
record = {}
record["login"] = user.login
record["name"] = user.name
results << record
end
Is there a cleaner way in Ruby to do this?
Trying to map over User.all is going to cause performance issues (later, if not now). To avoid instantiating all User objects, you can use pluck to get the data directly out of the DB and then map it.
results = User.all.pluck(:login, :first, :last).map do |login, first, last|
{ 'login' => login, 'name' => first << last }
end
Instantiating all the users is going to be problematic. Even the as_json relation method is going to do that. It may even be a problem using this method, depending on how many users there are.
Also, this assumes that User#name really just does first + last. If it's different, you can change the logic in the block.
You can use ActiveRecord::QueryMethods#select and ActiveRecord::Relation#as_json:
User.select(:login, '(first || last) as name').as_json(except: :id)
I would write:
results = User.all.map { |u| { login: u.login, name: u.name } }
The poorly named and poorly documented method ActiveRecord::Result#to_hash does what you want, I think.
User.select(:login, :name).to_hash
Poorly named because it does in fact return an array of Hash, which seems pretty poor form for a method named to_hash.

How to use while and until loops on active records in rails?

I have a model called houses, I need to get a list of houses, like '#houses = House.where(...)'
and then check each of them using some method(not just a simple where) until I will get record that matches (when the method returns true).
I've only used .each so far. How do I go to check the next record in the list using an UNTIL or WHILE loop?
Thanks
Why don't you use Enumerable#detect for this purpose?
#houses = House.where(...)
#house = #houses.detect { |h| your condition here }
#houses = House.where(...)
status = #houses.detect { |h| your condition here }.present?
return for you true/false

NoMethodError for nil object on sort_by, and all objects exist

So I have a bunch of users, who all have user.dj_name attributes. It's a validated necessity on the model, but I'm still being cautious here because I'm running into problems.
I want to get a bunch of users, then order them by their dj_name. Something like this:
#djs = Event.all.map { |e| e.program.user }.sort_by {|x,y| x.dj_name <=> y.dj_name }
where it's pulling all DJs who have Events (shows). It fails with "NoMethodError: undefined method `dj_name' for nil:NilClass"
So I tried:
#djs = Event.all.map { |e| e.program.user }
#djs.compact.sort_by! {|x,y| x.dj_name <=> y.dj_name rescue nil}
And it doesn't sort. Without the "rescue nil" clause, I get the same error.
And if I do a reject! if the object is nil I get nothing.
> #djs.reject! {|d| d.nil? }
=> nil
It seems like none of the objects in the array are nil, the sorting mechanism is giving me errors, and rescuing it just stops the sorting process and returns an unchanged array.
halp?
Use sort!, not sort_by!.
sort_by! passes a single argument into its block. So, when you call .sort_by! {|x,y| ... }, y is always nil.
The purpose of sort_by! is to sort by keys instead of elements. The block gets a single element and must return the element's key to use for sorting.
In this code:
#djs.compact.sort_by! {|x,y| x.dj_name <=> y.dj_name rescue nil}
the block returns nil as a key for every element. As result, no sorting happens.
BTW, I agree with #MurifoX that in this particular case you should you database-provided sorting.
Use the database sort for this kind of task. You can resume your queries by using:
Event.all(:include => {:program => :user}, :order => 'users.dj_name')
Decoupling this query would result in the include method making the join associations on your models and the order creating an ORDER BY on your query.

Sort Array in Controller

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

How do you iterate over active record objects in Ruby On Rails?

This question is quite simple but I have run into the problem a few times.
Let's say you do something like:
cars = Vehicle.find_by_num_wheels(4)
cars.each do |c|
puts "#{c.inspect}"
end
This works fine if cars is an array but fails if there is only one car in the database. Obviously I could do something like "if !cars.length.nil?" or check some other way if the cars object is an array before calling .each, but that is a bit annoying to do every time.
Is there something similar to .each that handles this check for you? Or is there an easy way to force the query result into an array regardless of the size?
You might be looking for
cars = Vehicle.find_all_by_num_wheels(4)
The dynamic find_by_ methods only return one element and you have to use find_all_by_ to return multiple.
If you always want all of the cars, you should use find_all instead:
cars = Vehicle.find_all_by_num_wheels(4)
You could also turn a single Vehicle into an array with:
cars = [cars] unless cars.respond_to?(:each)
Named scoped version for your problem
Vehicle.scoped(:conditions => { :num_wheels => 4 } ).each { |car| car.inspect }
You can do this to get arrays everytimes :
cars = Vehicle.find(:all, :conditions => {num_wheels => 4})
I don't think that you have a loop that will check if the object is an array.
Another solution could be:
for i in (1..cars.lenght)
puts cars[i].inspect
end
(haven't tested, it might break to test the lenght on a string. Let me know if it does)

Resources