Writing a method that simply multiplies the object by something - ruby-on-rails

I'm trying to convert a bunch of numbers from imperial to metric on the front end of my site depending on if the user has set their measurement_units to 'metric' or 'imperial'
I can just do #myWeight*.45 to convert the number, but what I want to do is write a helper method like this
def is_imperial?
if User.measurement_units == 'metric'
*0.453592
elsif User.measurement_units == 'imperial'
*1
end
end
then be able to do this: #myWeight*.is_imperial?
I'm just not sure how I would assign the *value to the method is_imperial?
Thanks for the help!
EDIT:
#myWeight is a float calculated from adding several numbers.
I'm just trying to find an elegant way of converting any number that shows up on the site to metric if the user has metric as the value in the measurement_units field on the User model.
I assumed I would need to create a helper method in the application_helper.rb. Is that not correct?

I think you mean something like this:
class User
def imperial
f_multiplier = 0.0
f_multiplier = !!(self.measurement_units == 'metric') ? 0.453592 : 1
imperial = self.weight * f_multiplier
end
end
puts #myWeight.imperial

If you want the measurement_units method to be dynamic based on the user, then I think you need to make it an instance method.
Modify the is_imperial? method to return the right number:
def is_imperial?
if measurement_units == 'metric'
0.453592
elsif measurement_units == 'imperial'
1
end
end
Then you can call the method with something like this:
#myWeight.send(:*, is_imperial?)
If #myWeight represents a User object you might have to change it to this:
#myWeight.weight.send(:*, is_imperial?)
Methods that end with a ? in Ruby are expected to return true or false, so you should rename the method to be something like weight_conversion_factor.

Assuming #myWeight is Float value, you seem like you are looking for how to monkey patch. Check in rails console, #myWeight.class.name returns Float.
For monkey patching in Rails,
Create config/initializers/extensions directory. This is where you will store any future monkey patched methods.
Create a file called, floats.rb.
Add the following code.
class Float
def is_imperial?
if User.measurement_units == 'metric'
self*0.453592
elsif User.measurement_units == 'imperial'
self*1
end
end
end
Make sure to restart the Rails server to reinitialize.

Related

Custom Decimal Type for localized input using Rails 5's Attributes API

I am trying to create a custom Decimal Type using the Rails 5's Attributes API to accepting localized user input. It looks like below:
class Decimal < ActiveRecord::Type::Decimal
def cast(value)
return unless value
cast_value(value.is_a?(String) ? parse_from_string(value) : value)
end
def changed_in_place?(raw_old_value, new_value)
raw_old_value != serialize(new_value)
end
def parse_from_string(value)
delimiter = I18n.t('number.format.delimiter')
separator = I18n.t('number.format.separator')
value.gsub(delimiter, '_').gsub(separator, '.')
end
end
I also have a custom form builder to show a formatted value to the user. When submitting the form to create resources (models entities), it works fine. However, when submitting the form to update resources, the validates_numericality_of validator marks my custom attribute as invalid (not_a_number). After some research in active model's source code, I reached this piece of code in NumericalityValidator.
https://github.com/rails/rails/blob/6a1b7985602c5bfab4c8875ca9bf0d598e063a65/activemodel/lib/active_model/validations/numericality.rb#L26-L49
But I don't understand what I could change to make this works. Any ideas?!
The validator uses a variable called raw_value. It tries to get that raw value from your object, check the lines 35 to 38.
I guess you can define a method on your model using your attribute's name with "_before_type_cast" to return a numeric value that the validator can use.
If your attribute is called, lets say, amount, you can do:
def amount_before_type_cast
amount.to_number
end
Then you'll have to define a method on your custom type to turn it into a number, maybe something like:
def to_number
value.gsub(/\D/,'').to_i #remove all non-digit and turn it into an integer
end
I made it work by changing my custom Decimal type.
class Decimal < ActiveRecord::Type::Decimal
def cast(value)
return unless value
if value.is_a?(String)
if numeric_string?(value)
value = value.to_s.to_numeric
else
return value
end
end
cast_value(value)
end
def value_constructed_by_mass_assignment?(value)
if value.is_a?(String)
numeric_string?(value)
else
super
end
end
def numeric_string?(value)
number = value.to_s.gsub(/[.,]/, '.' => '', ',' => '.')
/\A[-+]?\d+/.match?(number)
end
end

Interpolating an attribute's key before save

I'm using Rails 4 and have an Article model that has answer, side_effects, and benefits as attributes.
I am trying to create a before_save method that automatically looks at the side effects and benefits and creates links corresponding to another article on the site.
Instead of writing two virtually identical methods, one for side effects and one for benefits, I would like to use the same method and check to assure the attribute does not equal answer.
So far I have something like this:
before_save :link_to_article
private
def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.key.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.key = linked_attrs.join('; ')
end
end
end
but chaining on the key like that gives me an undefined method 'key'.
How can I go about interpolating in the attribute?
in this bit: self.key you are asking for it to literally call a method called key, but what you want, is to call the method-name that is stored in the variable key.
you can use: self.send(key) instead, but it can be a little dangerous.
If somebody hacks up a new form on their browser to send you the attribute called delete! you don't want it accidentally called using send, so it might be better to use read_attribute and write_attribute.
Example below:
def link_to_article
self.attributes.each do |key, value|
unless key == "answer"
linked_attrs = []
self.read_attribute(key).split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.write_attribute(key, linked_attrs.join('; '))
end
end
end
I'd also recommend using strong attributes in the controller to make sure you're only permitting the allowed set of attributes.
OLD (before I knew this was to be used on all attributes)
That said... why do you go through every single attribute and only do something if the attribute is called answer? why not just not bother with going through the attributes and look directly at answer?
eg:
def link_to_article
linked_attrs = []
self.answer.split(';').each do |i|
a = Article.where('lower(specific) = ?', i.downcase.strip).first
if a && a.approved?
linked_attrs.push("<a href='/questions/#{a.slug}' target=_blank>#{i.strip}</a>")
else
linked_attrs.push(i.strip)
end
end
self.answer = linked_attrs.join('; ')
end

Spree error when using decorator with the original code

Need a little help over here :-)
I'm trying to extend the Order class using a decorator, but I get an error back, even when I use the exactly same code from source. For example:
order_decorator.rb (the method is exactly like the source, I'm just using a decorator)
Spree::Order.class_eval do
def update_from_params(params, permitted_params, request_env = {})
success = false
#updating_params = params
run_callbacks :updating_from_params do
attributes = #updating_params[:order] ? #updating_params[:order].permit(permitted_params).delete_if { |k,v| v.nil? } : {}
# Set existing card after setting permitted parameters because
# rails would slice parameters containg ruby objects, apparently
existing_card_id = #updating_params[:order] ? #updating_params[:order][:existing_card] : nil
if existing_card_id.present?
credit_card = CreditCard.find existing_card_id
if credit_card.user_id != self.user_id || credit_card.user_id.blank?
raise Core::GatewayError.new Spree.t(:invalid_credit_card)
end
credit_card.verification_value = params[:cvc_confirm] if params[:cvc_confirm].present?
attributes[:payments_attributes].first[:source] = credit_card
attributes[:payments_attributes].first[:payment_method_id] = credit_card.payment_method_id
attributes[:payments_attributes].first.delete :source_attributes
end
if attributes[:payments_attributes]
attributes[:payments_attributes].first[:request_env] = request_env
end
success = self.update_attributes(attributes)
set_shipments_cost if self.shipments.any?
end
#updating_params = nil
success
end
end
When I run this code, spree never finds #updating_params[:order][:existing_card], even when I select an existing card. Because of that, I can never complete the transaction using a pre-existent card and bogus gateway(gives me empty blanks errors instead).
I tried to bind the method in order_decorator.rb using pry and noticed that the [:existing_card] is actuality at #updating_params' level and not at #updating_params[:order]'s level.
When I delete the decorator, the original code just works fine.
Could somebody explain to me what is wrong with my code?
Thanks,
The method you want to redefine is not really the method of the Order class. It is the method that are mixed by Checkout module within the Order class.
You can see it here: https://github.com/spree/spree/blob/master/core/app/models/spree/order/checkout.rb
Try to do what you want this way:
Create file app/models/spree/order/checkout.rb with code
Spree::Order::Checkout.class_eval do
def self.included(klass)
super
klass.class_eval do
def update_from_params(params, permitted_params, request_env = {})
...
...
...
end
end
end
end

Refactor case/when using strings

I have a number of these in my controller:
def ups
#ups ||= Shipper::Ups.new(
ENV['UPS_ACCESS_KEY'],
ENV['UPS_PASSWORD'],
ENV['UPS_USERNAME'],
ENV['UPS_ACCOUNT']
)
end
And then I have this block that gets called:
def type(number, carrier)
case carrier.slug
when 'ups'
number_details = ups.track(number)
when 'fedex'
number_details = fedex.track(number)
when 'usps'
number_details = usps.track(number)
end
return number_details
end
But seems I could refactor that quite a bit if I could take the carrier.slug and prepend it to the lines like ups.track(number).
Is there a way to do that?
you can use send to do this but before that we need to make sure that you have the right carrier slug
if %w[ups fedex usps].include?(carrier.slug)
send(carrier.slug).track(number)
end

Ruby on Rails: how do I set a variable where the variable being changed can change?

i want to do
current_user.allow_????? = true
where ????? could be whatever I wanted it to be
I've seen it done before.. just don't remember where, or what the thing is called.
foo = "bar"
current_user.send("allow_#{foo}=", true)
EDIT:
what you're asking for in the comment is another thing. If you want to grab a constant, you should use for instance
role = "admin"
User.const_get(role)
That's a "magic method" and you implement the method_missing on your current_user object. Example from Design Patterns
#example method passed into computer builder class
builder.add_dvd_and_harddisk
#or
builder.add_turbo_and_dvd_dvd_and_harddisk
def method_missing(name, *args)
words = name.to_s.split("_")
return super(name, *args) unless words.shift == 'add'
words.each do |word|
#next is same as continue in for loop in C#
next if word == 'and'
#each of the following method calls are a part of the builder class
add_cd if word == 'cd'
add_dvd if word == 'dvd'
add_hard_disk(100000) if word == 'harddisk'
turbo if word == 'turbo'
end
end

Resources