Order model objects by an attr_accessor - ruby-on-rails

I thought that attr_accessor has the same behavior as the other when I have to sort a list of objects, but it seems that is different:
dataRecords = MyData.where("day = ?", Time.now.yesterday.strftime("%Y%m%d").to_i)
dataRecords.each do |data|
data.accessor_var = func(data.x, data.y)
end
#sortedData = dataRecords.order('accessor_var DESC')
but #sortedData is not being sorted...

You need to keep in mind that when you apply a scope or order to an ActiveRecord::Relation the data is reloaded from the table. This means that when you loop through them and change an attribute, unless you save the result the changes will not be available to the next scope call.
You can use sort_by instead which will work on the objects in memory rather than the database.
Option 1: Save as you loop (probably not much use with an accessor!)
dataRecords = MyData.where("day = ?", Time.now.yesterday.strftime("%Y%m%d").to_i)
dataRecords.each do |data|
data.accessor_var = func(data.x, data.y)
data.save
end
#sortedData = dataRecords.order('accessor_var DESC') # Reload from table will include the saved values.
Option 2: sort_by
dataRecords = MyData.where("day = ?", Time.now.yesterday.strftime("%Y%m%d").to_i)
dataRecords.each do |data|
data.accessor_var = func(data.x, data.y)
end
#sortedData = dataRecords.sort_by{|data| data.accessor_var}
Also, toro2k has some nice optimisation for your sorting once you understand the situation.

It doesn't work because accessor_var is not a column in the database. You can use the method sort_by:
dataRecords.each { ... }
#sortedData = dataRecords.sort_by(&:accessor_var).reverse
Or, to save an interation over dataRecords:
#sortedData = dataRecords.sort_by { |data| data.accessor_var = func(data.x, data.y) }.reverse

Related

how append an object to association relation in rails?

In a rails 4.1 application I need to add an object to an "AssociationRelation"
def index
employee = Employee.where(id_person: params[:id_person]).take
receipts_t = employee.receipts.where(:consent => true) #gives 3 results
receipts_n = employee.receipts.where(:consent => nil).limit(1) #gives 1 result
#I would need to add the null consent query result to the true consent results
#something similar to this and the result is still an association relation
#receipts = receipts_t + receipts_n
end
Is there a simple way to do this?
A way of solving this:
def index
employee_receipts = Employee.find_by(id_person: params[:id_person]).receipts
receipts_t = employee_receipts.where(consent: true)
receipts_n = employee_receipts.where(consent: nil).limit(1)
#receipts = Receipt.where(id: receipts_t.ids + receipts_n.ids)
end
Unfortunately .or() can't be used here because it's only available from Rails v5.0.0.1
you could do this way
receipts_t_ids = employee.receipts.where(:consent => true).pluck(:id)
receipts_n_ids = employee.receipts.where(:consent => nil).limit(1).pluck(:id)
#receipts = Receipt.where(id: receipts_t_ids + receipts_n_ids)
To avoid extra queries and keeping arrays in memory, you can use or
Like this:
def index
employee_receipts = Employee.find_by(id_person: params[:id_person]).receipts
#receipts =
employee_receipts.where(consent: true).or(
employee_receipts.where(consent: nil).limit(1)
)
end

How to build a query with arbitrary placeholder conditions in ActiveRecord?

Assume I have an arbitrary number of Group records and I wanna query User record which has_many :groups, the catch is that users are queries by two bound fields from the groups table.
At the SQL level, I should end up with something like this:
SELECT * FROM users where (categories.id = 1 OR users.status = 0) OR(categories.id = 2 OR users.status = 1) ... -- to infinity
This is an example of what I came up with:
# Doesn't look like a good solution. Just for illustration.
or_query = groups.map do |g|
"(categories.id = #{g.category.id} AND users.status = #{g.user_status.id} )"
end.join('OR')
User.joins(:categories).where(or_query) # Works
What I think I should be doing is something along the lines of this:
# Better?
or_query = groups.map do |g|
"(categories.id = ? AND users.status = ? )".bind(g.category.id, g.user_status.id) #Fake method BTW
end.join('OR')
User.joins(:categories).where(or_query) # Works
How can I achieve this?
There has to be a better way, right?
I'm using Rails 4.2. So the shiny #or operator isn't supported for me.
I would collect the condition parameters separately into an array and pass that array (splatted, i.e. as an arguments list) to the where condition:
or_query_params = []
or_query = groups.map do |g|
or_query_params += [g.category_id, g.user_status.id]
"(categories.id = ? AND users.status = ?)"
end.join(' OR ')
User.joins(:categories).where(or_query, *or_query_params)
Alternatively, you might use ActiveRecord sanitization:
or_query = groups.map do |g|
"(categories.id = #{ActiveRecord::Base.sanitize(g.category_id)} AND users.status = #{ActiveRecord::Base.sanitize(g.user_status.id)})"
end.join(' OR ')
User.joins(:categories).where(or_query)

rails conditions based on attributes presence

I was wondering if there's a better way to do this:
def conditions(obj)
if self.setor.present?
obj = obj.joins(:negocios_setores).where("setor_id = ?", self.setor.id)
end
if self.uf.present?
obj = obj.joins(localizacao: [:uf]).where("uf_id = ?", self.uf_id)
end
if self.municipio.present?
obj = obj.joins(localizacao: [:municipio]).where("municipio_id = ?", self.municipio_id)
end
if !self.lucro_liquido_min.to_f.zero?
obj = obj.where("lucro_liquido_anual BETWEEN ? and ?", self.lucro_liquido_min, self.lucro_liquido_max)
end
if !self.faturamento_min.to_f.zero?
obj = obj.where("faturamento_bruto_anual BETWEEN ? and ?", self.faturamento_min, self.faturamento_max)
end
if !self.valor_min.to_f.zero?
obj = obj.where("valor BETWEEN ? and ?", self.valor_min, self.valor_max)
end
obj
end
Does rails 4 provide something to only do the condition if the value is present instead of placing it with a NULL value?
I don't believe that there is any way to do exactly what you mentioned. I've run into the same type of concatenation of queries.
To clean up a bit and make this tighter, you can use one-line if and unless. I think this is a bit cleaner and still readable.
def conditions(obj)
obj = obj.joins(:negocios_setores).where(setor: setor) if setor.present?
obj = obj.joins(localizacao: [:uf]).where("uf_id = ?", uf_id) if uf.present?
obj = obj.joins(localizacao: [:municipio]).where("municipio_id = ?", municipio_id) if municipio.present?
obj = obj.where("lucro_liquido_anual BETWEEN ? and ?", lucro_liquido_min, lucro_liquido_max) unless lucro_liquido_min.to_f.zero?
obj = obj.where("faturamento_bruto_anual BETWEEN ? and ?", faturamento_min, faturamento_max) unless faturamento_min.to_f.zero?
obj = obj.where("valor BETWEEN ? and ?", valor_min, valor_max) unless valor_min.to_f.zero?
obj
end
I also changed the first query to use Rails style queries rather than SQL in the where.

ruby - writing an array to a hash without overwriting

I do the following
my_hash = Hash.new
my_hash[:children] = Array.new
I then have a function that calls itself a number of time each time writing to children
my_hash[:children] = my_replicating_function(some_values)
How do I write without overwriting data that is already written ?
This is what the entire function looks like
def self.build_structure(candidates, reports_id)
structure = Array.new
candidates.each do |candidate, index|
if candidate.reports_to == reports_id
structure = candidate
structure[:children] = Array.new
structure[:children] = build_structure(candidates, candidate.candidate_id)
end
end
structure
end
Maybe this:
structure[:children] << build_structure(candidates, candidate.candidate_id)
structure[:children] << build_structure(candidates, candidate.candidate_id)

Using a method while looping through an array in ruby

I am using ruby-aaws to return Amazon Products and I want to enter them into my DB. I have created a model Amazonproduct and I have created a method get_amazon_data to return an array with all the product information. When i define the specific element in the array ( e.g. to_a[0] ) and then use ruby-aaws item_attributes method, it returns the name I am searching for and saves it to my DB. I am trying to iterate through the array and still have the item_attributes method work. When i don't define the element, i get this error: undefined method `item_attributes' for #Array:0x7f012cae2d68
Here is the code in my controller.
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each { |name|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = #arr.item_attributes.title.to_s
}
EDIT: Code in my model to see if that helps:
class Amazonproduct < ActiveRecord::Base
def self.get_amazon_data(r)
resp = Amazon::AWS.item_search('GourmetFood', { 'Keywords' => 'Coffee Maker' })
items = resp.item_search_response.items.item
end
end
Thanks for any help/advice.
I'm not familiar with the Amazon API, but I do observe that #arr is an array. Arrays do not usually have methods like item_attributes, so you probably lost track of which object was which somewhere in the coding process. It happens ;)
Try moving that .item_attributes call onto the object that supports that method. Maybe amazonproduct.get_amazon_data(:r), before its being turned into an array with to_a, has that method?
It's not quite clear to me what your classes are doing but to use #each, you can do something like
hash = {}
[['name', 'Macbook'], ['price', 1000]].each do |sub_array|
hash[sub_array[0]] = sub_array[1]
end
which gives you a hash like
{ 'name' => 'Macbook', 'price' => 1000 }
This hash may be easier to work with
#product = Product.new
#product.name = hash[:name]
....
EDIT
Try
def create
#arr = Amazonproduct.get_amazon_data( :r ).to_a
#arr.each do |aws_object|
#amazonproduct = Amazonproduct.new(params[:amazonproducts])
#amazonproduct.name = aws_object.item_attributes.title.to_s
end
end

Resources