I18n: how can I guard against empty interpolation arguments? - ruby

Given the following i18n key:
greeting: "Hi %{name}! Feeling %{adjective}?"
If I call I18n.t!("greeting", adjective: "strigine"), I get an exception: I18n::MissingInterpolationArgument. This is good.
However, if I pass no arguments at all, I just get the raw string.
I18n.t!("greeting") # => "Hi %{name}! Feeling %{adjective}?"
I want to ensure that this doesn't happen. Is there a version of this method call that raises if no arguments are given, or do I have to code it myself?

I18n does this on purpose
The reasons are documented in the tests as follows:
If no interpolation parameter is not given, I18n should not alter the
string. This behavior is due to three reasons:
Checking interpolation keys in all strings hits performance, badly;
This allows us to retrieve untouched values through I18n. For example I could have a middleware that returns I18n lookup results in
JSON to be processed through Javascript. Leaving the keys untouched
allows the interpolation to happen at the javascript level;
Security concerns: if I allow users to translate a web site, they can insert %{} in messages causing the I18n lookup to fail in every
request.
How to work around it
If you want an exception in this case, you can define a method like this:
# Needed because I18n will happily return the raw template string if given
# no interpolation arguments
# https://github.com/svenfuchs/i18n/blob/v0.7.0/lib/i18n/tests/interpolation.rb#L6-L21
def i18n_strict_t!(key, options = {})
localized = I18n.t!(key, options)
if missing = localized.match(I18n::INTERPOLATION_PATTERN)
fail I18n::MissingInterpolationArgument.new(
missing.captures.first, options, localized
)
end
localized
end
Usage:
i18n_strict_t!("greeting")
# => I18n::MissingInterpolationArgument: missing
# interpolation argument "name" in
# "Hi %{name}! Feeling %{adjective}?" ({} given)
i18n_strict_t!("greeting", name: "Carla", adjective: "taciturn")
# => "Hi Carla! Feeling taciturn?"
Or for slower but simpler implementation:
def i18n_strict_t!(key, options = {})
options[:force_interpolation] = true if options.empty?
I18n.t!(key, options)
end

Related

Custom Methods for Treetop Syntax Nodes

I have a Treetop PEG grammar that matches some keys. I want to look up the values associated with those keys in a hash I give the parser. How can I make it so that the syntax nodes have access to methods or variables from the parser?
For example, here's a simple grammar that finds a single word and tries to look up its value:
# var.treetop
grammar VarResolver
include VarLookup
rule variable
[a-zA-Z] [a-zA-Z0-9_]*
{
def value
p found:text_value
find_variable(text_value)
end
}
end
end
Here's a test file using it:
# test.rb
require 'treetop'
module VarLookup
def set_variables(variable_hash)
#vars = variable_hash
end
def find_variable(str)
#vars[str.to_sym]
end
end
Treetop.load('var.treetop')
#p = VarResolverParser.new
#p.set_variables name:'Phrogz'
p #p.parse('name').value
Running this test, I get the output:
{:found=>"name"}
(eval):16:in `value': undefined method `find_variable'
for #<Treetop::Runtime::SyntaxNode:0x00007f88e091b340> (NoMethodError)
How can I make find_variable accessible inside the value method? (In the real parser, these rules are deeply nested, and need to resolve the value without returning the actual name to the top of the parse tree. I cannot just return the text_value and look it up outside.)
This is a significant weakness in the design of Treetop.
I (as maintainer) didn't want to slow it down further by
passing yet another argument to every SyntaxNode,
and break any custom SyntaxNode classes folk have
written. These constructors get the "input" object, a Range
that selects part of that input, and optionally an array
of child SyntaxNodes. They should have received the
Parser itself instead of the input as a member.
So instead, for my own use (some years back), I made
a custom proxy for the "input" and attached my Context
to it. You might get away with doing something similar:
https://github.com/cjheath/activefacts-cql/blob/master/lib/activefacts/cql/parser.rb#L203-L249

Ruby method with optional options and &block parameter

Hey there
Is it possible to have optional attributes and a block as parameters
for a method call?
Example: I have to call
method(foo, foo: bar, -> { do_something }
and tried it with
def method(foo, *bar, &block)
end
As for my understanding the block always has to be at last position?
After a bit of research I found out the unary(?) * seems to be for
arrays. Since I try to pass a Hash I changed the code to
def method(foo, bar={}, &block)
end
But this doesn't do the trick either. I guess its because he cant
figure out where the bar ends and the block starts.
Any ideas or suggestions? Thank you in advance
Append: Just for the curious why I need this. We have a big json
schema running and have a small DSL that builds the json from the
model definitation. Without going to much into detail we wanted to
implement exportable_scopes.
class FooBar
exportable_scope :some_scope, title: 'Some Scope', -> { rewhere archived: true }
end
On some initializer this is supposed to happens:
def exportable_scope scope, attributes, &block
scope scope block
if attributes.any?
attributes.each do |attribute|
exportable_schema.scopes[scope] = attribute
end
else
exportable_schema.scopes[scope] = {title: scope}
end
end
So this is working fine, I just need a hint for the method
parameters.
Yes, it is possible.
When mixing different kinds of parameters, they have to be included in the method definition in a specific order:
Positional parameters (required and optional) and a single splat parameter, in any order;
Keyword parameters (required and optional), in any order;
Double splat parameter;
Block parameter (prefixed with &);
The order above is somewhat flexible. We could define a method and begin the parameter list with a single splat argument, then a couple of optional positional arguments, and so on. Even though Ruby allows that, it's usually a very bad practice as the code would be hard to read and even harder to debug. It's usually best to use the following order:
Required positional parameters;
Optional positional parameters (with default values);
Single splat parameter;
Keyword parameters (required and optional, their order is irrelevant);
Double splat parameter;
Explicit block parameter (prefixed with &).
Example:
def meditate cushion, meditation="kinhin", *room_items, time: , posture: "kekkafuza", **periods, &b
puts "We are practicing #{meditation}, for #{time} minutes, in the #{posture} posture (ouch, my knees!)."
puts "Room items: #{room_items}"
puts "Periods: #{periods}"
b.call # Run the proc received through the &b parameter
end
meditate("zafu", "zazen", "zabuton", "incense", time: 40, period1: "morning", period2: "afternoon" ) { puts "Hello from inside the block" }
# Output:
We are practicing zazen, for 40 minutes, in the kekkafuza posture (ouch, my knees!).
Room items: ["zabuton", "incense"]
Periods: {:period1=>"morning", :period2=>"afternoon"}
Hello from inside the block
Notice that when calling the method, we have:
Provided the cushion mandatory positional argument;
Overwritten the default value of the meditation optional positional argument;
Passed a couple of extra positional arguments (zabuton and incense) through the *room_items parameter;
Provided the time mandatory keyword argument;
Omitted the posture optional keyword argument;
Passed a couple of extra keyword arguments (period1: "morning", period2: "afternoon") through the **periods parameter;
Passed the block { puts "Hello from inside the block" } through the &b parameter;
Please note the example above servers only to illustrate the possibility of mixing different types of parameters. Building a method like this in real code would be a bad practice. If a method needs that many arguments, it's probably best to split it into smaller methods. If it's absolutely necessary to pass that much data to a single method, we should probably create a class to store the data in a more organized way, then pass an instance of that class to the method as a single argument.

rspec check any argument is hash including particular pair

I have a shared rspec example for a number of functions (rest requests). Each function receives a hash which I want to check, but they can be at different positions, e.g.:
get(url, payload, headers)
delete(url, headers)
I want to write the following test:
shared_examples_for "any request" do
describe "sets user agent" do
it "defaults to some value" do
rest_client.should_receive(action).with(????)
run_request
end
it "to value passed to constructor"
end
end
end
describe "#create" do
let(:action) {:post}
let (:run_action) {rest_client.post(url, payload, hash_i_care_about)}
it_behaves_like "any request"
end
The question is, how can I write a matcher which matches any of the arguments, e.g.:
client.should_receive(action).with(arguments_including(hash_including(:user_agent => "my_agent")))
In order to match any argument, you can pass a block to should_receive which can then check the arguments in any fashion you want:
client.should_receive(action) do |*args|
# code to check that one of the args is the kind of hash you want
end
You could search the list of args for an argument of hash type, you could pass a parameter into the shared example indicating what position in the argument list the hash should exist in etc. Let me know if this is not clear and I can provide more detail.
This is covered briefly in https://www.relishapp.com/rspec/rspec-mocks/docs/argument-matchers
My example is a little bit in conjunction with what Peter Alfvin suggested:
shared_examples "any_request" do |**args|
action = args[:action]
url = args[:url]
payload = args[:payload]
headers = args[:headers]
# ...etc
case action
when :get
# code to carry on
when :post
# code to continue
end
end
This way you can define and work with the arguments as your code expands, in any order and in any amount. You would call the function like so:
it_behaves_like "any_request", { action: :post,
url: '/somewhere' }
Undeclared arguments, like :payload in this example would automatically carry the value of nil. Test its existence like: if payload.nil? or unless payload.nil? etc.
Note: This works for Ruby 2.0, Rails 4.0, rspec 2.13.1. Actual code definitions may vary on earlier versions.
Note_note: ... do |**args| the two asterisks is not a typo ;)

Strange ruby syntax

what the syntax is in Action Mailer Basics rails guide ?
class UserMailer < ActionMailer::Base
def welcome_email(user)
recipients user.email
from "My Awesome Site Notifications <notifications#example.com>"
subject "Welcome to My Awesome Site"
sent_on Time.now
body {:user => user, :url => "http://example.com/login"}
end
end
How should i understand the construction, like
from "Some text for this field"
Is it an assignment the value to a variable, called "from" ?
No, it's a method call. The name of the method is from, and the argument is a string. In Ruby, parentheses around method calls are optional, so
from "Some text for this field"
is the same as
from("Some text for this field")
Rails (and many Ruby libraries) like to express code in a natural language style, though, so the parentheses-less version reads better, hence why it is used in examples.
It is a call to a method from with the argument "Some text for this field"
The method comes from the ActionMailer::Base class that your UserMailer extends from.
In Ruby the parentheses around a method call are optional unless something would be ambiguous so the statement is equivalent to from("Some text for this field")
Rails has a coding style that prefers to be close to natural language where possible, hence not using parentheses unless necessary.
Calling this method sets an instance variable #from to the value you provide so that it can be used later when sending the message.
Normally when you have accessor methods for getting and setting a variable you would have from= to set the value and from to return the value, however ActionMailer uses something called adv_attr_accessor to define the from method so that if you call it with a parameter then it acts as a setter but if you call it with no parameters then it acts as a getter.
This can be seen in actionmailer-2.x.x/lib/action_mailer/base.rb and actionmailer-2.x.x/lib/action_mailer/adv_attr_accessor.rb
It's not an assignment. In Ruby, assignments are done using the assignment operator = like this:
var = val
You are probably thinking of some Lisp dialects where assignment looks like this:
(def var val)
It's just a simple receiverless message send.
In Ruby, the general syntax for a message send is
receiver.selector(argument1, argument2)
However, if the receiver is self, you can leave off the receiver, so
selector(argument1, argument2)
is the same as
self.selector(argument1, argument2)
[Note: this is not quite true. In Ruby, private methods can only be invoked via a receiverless message send, so if in this example self responds to the selector message by invoking a private method, only the first variant will work, the second will raise a NoMethodError exception.]
Also, in cases where there are no ambiguities, you can leave off the parentheses around the arguments like this:
receiver.selector argument1, argument2
If you put the two things together, you can now see that
selector argument1, argument2
is equivalent to
self.selector(argument1, argument2)
and thus
from "Some text for this field"
is equivalent to
self.from("Some text for this field")
There is a third shortcut in Ruby's message sending syntax: if the very last argument to a message send is a Hash literal, then you can leave out the curly braces. So, the last line in the above example could also be written as
body :user => user, :url => "http://example.com/login"
Also, in Ruby 1.9, a Hash literal where all keys are Symbols can be written using an alternative Hash literal syntax:
{ key1: val1, key2: val2 }
is the same as the old syntax
{ :key1 => val1, :key2 => val2 }
which means that, at least in Ruby 1.9, that last line could also be written as
body user: user, url: "http://example.com/login"
You could also call from an attribute. It's a property of the email, but how it's implemented is hidden from you (encapsulation). This is a Good Thing. It means that if Rails core decided it's better to change #from into several variables, you wouldn't need to change any of your code.

How can I validate a function passed to a method in Ruby?

I'm writing a library to humanize bytes in Ruby (e.g. to turn the byte count 1025 into the string 1.1K), and I'm stuck on one element of the design.
The plan is to extend Numeric with ahumanize method that returns a human-friendly string when called on a number. After looking at the source of Number::Bytes::Human (a Perl module that I like a lot for this), I decided to add two options to the method: one to use 1000 byte blocks and one to use floor rather than ceil for the default rounding function.
In order to be maximally flexible, the method's definition uses a hash for the parameters, so that users can change one or both of the options. If no parameters are passed, a default hash is used. That gives me something like this:
def humanize(params = {})
params = {:block => 1024, :r_func => lambda }.merge params
# yada yada
end
Ideally, I would like to let the user pass a function as the value of params[:r_func], but I can't figure out how to validate that it's either ceil or floor. Because I can't get a handle on this, I've ended up doing the following, which feels pretty clumsy:
def humanize(params = {})
params = {:block => 1024, :r_func => 'ceil' }.merge params
if params[:r_func].eql? 'ceil'
params[:r_func] = lambda { |x| x.ceil }
elsif params[:r_func].eql? 'floor'
params[:r_func] = lambda { |x| x.floor }
else
raise BadRound, "Rounding method must be 'ceil' or 'floor'."
end
# blah blah blah
end
If anyone knows a trick for peeking at the method that a Ruby lambda contains, I would love to hear it. (I'm also happy to hear any other design advice.) Thanks.
There's no reason to let the user pass a method in if you're going to be that draconian about what they are allowed to pass (you know there are other rounding schemes besides ceiling and floor, right?)
If you want to restrict the user to ceiling and floor, just allow them to pass the symbol :ceiling or :floor in. A more flexible design would be to allow the method to take a block which receives a single parameter, the number to be rounded. Then the user could use whatever rounding algorithm they prefer, including custom ones.
By the way, Numeric#humanize falls into that category of monkeypatches with such a popular name that you are likely to run into namespace collisions (and resulting subtle bugs) in anything but a small, personal project.
I don't see any point in having the caller pass a lambda if you're not going to actually call the thing. Make it a symbol instead and you can do something like:
raise BadRound, "Rounding method must be :ceil or :floor." unless [:ceil, :floor].include? params[:r_func]
op = lambda {|x| x.send params[:r_func]}
# blah blah blah
Why have them pass a function pointer instead of a boolean? That way you avoid the problem of having to validate the function.

Resources