Avoid nested select blocks - ruby

I have to retrieve some information related to movies and shows from a json document.
unique_nos = js['navigation']['category'].select{|n| n['name']=="Home"}.first['category'].select{|s| s['name']=="#{type}"}.first['category'].select{|k| k['name']=='Movie Studios'}.first['category'].map{|l| l['categoryId']}
The same would go for tv shows also.
unique_nos = js['navigation']['category'].select{|n| n['name']=="Home"}.first['category'].select{|s| s['name']=='TV'}.first['category'].select{|k| k['name']=='Networks'}.first['category'].map{|l| l['categoryId']}
I would like to avoid duplicated code performing same tasks. I would rather like to pass this block as a parameter so it could be dynamic. Is there any way to achieve this with metaprogramming?

You can simply extract it as a method:
def find_unique_nos(js, type, category)
js['navigation']['category'].select{|n| n['name']=="Home"}.first['category'].select{|s| s['name']== type }.first['category'].select{|k| k['name']==category}.first['category'].map{|l| l['categoryId']}
end
On a side note, select { ... }.first is equivalent to find { ... }, so you can simplify this to:
def find_unique_nos(js, type, category)
js['navigation']['category'].find{|n| n['name'] == "Home" }['category']
.find{|s| s['name'] == type }['category']
.find{|k| k['name'] == category }['category']
.map{|l| l['categoryId']}
end
If you want to be more sophisticated, you can use a builder to do the repetitive job of find{ ... }['category']:
def find_unique_nos(js, type, category)
['Home', type, category].inject(js['navigation']['category']) do |cat, name|
cat.find{|n| n['name'] == name }['category']
end.map{|l| l['categoryId']}
end

Please consider to use intermediate variables to break down such long chains, it will help ease debugging and comprehension. Using your same code with reformatting:
def unique_numbers(json: j, type: t)
category = type == 'TV' ? 'Networks' : 'Movie Studios'
json['navigation']['category']
.select{|n| n['name']=="Home"}
.first['category']
.select{|s| s['name'] == type }
.first['category']
.select{|k| k['name'] == category }
.first['category']
.map{|l| l['categoryId']}
end

Related

How to use scopes to filter data by partial variable

I have some working script filtering my results with Active Record Scoping. Everything works fine when i want to filter by comparing params with data from database.
But i have some function counting car price inside _car.html.erb partial, the result of this function depends on params result.
How can i scope search results by result of this function and show only cars which are under some price (defined in params).
Some code to make it more clear:
car.rb (model file)
scope :price_leasing, -> (price_leasing) { where('price_leasing <= ?', price_leasing) }
# for now price_leasing is getting price from database
scope :brand, -> (brand) { where brand: brand }
scope :car_model, -> (car_model) { where car_model: car_model }
scope :category, -> (category) { where category: category }
cars_controller.rb (controller file)
def index
#cars = Car.where(nil)
#cars = #cars.price_leasing(params[:price_leasing]) if params[:price_leasing].present?
#cars = #cars.brand(params[:brand]) if params[:brand].present?
#cars = #cars.car_model(params[:car_model]) if params[:car_model].present?
#cars = #cars.category(params[:category]) if params[:category].present?
#brands = Brand.all # importing all car brands into filters
end
in index.html.erb i have "render #cars" code
<%=
if #cars.size > 0
render #cars.where(:offer_status => 1)
else
render html: '<p>Nie znaleziono pasujących wyników.</p>'.html_safe
end
%>
inside _car.html.erb file i have function from helper
<h3 class="car-cell__price"><%= calculate_car_price(car.price, car.id) %> <span class="car-cell__light-text">zł/mc</span></h3>
my calculate_car_price() function inside helper
def calculate_car_price(car_price, car_id)
car = Car.find(car_id)
fullprice = car_price
if params[:price_leasing].present?
owncontribution = params[:price_leasing].to_i
else
owncontribution = car.owncontribution
end
pv = fullprice - owncontribution + (0.02 * fullprice)
if params[:period].present?
carperiod = params[:period].to_i
carprice = (Exonio.pmt(0.0522/60, carperiod, pv)) * -1
else
carprice = (Exonio.pmt(0.0522/60, 60, pv)) * -1
end
p number_with_precision(carprice, precision: 0)
end
i would love to scope by the result of this function. Is it possible?
The thing about scopes are that they are implemented at the database level. You want to select records that have a value
To do what you want at the DB level, you would need to a virtual column in the database, the implementation will change based on which database product you're using (I would not expect a virtual column definition in postgreSQL to be the same as a virtual column definition in mySQL)
So I'm not sure there's an optimal way to do this.
I would suggest you build your own class method and instance method in your model Car. It would be less performant but easier to understand and implement.
def self.car_price_under(target_price, params)
select { |v| v.car_price_under?(target_price, params) }
end
def car_price_under?(target_price, params)
full_price = price
if params[:price_leasing].present?
my_own_contribution = params[:price_leasing].to_i
else
my_own_contribution = owncontribution
end
pv = full_price - my_own_contribution + (0.02 * full_price)
if params[:period].present?
car_period = params[:period].to_i
new_car_price = (Exonio.pmt(0.0522/60, car_period, pv)) * -1
else
new_car_price = (Exonio.pmt(0.0522/60, 60, pv)) * -1
end
new_car_price <= target_price
end
This would let you do...
#cars = Car.where(nil)
#cars = #cars.brand(params[:brand]) if params[:brand].present?
#cars = #cars.car_model(params[:car_model]) if params[:car_model].present?
#cars = #cars.category(params[:category]) if params[:category].present?
#cars = #cars.car_price_under(target_price, params) if target_price.present?
Note that this is happening in rails, NOT in the database, so the car_price_under method should be called after all other scopes to minimise the number of records that need to be examined. You cannot chain additional scopes as #cars would be an array... if you want to be able to chain additional scopes (or you want an active record relation, not an array) you could do something like #cars = Car.where(id: #cars.pluck(:id))

ODOO - How to filter Many2Many field containing zero items

How can I filter an Odoo Many2Many field for containing zero items.
Example: I'm trying to filter tasks that have 0 followers (message_follower_ids).
[['message_follower_ids','=',False]] Returns no results, but there should be many.
Odoo version: 8.0
Frank
message_follower_ids is a compute field.
If you want to search by any compute field you have to write the search method of it in old api it is fnct_search and in that method you can return domain.
In your case message_follower_ids is compute one and also having the fnct_search method. so, whenever you search for follower in search bar in top right corner that method will call and return the domain and you will get your filtered list.
But in that fnct_search You need to change to accomplish your need.
Like this.
class mail_thread(osv.AbstractModel):
_inherit = 'mail.thread'
def _get_followers(self, cr, uid, ids, name, arg, context=None):
fol_obj = self.pool.get('mail.followers')
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('res_id', 'in', ids)])
res = dict((id, dict(message_follower_ids=[], message_is_follower=False)) for id in ids)
user_pid = self.pool.get('res.users').read(cr, uid, [uid], ['partner_id'], context=context)[0]['partner_id'][0]
for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids):
res[fol.res_id]['message_follower_ids'].append(fol.partner_id.id)
if fol.partner_id.id == user_pid:
res[fol.res_id]['message_is_follower'] = True
return res
def _search_followers(self, cr, uid, obj, name, args, context):
"""Search function for message_follower_ids
Do not use with operator 'not in'. Use instead message_is_followers
"""
fol_obj = self.pool.get('mail.followers')
res = []
for field, operator, value in args:
assert field == name
# TOFIX make it work with not in
assert operator != "not in", "Do not search message_follower_ids with 'not in'"
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('partner_id', operator, value)])
if not fol_ids and operator == '=' and value==False:
fol_ids = fol_obj.search(cr, SUPERUSER_ID, [('res_model', '=', self._name), ('partner_id', '!=', value)])
res_ids = [fol.res_id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)]
res.append(('id', 'not in', res_ids))
else:
res_ids = [fol.res_id for fol in fol_obj.browse(cr, SUPERUSER_ID, fol_ids)]
res.append(('id', 'in', res_ids))
return res
_columns = {
'message_follower_ids': fields.function(_get_followers,fnct_search=_search_followers),
}
Also need to add mail module in dependency list.

XML - How to pull out repeating children nodes

I am trying to extract repeating child elements from an xpath.
This is a sample of the XML:
<productDetail>
<productTypeCode>123</productTypeCode>
<productPrice currency="EUR">13.27</productPrice>
<productPrice currency="US">15</productPrice>
</productDetail>
As you can see the productPrice currency node is repeating.
I am able to pull out one of them by looping through each of the elements:
#node.children.each do |c|
if c.name == "productDetail"
info = {}
productTypeCode = nil
c.children.each do |gc|
name = gc.name
if name == "productTypeCode"
productTypeCode = gc.text
elsif name == "productPrice"
info["productPrice"] = gc.text
attrs = gc.attributes
info["productPrice_cur"] = attrs["currency"].value
end
As you can see I only have the "productPrice" information once in the loop, but there are two of them in the XML data.
How do I access both of the values, seen as the xpath and value names are the same?
I am coding this in Ruby.

How can I create a complete_name field in a custom module for a custom hierarchy like used on product categories in Odoo?

I'm trying to create a field “complete_name” that displays a hierarchy name similar to whats done on the product categories grid but I can't seem to get it to work. It just puts Odoo in an endless loading screen when I access the relevant view using the new field "complete_name".
I have tried to copy the code used in addons/product/product.py and migrate to work with Odoo 9 API by using compute instead of .function type but it did not work.
Can someone help me understand whats wrong? Below is my model class which works fine without the complete_name field in my view.
class cb_public_catalog_category( models.Model ):
_name = "cb.public.catalog.category"
_parent_store = True
parent_left = newFields.Integer( index = True )
parent_right = newFields.Integer( index = True )
name = newFields.Char( string = 'Category Name' )
child_id = newFields.One2many( 'catalog.category', 'parent_id', string = 'Child Categories' )
complete_name = newFields.Char( compute = '_name_get_fnc', string = 'Name' )
def _name_get_fnc( self ):
res = self.name_get( self )
return dict( res )
Your compute function is supposed to define the value of an attribute of your class, not return a value. Ensure the value you are assigning complete_name is a string.
Also name_get() returns a tuple. I am not sure if you really want a string representation of this tuple or just the actual name value.
Try this
def _name_get_fnc( self ):
self.complete_name = self.name_get()[1]
If you really want what is returned by name_get() then try this.
def _name_get_fnc( self ):
self.complete_name = str(self.name_get())
If you are still having issues I would incorporate some logging to get a better idea of what you are setting the value of complete_name to.
import logging
_logger = logging.getLogger(__name__)
def _name_get_fnc( self ):
_logger.info("COMPUTING COMPLETE NAME")
_logger.info("COMPLETE NAME: " + str(self.name_get()))
self.complete_name = self.name_get()
If this does not make it apparent what the issue is you could always try statically assigning it a value in the off chance that there is a problem with your view.
def _name_get_fnc( self ):
self.complete_name = "TEST COMPLETE NAME"
After further review I think I have the answer to my own question. It turns out as with a lot of things its very simple.
Simply use "_inherit" and inherit the product.category
model. This gives access to all the functions and fields
of product.category including the complete_name field
and computes the name from my custom model data. I was
able to remove my _name_get_func and just use the inherited
function.
The final model definition is below. Once this
update was complete I was able to add a "complete_name" field
to my view and the results were as desired!
class cb_public_catalog_category( models.Model ):
_name = "cb.public.catalog.category"
_inherit = 'product.category'
_parent_store = True
parent_left = newFields.Integer( index = True )
parent_right = newFields.Integer( index = True )
name = newFields.Char( string = 'Category Name' )
child_id = newFields.One2many( 'catalog.category', 'parent_id', string = 'Child Categories' )

Use default value if item not found in array

So I've got my code trying to select an object from an array of objects, and if the object isn't found, I want to create my defaults.
lead_time = lead_times.select{|d| LeadTimeProfile.new unless d.day_of_week == day }
however, from what I can tell, this is not returning me the devault LeadTimeProfile.
is there a way of doing this? Or have I got it right?
So I've got my code trying to select an object from an array of objects, and if the object isn't found, I want to create my defaults.
Take a look at Enumerable#find
lead_time = lead_times.find{ |d| d.day_of_week == day } || LeadTimeProfile.new
filter your array first, and then do the construction
lead_time = lead_times.select{|d| d.day_of_week == day}.map {|d| LeadTimeProfile.new(d)}
Passing a lambda as a parameter also works.
lead_time = lead_times.find(lambda { LeadTimeProfile.new } ){ |d| d.day_of_week == day }
Here is another way to get the same results as what Kyle posted. There is no difference between this and using a or gate other than maybe making chaining method calls a bit cleaner.
day = 2
lead_times.find(-> { LeadTimeProfile.new }) { |p|
p.day_of_week == day
}.day_of_week

Resources