What's the meaning of the semicolon after the id-parameter? Please see my comment within the snippet.
That isn't a Ruby-symbol. Isn't it? Otherwise the semicolon would be on the left-side. The ID is an integer. A bit confused currently ...
field :vendor, VendorType, null: false,
description: "A single car-vendor." do
argument :id, ID, required: true
end
def vendor(id:) # Why is there a semicolon after "id"?
Vendor.find(id)
end
The syntax is defining a keyword argument that the caller has to explicitly state:
def v(id:)
p id
end
v(1) # error
v(id: 1)
1
=> 1
v(something: 1)
ArgumentError (missing keyword: id)
It has its uses, primarily for interaction in the console and ensuring the user knows what they are doing before they call the method, or ensuring new developers of the codebase understand how to call the method.
If the required argument is missing, Ruby throws a nice ArgumentError which is concise and reduces the cognitive overhead for future developers to engage with a codebase.
Related
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.
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
Short story:
"Why does defined?(59[0][:whatever]) evaluate to true?"
Long story:
I came across some strange behaviour lately which threw me off.
I was developing a method that did some washing of the data:
#Me washing input data:
def foo(data)
unless data && defined?(data[0]) && defined?(data[0][:some_param])
method2(data[0][:some_param])
else
freak_out()
end
end
I usually write tests where I spew in all kinds of junk data to make sure nothing strange happens:
describe "nice description" do
it "does not call method2 on junk data" do
expect(some_subject).to_not receive(:method2)
random_junk_data_array.each do |junk|
some_subject.foo(junk)
end
end
end
Well, method2 was called here. It happened when junk was a fixnum.
I am using ruby 2.1.0, and I can see that Fixnum has a #[] method which fetches the bit at that position, nice.
But why is fixnum[0][:some_param] considered to be defined?
defined? expression tests whether or not expression refers to anything recognizable (literal object, local variable that has been initialized, method name visible from the current scope, etc.). The return value is nil if the expression cannot be resolved. Otherwise, the return value provides information about the expression.
Let me explain with an example :-
defined?("a") # => "expression"
# this returns "method", as there is a method defined on the class String. So, the
# method invocation is possible, and this is a method call, thus returning `"method"`.
defined?("a".ord) # => "method"
# it return nil as there is no method `foo` defined on `"a"`,
# so the call is not possible.
defined?("a".foo) # => nil
Now coming to your point :-
As you said data[0] gives a Fixnum instance, and of-course Fixnum#[] exist. Thus fixnum_instance[:some_param] is also a valid method call. It just testing if the method is defined or not. If defined, it will tell yes this is a "method" expression. Otherwise nil. Not actually will check if the method call succeeded or not.
In Ruby all objects has truthy values except nil and false, thus "method" being a string object also has the truthy value, thus your condition got succeeded.
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 ;)
Say I have class Player and I want a boolean method to check if a player is attacked:
class Player
attr_accessor :name, :health, :attacked?
def initialize(name)
#name = name
#health = 100
#attacked? = false
end
end
I get a syntax error:
SyntaxError: (irb):14: syntax error, unexpected '='
#attacked? = false
^
from /usr/bin/irb:12:in `<main>'
Removing the question mark from attacked fixes the problem, but I thought it would better follow convention to have attacked? in my code. It's not a big deal to forgo the question mark, but why are zero? and nil? conventions when#variables? and def methods?= are invalid?
Note that if you comment out the line causing your error (#attacked? = false), you will still get an error relating to the question mark:
NameError: invalid attribute name `attacked?'
The problem is that ? is not a valid character in a variable name. The first error (the SyntaxError that you’re seeing) is caused at parse time and caught immediately. The second error is caused when Ruby tries to evaluate the code and cannot create an instance variable with a name containing an invalid character.
A question mark is a valid character at the end of a method name though (actually it’s possible to have a method with a ? anywhere in its name, but you can’t call such a method directly).
One way to achieve what you want is something like this:
class Player
attr_accessor :name, :health, :attacked
alias :attacked? :attacked
def initialize(name)
#name = name
#health = 100
#attacked = false
end
end
This leaves attacked without the question mark, but adds attacked? as an alias.
I've run into the same problem before, and wished that I could make instance variables with a trailing question mark. It seems to be a corner case in Ruby's grammar. Check this out:
>> 1 ? 2 : 3
=> 2
>> 1?2:3
=> 2
>> #a = true
=> true
>> #a?1:2
=> 1
>> a = true
=> true
>> a ? 1 : 2
=> 1
>> a?1:2
SyntaxError: (irb):9: syntax error, unexpected ':', expecting $end
So the ? symbol is overloaded in Ruby's grammar -- it is used for the ternary operator, and as part of valid identifiers for method names. This causes ambiguity in the last case, where Ruby's lexer seems to bite off the a? as a single token, which leaves it unable to parse the rest of the ternary operator properly. With instance variables, this can't happen.
Again, I agree that allowing question marks in instance variable names would be very useful, certainly more valuable than making some obscure uses of the ternary operator non-ambiguous.