remove element from the array of AR object - ruby-on-rails

There is a dropdown menu which contains an array of AR objects.
Need to show the element if the amount in the cart more than 3000.
I do so
element_of_dropmenu.delete_if {|x| x.name == "free delivery" && basket_sum < 3000} # remove one element
The amount in the cart is not true for the DeliveryType. This is a different model
you can do so
if sum > 3000
element_of_dropmenu
else
element_of_dropmenu.drop(1)
end
I need to display all elements when the sum>30, and remove one element when sum<30
i use rails 3.2.6 and ruby 2.0.0 :)
I think the way to do it wrong and ugly.
Tell me how to correct the code in a better way.
Thanks.

I believe you can achieve the result with simple SQL or ActiveRecord methods. Sth like below snippet would solve your problem.
YourModel.where(["name = ? AND basket_sum < ? ", "freedelivery", 3000])
or,
YourModel.where("name = ?", "freedelivery").where("basket_sum < ?", 3000)

You can't call mutator methods on ActiveRecord from 4.1 onwards check release notes
Relation no longer has mutator methods like #map! and #delete_if. Convert to an Array by calling #to_a before using these methods.
You can make use of select
element_of_dropmenu.select {|x| x.name != "free delivery" && basket_sum > 3000}
Or as marmeladze suggested you can use where clause or better create a scope
scope :premium, -> { where("name = ? AND basket_sum > ?", "free delivery", 3000) }
And chain it with association
element_of_dropmenu.premium

Related

Execute method on mongoid scope chain

I need to take some random documents using Rails and MongoId. Since I plan to have very large collections I decided to put a 'random' field in each document and to select documents using that field. I wrote the following method in the model:
def random(qty)
if count <= qty
all
else
collection = [ ]
while collection.size < qty
collection << where(:random_field.gt => rand).first
end
collection
end
end
This function actually works and the collection is filled with qty random elements. But as I try to use it like a scope like this:
User.students.random(5)
I get:
undefined method `random' for #<Array:0x0000000bf78748>
If instead I try to make the method like a lambda scope I get:
undefined method `to_criteria' for #<Array:0x0000000df824f8>
Given that I'm not interested in applying any other scopes after the random one, how can I use my method in a chain?
Thanks in advance.
I ended up extending the Mongoid::Criteria class with the following. Don't know if it's the best option. Actually I believe it's quite slow since it executes at least qty queries.
I don't know if not_in is available for normal ActiveRecord modules. However you can remove the not_in part if needed. It's just an optimization to reduce the number of queries.
On collections that have a double (or larger) number of documents than qty, you should have exactly qty queries.
module Mongoid
class Criteria
def random(qty)
if count <= qty
all
else
res = [ ]
ids = [ ]
while res.size < qty
el = where(:random_field.gt => rand).not_in(id: ids).first
unless el.nil?
res << el
ids << el._id
end
end
res
end
end
end
end
Hope you find this useful :)

Rails find by condition

Just wondering if there is any function that allows to do that:
MyModel.find_by_conditon(method_a - method_b > 0)
MyModel.class
def method_a
next_service_date.to_i
end
def method_b
last_service_date.to_i
end
def next_service_date
service_date.present? ? calculated_time_estimation : row_time_estimation
end
def calculated_time_estimation
service_date + calculated_service_period
end
def calculated_service_period
case(service_frequency_period)
when 'Year'
service_frequency_number.to_i.year
when 'Month'
service_frequency_number.to_i.month
when 'Week'
service_frequency_number.to_i.day * 7
when 'Day'
service_frequency_number.to_i.day
end
end
service_frequency_number, service_frequency_period, service_date are attributes for MyModel
Assuming that method_a and method_b are actually attributes, you can do:
MyModel.where('method_a - method_b > ?', 0)
Edit
I think the only way that you're going to be able to query on a calculated field is to move the calculation to the DB, where you would have a scalar function to return the results. Then you could do MyModel.where('next_service_date_on_db(service_date) > 0'). Unfortunately, that would tie you to a DB-specific implementation, but you're not going to be able to query that way without a server-side function.
Now, if you have the entire collection, you could filter based on those conditions, but you'll have to load the entire set. For example:
MyModel.all.select {|m| m.method_a - m.method_b > 0}
#all returns an array of all of the objects, and select filters based on the condition of the block. Unfortunately, this solution loads all of the records to sort application side.
Not entirely sure what you're trying to achieve .. but you may have to resort to SQL?
(and what's method_a and b?)
MyModel.where('? > 0', (method_a - method_b))

Rails: combine two queries into one

I am new to rails. Here is the following code with Foo as model object:
a = Foo
a = Foo.where(age: 18)
if params[:sort] == "desc"
a = a.order("name desc")
end
Here two queries are performed, I want to combine them to one or you can say i want to perform Foo.where(age=18).order("name asc")
Remember there can be the case when order is no needed i.e. params[:sort] is not equal to desc.
Please don't give solution like
if params[:sort] == "desc"
a = a.where(age=18).order("name desc")
else
a = a.where(age=18)
end
as it makes code redundant and also for more parameters it might not work.
No, you're mistaken. Actually, no queries are performed here.
a = Foo
a = Foo.where(age=18)
if params[:sort] == "desc"
a = a.order("name desc")
end
The actual query is sent where you start retrieving data. That is, do something like
a.each do |b|
# do something with b
end
Until then you can safely chain criteria building methods (where, order, select and others).
Actually your code will only execute one query. this is because in rails the calls to the database are only done once you access the result. So when you will write a.first (or something similar) it will make the DB call.
If its that what you mean... A simple solution would be:
a.where(age: 18).order("name #{params[:sort] || 'asc'}")
So if params[:sort] is nil it will default to asc.

How to apply named_scopes incrementally in Rails

named_scope :with_country, lambad { |country_id| ...}
named_scope :with_language, lambad { |language_id| ...}
named_scope :with_gender, lambad { |gender_id| ...}
if params[:country_id]
Event.with_country(params[:country_id])
elsif params[:langauge_id]
Event.with_state(params[:language_id])
else
......
#so many combinations
end
If I get both country and language then I need to apply both of them. In my real application I have 8 different named_scopes that could be applied depending on the case. How to apply named_scopes incrementally or hold on to named_scopes somewhere and then later apply in one shot.
I tried holding on to values like this
tmp = Event.with_country(1)
but that fires the sql instantly.
I guess I can write something like
if !params[:country_id].blank? && !params[:language_id].blank? && !params[:gender_id].blank?
Event.with_country(params[:country_id]).with_language(..).with_gender
elsif country && language
elsif country && gender
elsif country && gender
.. you see the problem
Actually, the SQL does not fire instantly. Though I haven't bothered to look up how Rails pulls off this magic (though now I'm curious), the query isn't fired until you actually inspect the result set's contents.
So if you run the following in the console:
wc = Event.with_country(Country.first.id);nil # line returns nil, so wc remains uninspected
wc.with_state(State.first.id)
you'll note that no Event query is fired for the first line, whereas one large Event query is fired for the second. As such, you can safely store Event.with_country(params[:country_id]) as a variable and add more scopes to it later, since the query will only be fired at the end.
To confirm that this is true, try the approach I'm describing, and check your server logs to confirm that only one query is being fired on the page itself for events.
Check Anonymous Scopes.
I had to do something similar, having many filters applied in a view. What I did was create named_scopes with conditions:
named_scope :with_filter, lambda{|filter| { :conditions => {:field => filter}} unless filter.blank?}
In the same class there is a method which receives the params from the action and returns the filtered records:
def self.filter(params)
ClassObject
.with_filter(params[:filter1])
.with_filter2(params[:filter2])
end
Like that you can add all the filters using named_scopes and they are used depending on the params that are sent.
I took the idea from here: http://www.idolhands.com/ruby-on-rails/guides-tips-and-tutorials/add-filters-to-views-using-named-scopes-in-rails
Event.with_country(params[:country_id]).with_state(params[:language_id])
will work and won't fire the SQL until the end (if you try it in the console, it'll happen right away because the console will call to_s on the results. IRL the SQL won't fire until the end).
I suspect you also need to be sure each named_scope tests the existence of what is passed in:
named_scope :with_country, lambda { |country_id| country_id.nil? ? {} : {:conditions=>...} }
This will be easy with Rails 3:
products = Product.where("price = 100").limit(5) # No query executed yet
products = products.order("created_at DESC") # Adding to the query, still no execution
products.each { |product| puts product.price } # That's when the SQL query is actually fired
class Product < ActiveRecord::Base
named_scope :pricey, where("price > 100")
named_scope :latest, order("created_at DESC").limit(10)
end
The short answer is to simply shift the scope as required, narrowing it down depending on what parameters are present:
scope = Example
# Only apply to parameters that are present and not empty
if (!params[:foo].blank?)
scope = scope.with_foo(params[:foo])
end
if (!params[:bar].blank?)
scope = scope.with_bar(params[:bar])
end
results = scope.all
A better approach would be to use something like Searchlogic (http://github.com/binarylogic/searchlogic) which encapsulates all of this for you.

Refactoring Model Methods in Ruby On Rails

A common idiom that my camp uses in rails is as follows:
def right_things(all_things, value)
things = []
for thing in all_things
things << thing if thing.attribute == value
end
return things
end
how can I make this better/faster/stronger?
thx
-C
def right_things(all_things, value)
all_things.select{|x| x.attribute == value}
end
If your things are ActiveRecord models and you only need the items selected for your current purpose, you may, if you're using Rails 2.0 (? definitely 2.1) or above, find named_scopes useful.
class Thing
named_scope :rightness, lambda { |value| :conditions => ['attribute = ?', value] }
end
So you can say
Thing.rightness(123)
, which is (in this case) similar to
Thing.find_by_attribute(123)
in that it boils down to a SQL query, but it's more easily chainable to modify the SQL. If that's useful to you, which it may not be, of course...

Resources