Rails helper pass negative symbol as argument in self method - ruby-on-rails

In my Rails 6, Ruby 2.7 app I'm using ActionView::Helpers::NumberHelper and number_to_currency method. Everything works fine but in one place I need to have negative amount instead of positive. To do so I created two methods:
formatters/document_amount_formatter.rb
module Formatters
# Formats the amount in a suitable form to be used in PDF creator.
class DocumentAmountFormatter
extend ActionView::Helpers::NumberHelper
# The method to call.
#
# #return [String]
def self.call(amount)
number_to_currency(amount.to_f, delimiter: '.', separator: ',', format: '%n €')
end
def self.negative_amount(amount)
number_to_currency(-amount.to_f, delimiter: '.', separator: ',', format: '%n €')
end
end
end
Both works well:
Formatters::CashbookDocumentAmountFormatter.call(cash_transactions.first.gross_amount)
=> "100,00 €"
Formatters::CashbookDocumentAmountFormatter.negative_amount(cash_transactions.first.gross_amount)
=> "-100,00 €"
But I'm not so sure if this is a good approach, tbh the code seems to be smelly. Is it possible to change those two methods into one? How to pass '-' or '+' as an argument inside of one of these methods?

Call call from within negative_amount.
def self.negative_amount(amount)
call(-amount)
end
The next question being, why have this method at all? The caller can write formatter.call(-amount) just as easily and more obviously.
Note that you probably shouldn't hard-code currency formatting and instead make use if internationalization.

Related

Set unit for number_to_currency with a user setting?

I want to allow users to change the currency unit throughout their account.
The obvious way to do it is to pass the unit parameter to number_to_currency, but given number_to_currency is used hundreds of times throughout the app, it seems a little repetitive to do that.
So is there some way to change what unit is used for all instances of number_to_currency based on a setting stored in the database for each user?
Sounds to me like you need some sort of global function / variable to define the symbol
I would do it like this:
#app/helpers/application_helper.rb
def unit
User.find(current_user.id).select(:currency_type) #I don't know how your units are stored - you may need logic to return the correctly formatted unit
end
This will allow you to call: <%= number_to_currency, unit: unit %>
Overriding Helper Method
number_to_currency is literally just a helper itself, which means you can append options on the fly:
Original
# File actionpack/lib/action_view/helpers/number_helper.rb, line 106
def number_to_currency(number, options = {})
return unless number
options = escape_unsafe_delimiters_and_separators(options.symbolize_keys)
wrap_with_output_safety_handling(number, options.delete(:raise)) {
ActiveSupport::NumberHelper.number_to_currency(number, options)
}
end
Amended
#app/helpers/application_herlper.rb
def number_to_currency(number, options ={})
unit = User.find(current_user.id).select(:currency_type)
options[:unit] = unit unless options[:unit].present?
super
end
You could pass the currency as an option to the number_to_currency method as shown:
number_to_currency(1234567890.506, locale: :fr)
In that case you would need to replace :fr to whatever points to the user's setting and to create such a locale with such options:
number:
currency:
format:
unit: '€'
format: '%n %u'
separator: ","
delimiter: "."
precision: 2
significant: false
strip_insignificant_zeros: false
Or you set the unit in another way:
number_to_currency(1234567890.50, unit: "£", format: "%u %n")
=> "£ 1.234.567.890,50"
Hope this helps you.

Ruby syntax error, unexpected '=', expecting ')'

I am attempting to write my own solution to a Ruby exercise from Rubymonk where the purpose is to create three methods (add, subtract, and calculate) so when 'calculate' is called you can determine whether or not numbers are added or subtracted based on what is passed in. I am receiving the following error:
main:11: syntax error, unexpected '=', expecting ')' def calculate(*numbers, options={})
Can anyone tell me what the issue is with my code? Thanks for any and all help!
def add(*numbers)
numbers.inject(0) {|sum, number| sum + number}
end
def subtract(*numbers)
numbers.inject{|diff, number| diff - number}
end
def calculate(*numbers, options={})
result = add(numbers) if options.empty?
result = add(numbers) if options[:add]
result = subtract(numbers) if options[:subtract]
result
end
def calculate(*numbers, options={})
is not a valid method definition b/c *numbers takes the place a variable number of arguments. You have two options as I see it -
def calculate(options={}, *numbers)
or
def calculate(*args)
numbers, options = args[0..-2], args[-1] || {}
if you want to keep the same argument order
The splat argument *numbers needs to be the last argument. Otherwise, how would Ruby know when to treat the last argument as options or as the last number?
You can use (*numbers, options) (without a default value), but that would require that you always pass an options hash to the method (otherwise your last number will be set as the options variable instead).
Try this way:
def calculate(options={},*numbers)
Using optional arguments after the fully optional argument ( the * notation) do not work since it creates an ambiguity.
Read more at:
http://www.skorks.com/2009/08/method-arguments-in-ruby/
You can't use both a splat and a param with a default as last argument, this is too ambiguous for the parser (how to know that the last arg passed is meant to be the options?)
you can work around this in many ways ; one idiom from rails (active support) is :
def calculate(*args)
options = args.extract_options!
# ...
end
where extract_options! is a monkey-patch to Array from ActiveSupport defined as follow :
def extract_options!
last.is_a?(::Hash) ? pop : {}
end
as a side note :
an options hash is not really usefull here. you could pass in just a symbol as first argument, maybe.
if you use a hash, logic could be simpler :
def calculate(*args)
options = args.extract_options!
method = options.fetch(:method, :add)
send method, *args
end
on add, you don't need inject(0), injectuses the first element of your array as a first "memo" value if you don't provide one
you can pass a symbol to inject, which will be the method called on your "memo" value, with "next value" as argument :
(1..10).inject(:+)
# this is the same as
(1..10).inject{ |memo, next| memo + next }
# or, more exactly
(1..10).inject{ |memo, next| memo.send :+, next }

Pass an arithmetic operator as parameter to rails method?

Is it possible to pass an arithmetic operator ( *, +, -, /) as a parameter to a ruby method? I have seen this performed in C++. Is rails capable of something similar?
def calculate(operator)
1254 operator 34
end
puts calculate(+)
Use Object#send:
def calculate(op)
1254.send(op, 34)
end
puts calculate(:+)
This works for any method, including the defined arithmetic operators. Note that you need to send the method name as a symbol or string.
You could use a block, and do something like
def calculate
yield 1254,34
end
calculate &:+ # => 1288
Operators desugar to messages. So you can pass a symbol that names the message you want to send.
def calculate(operator)
1254.send(operator, 34)
end
# to call:
puts calculate(:+)
Note, of course, that this will allow you to call any method, so if you're going to be accepting user input when you're doing this sort of thing, you'll usually want to whitelist.
You could achieve this with Object#send or Object#public_send methods:
def calculate(operator)
# specify accepted operators here:
raise ArgumentError unless [:*, :+, :-, :/].include? operator.to_sym
1254.public_send(op, 34)
end

Overriding the way Integers print in .erb

I want to print numbers in my rails application with commas. As per the answer here, I could wrap every single number I print with
number_with_delimiter(#number, :delimiter => ',')
However, I don't want to go into my views and apply this manually. I'd much rather override the way integers are printed.
In java/jsp, anything inside a <%= %> tag gets a toString() call to evaluate what is printed on the page, so I figured that overriding the to_s method for the Integer class would do:
class Integer
def to_s
number_with_delimiter(self, :delimiter => ',')
end
end
Unfortunately, this doesn't work in that the numbers printed using the <%=%> tag don't appear with commas. (No errors are raised.)
How do I get this right? Does the <%=%> block not automagically call a to_s method on the given object? How does it evaluate what to print?
You're monkey patching the wrong thing, you want to patch Fixnum. For example:
>> class Fixnum
>> def to_s
>> 'pancakes'
>> end
>> end
=> nil
>> 1.to_s
=> "pancakes"
and for ERB:
>> ERB.new('<%= 11 %>').result
=> "pancakes"
That said, your shortcut (like most shortcuts) will probably end up causing you various new and interesting problems elsewhere. You will end up sending '1,000' to something (such as a database or client-side JavaScript or ...) that expects '1000' and you'll get a confusing hissy fit for your efforts. You'll also have to worry about the other numeric classes such as Float and Bignum.
Fix your views, don't kludge around your own laziness.

How are symbols used to identify arguments in ruby methods

I am learning rails and going back to ruby to understand how methods in rails (and ruby really work). When I see method calls like:
validates :first_name, :presence => true
I get confused. How do you write methods in ruby that accept symbols or hashes. The source code for the validates method is confusing too. Could someone please simplify this topic of using symbols as arguments in ruby class and instance methods for me?
UPDATE:
Good one #Dave! But What I was trying out was something like:
def full_name (:first_name, :last_name)
#first_name = :first_name
#last_name = :last_name
p "#{#first_name} #{last_name}"
end
full_name("Breta", "Von Sustern")
Which obviously raises errors. I am trying to understand: Why is passing symbols like this as arguments wrong if symbols are just like any other value?
Symbols and hashes are values like any other, and can be passed like any other value type.
Recall that ActiveRecord models accept a hash as an argument; it ends up being similar to this (it's not this simple, but it's the same idea in the end):
class User
attr_accessor :fname, :lname
def initialize(args)
#fname = args[:fname] if args[:fname]
#lname = args[:lname] if args[:lname]
end
end
u = User.new(:fname => 'Joe', :lname => 'Hacker')
This takes advantage of not having to put the hash in curly-brackets {} unless you need to disambiguate parameters (and there's a block parsing issue as well when you skip the parens).
Similarly:
class TestItOut
attr_accessor :field_name, :validations
def initialize(field_name, validations)
#field_name = field_name
#validations = validations
end
def show_validations
puts "Validating field '#{field_name}' with:"
validations.each do |type, args|
puts " validator '#{type}' with args '#{args}'"
end
end
end
t = TestItOut.new(:name, presence: true, length: { min: 2, max: 10 })
t.show_validations
This outputs:
Validating field 'name' with:
validator 'presence' with args 'true'
validator 'length' with args '{min: 2, max: 10}'
From there you can start to see how things like this work.
I thought I'd add an update for Ruby 2+ since this is the first result I found for 'symbols as arguments'.
Since Ruby 2.0.0 you can also use symbols when defining a method. When calling the method these symbols will then act almost the same as named optional parameters in other languages. See example below:
def variable_symbol_method(arg, arg_two: "two", arg_three: "three")
[arg, arg_two, arg_three]
end
result = variable_symbol_method :custom_symbol, arg_three: "Modified symbol arg"
# result is now equal to:
[:custom_symbol, "two", "Modified symbol arg"]
As shown in the example, we omit arg_two: when calling the method and in the method body we can still access it as variable arg_two. Also note that the variable arg_three is indeed altered by the function call.
In Ruby, if you call a method with a bunch of name => value pairs at the end of the argument list, these get automatically wrapped in a Hash and passed to your method as the last argument:
def foo(kwargs)
p kwargs
end
>> foo(:abc=>"def", 123=>456)
{:abc=>"def", 123=>456}
>> foo("cabbage")
"cabbage"
>> foo(:fluff)
:fluff
There's nothing "special" about how you write the method, it's how you call it. It would be perfectly legal to just pass a regular Hash object as the kwargs parameter. This syntactic shortcut is used to implement named parameters in an API.
A Ruby symbol is just a value as any other, so in your example, :first_name is just a regular positional argument. :presence is a symbol used as a Hash key – any type can be used as a Hash key, but symbols are a common choice because they're immutable values.
I think all replies have missed the point of question; and the fact it is asked by someone who is - I guess - not clear on what a symbol is ?
As a newcomer to Ruby I had similar confusions and to me an answer like following would have made more sense
Method Arguments are local variables populated by passed in values.
You cant use symbols as Arguments by themselves, as you cant change value of a symbol.
Symbols are not limited to hashes. They are identifiers, without the extra storage space of a string. It's just a way to say "this is ...."
A possible function definition for the validates call could be (just to simplify, I don't know off the top of my head what it really is):
def validates(column, options)
puts column.to_s
if options[:presence]
puts "Found a presence option"
end
end
Notice how the first symbol is a parameter all of its own, and the rest is the hash.

Resources