Ruby on Rails 4.0 - Retrieving a parameter from within a model - ruby-on-rails

In my records index, I wish to insert an icon, of which name is calculated in the model, using parameters thanks to a parameters helper.
Retrieving parameters actually does not work.
In the BusinessRules index table, I specified an image tag:
<td><%= image_tag(business_rule.index_audit_tag, :alt => "Quality hit") %>
Which I extract from a public function in the model:
### display audit tag filename
def index_audit_tag
ratio = (1-self.bad_records / (self.all_records+1)) * 100
image_file = case ratio
when 0..60 then "red.png"
when 60..90 then "yellow-png"
# when red_threshold..yellow_threshold then red_image
# when yellow_threshold..green_threshold then yellow_image
else "green.png"
end
return image_file
end
It works fine when hard-coded, but I would like to use the red_threshold etc. parameters which are available through the parameters_helper:
def red_threshold
list_id = ParametersList.where("code=?", 'LIST_OF_DISPLAY_PARAMETERS').take!
#myparam = Parameter.where("parameters_list_id=? AND name=? AND ? BETWEEN active_from AND active_to", list_id, 'Tag 1-Green light', Time.now ).take!
#myparam.param_value.to_i
end
If I try using the parameters, I get the error:
undefined local variable or method `red_threshold'
How can I do that?

You can't call a helper method from within a model. Helpers are in the view layer of MVC and models are in thee model layer. To fix this you need to put both halves of the logic in the same layer.
If you want to keep index_audit_tag in the model layer:
In the Parameter model:
class Parameter
def self.red_threshold
list_id = ParametersList.where("code=?", 'LIST_OF_DISPLAY_PARAMETERS').take!
myparam = Parameter.where("parameters_list_id=? AND name=? AND ? BETWEEN active_from AND active_to", list_id, 'Tag 1-Green light', Time.now ).take!
myparam.param_value.to_i
end
end
(Note: You can probably improve this to do one query, but I'm not clear on your data model so I didn't try.)
And in your BusinessRule model:
def index_audit_tag
ratio = (1-self.bad_records / (self.all_records+1)) * 100
image_file = case ratio
when 0..60 then "red.png"
when 60..90 then "yellow-png"
when Parameter.red_threshold..Parameter.yellow_threshold then red_image
when Parameter.yellow_threshold..Parameter.green_threshold then yellow_image
else "green.png"
end
return image_file
end
If you want to put the icon logic in the view layer:
Many people would argue that the logic for choosing the right icon doesn't belong in the model. So another way to do this (and probably how I would do it) is to remove index_audit_tag from the model instead, and put it in a helper:
def index_audit_tag_for(business_rule)
ratio = (1-business_rule.bad_records / (business_rule.all_records+1)) * 100
image_file = case ratio
when 0..60 then "red.png"
when 60..90 then "yellow-png"
when red_threshold..yellow_threshold then red_image
when yellow_threshold..green_threshold then yellow_image
else "green.png"
end
return image_file
end
Then it will have no trouble finding the *_threshold methods which are also in the view.

Related

RoR converting a virtual attribute into two database attributes

I'm currently having trouble finding a nice way to code the following situation:
There is a Model called TcpService, which has two attributes, port_from and port_to, both Integers. It also has a virtual attribute called portrange, which is a String. portrange is the String representation of the attributes port_from and port_to, so portrange = "80 90" should yield port_from = 80, port_to = 90. What I'm trying to do now is using the same Formtastic form for creating AND updating a TcpService-object. The form looks pretty standard (HAML code):
= semantic_form_for #tcp_service do |f|
= f.inputs do
= f.input :portrange, as: :string, label: "Portrange"
-# calls #tcp_service.portrange to determine the shown value
= f.actions do
= f.action :submit, label: "Save"
The thing is, I don't know of a non-messy way to make the values I want appear in the form. On new I want the field to be empty, if create failed I want it to show the faulty user input along with an error, else populate port_from and port_to using portrange. On edit I want the String representation of port_from and port_to to appear, if update failed I want it to show the faulty user input along with an error, else populate port_from and port_to using portrange.
The Model looks like this, which seems quite messy to me.
Is there a better way of making it achieve what I need?
class TcpService < ActiveRecord::Base
# port_from, port_to: integer
attr_accessor :portrange
validate :portrange_to_ports # populates `port_from` and `port_to`
# using `portrange` AND adds errors
# raises exception if conversion fails
def self.string_to_ports(string)
... # do stuff
return port_from, port_to
end
# returns string representation of ports without touching self
def ports_to_string
... # do stuff
return string_representation
end
# is called every time portrange is set, namely during 'create' and 'update'
def portrange=(val)
return if val.nil?
#portrange = val
begin
self.port_from, self.port_to = TcpService.string_to_ports(val)
# catches conversion errors and makes errors of them
rescue StandardError => e
self.errors.add(:portrange, e.to_s())
end
end
# is called every time the form is rendered
def portrange
# if record is freshly loaded from DB, this is true
if self.port_from && self.port_to && #portrange.nil?
self.ports_to_string()
else
#portrange
end
end
private
# calls 'portrange=(val)' in order to add errors during validation
def portrange_to_ports
self.portrange = self.portrange
end
end
Thanks for reading
In your model
def portrange
return "" if self.port_from.nil? || self.port_to.nil?
"#{self.port_from} #{self.port_to}"
end
def portrange=(str)
return false unless str.match /^[0-9]{1,5}\ [0-9]{1,5}/
self.port_from = str.split(" ").first
self.port_to = str.split(" ").last
self.portrange
end
Using this you should be able tu use the portrange setter and getter in your form.

Tracking object changes rails (Active Model Dirty)

I'm trying to track changes on a method just like we are tracking changes on record attributes using the Active Model Dirty.
At the moment I'm using this method to check changes to a set of attributes.
def check_updated_attributes
watch_attrs = ["brand_id", "name", "price", "default_price_tag_id", "terminal_usp", "primary_offer_id", "secondary_offer_id"]
if (self.changed & watch_attrs).any?
self.tag_updated_at = Time.now
end
end
However the attribute price is not a normal attribute with a column but a method defined in the model that combines two different attributes.
This is the method:
def price
if manual_price
price = manual_price
else
price = round_99(base_price)
end
price.to_f
end
Is there a way to track changes on this price method? or does this only work on normal attributes?
Edit: Base price method:
def base_price(rate = Setting.base_margin, mva = Setting.mva)
(cost_price_map / (1 - rate.to_f)) * ( 1 + mva.to_f)
end
cost_price and manual_price are attributes with columns it the terminal table.
Ok, solved it.
I had to create a custom method named price_changed? to check if the price had changed.
def price_changed?
if manual_price
manual_price_changed?
elsif cost_price_map_changed?
round_99(base_price) != round_99(base_price(cost_price = read_attribute(:cost_price_map)))
else
false
end
end
This solved the problem, although not a pretty solution if you have many custom attributes.

Creating new class to put into an Array in Ruby

I am coming from a C# background and trying to learn Ruby and Ruby on Rails. I have the following Car class - note the build_xml method I need in order to build XML in that syntax and then pass to a WebService
class Car
##array = Array.new
#this will allow us to get list of all instances of cars created if needed
def self.all_instances
##array
end
def initialize(id, model_number, engine_size, no_doors)
# Instance variables
#id = id
#model_number = model_number
#engine_size = engine_size
#no_doors = no_doors
##array << self
end
def build_car_xml
car = { 'abc:Id'=> #id, 'abc:ModelNo' => #model_number, 'abc:ES' => #engine_size, 'abc:ND' => #no_doors}
cars = {'abc:Car' => [car] }
end
end
In another class then I was using this as below:
car1 = Car.new('1', 18, 3.0, 4)
request = car1.build_car_xml
This works as expected and the request is formatted how I need and the webservice returns the results. I now want to expand this however so I can pass in an array of cars and produce the request XML - however I am struggling to get this part working.
So far I have been trying the following (for now I am ok with just the Id changing as it is the only parameter required to be unique):
car_array = []
(1..10).each do |i|
car_array << Car.new(i.to_s, 18, 3.0, 4)
end
Am I correct in saying that I would need to define a new build_car_xml method on my Car class that can take an array of cars and then build the xml so my request call would be something like:
request = Car.build_car_xml(car_array)
What i am unsure of is 1) - is this the correct way of doing things in Ruby and 2) how to construct the method so that it is Building the XML in the correct format in the way it was when I call it on the single object - i.e - I need the namespaces added before the actual value.
def build_car_xml(car_array)
#here is where I am unsure how to contruct this method
end
Possible solution ('abc:Car' is a wrong name, should be Cars if you want it to hold an array):
class Car
...
def self.build_cars_xml(cars)
{ 'abc:Car' => cars.map(&:build_car_xml) }
end
def build_car_xml
{ 'abc:Id'=> #id, 'abc:ModelNo' => #model_number, 'abc:ES' => #engine_size, 'abc:ND' => #no_doors }
end
end
cars =
(1..10).map do |i|
Car.new(i.to_s, 18, 3.0, 4)
end
Car.build_cars_xml(cars)
It doesn't meet your requirements as instance build_car_xml doesn't generate Car namespace, but for me it's some inconsistency. Your XML is actually a collection, even if it has just one element, instance method should not be responsible for collection. Car.build_cars_xml([Car.new(...)] looks more logical to me.

Why this application controller won't return correct value to controller who called?

Why do I get empty when I execute this? Asumming User's point is 2500. It should return 83
posts_controller
#user = User.find(params[:id])
#user.percentage = count_percentage(#user)
#user.save
application_controller
def count_percentage(user)
if user
if user.point > 2999
percentage = ((user.point / 5000.0) * 100 ).to_i
elsif user.point > 1999
percentage = ((user.point / 3000.0) * 100 ).to_i
end
return percentage
end
end
It looks like there is some lack of basic understanding, so I try to focus on a few points here.
count_percentage belongs to your model. It is a method which does things that are tied to your User records. In your example, the code can only be used with a User record. Therefore it belongs to your User class!
class User < ActiveRecord::Base
def percentage
if self.point > 2999
return ((self.point / 5000.0) * 100 ).to_i
elsif user.point > 1999
return ((self.point / 3000.0) * 100 ).to_i
else
# personally, I'd add a case for points range 0...3000
end
end
As you said, you want to put that value into your "side menu", so this allows to e.g #user.percentage there which gives you the desired percentage.
According to your description (if I understood it the right way) you want to store the calculated value in your user model. And here we go again: Model logic belongs into your model class. If you want to keep your percentage value up to date, you'd add a callback, like:
class User < ActiveRecord::Base
before_save :store_percentage
def precentage
# your code here
end
private
def store_percentage
self.my_percentage_field_from_my_database = self.percentage
end
end
To your actual problem: user.point may be NULL (db) / nil (ruby), so you should convert it to_i before actually calculating with it. Also that's why I've suggested an else block in your condition; To return a value wether or not your user.point is bigger than 1999 (if it even is an integer.. which I doubt in this case).
To answer on this question you should try to examine user.point, percentage value in the count_percentage method.
I strongly recommend pry. Because it's awesome.

Rails views handle blank values from model

Here I am, with my rails project, and this question strikes me.
For my User profile, some attributes will be blank and I want them to default to a specific value : if they are not set, I want my view to display the custom message and add a CSS class like "blank" around it.
I am using a Presenter, but as it acts a bit like a Helper, let's say those are helper methods.
In my Helper I have something like that :
def professional_information
handles_not_set user.university, user.job do |university, job|
content_tag(:p, university.content, class: university.error) +
content_tag(:p, job.content, class: job.error)
end
end
The handles_not_set is defined as followed :
def handles_not_set(*objects)
o = []
objects.each do |object|
attr = OpenStruct.new
if object.blank?
attr.content = 'Not specified.'
attr.errors = ['blank']
else
attr.content = object
attr.errors = []
end
o << attr
end
yield *o
end
But I find this rather inelegant and I'd like to keep the code DRY (i.e : I don't want my presenter's methods to be full of 'if xxx.blank?')
Any idea on how I could improve this ?
Thanks a lot !

Resources