I don't know how to create a ruby method that accepts a hash of parameters. I mean, in Rails I'd like to use a method like this:
login_success :msg => "Success!", :gotourl => user_url
What is the prototype of a method that accepts this kind of parameters? How do I read them?
If you pass paramaters to a Ruby function in hash syntax, Ruby will assume that is your goal. Thus:
def login_success(hsh = {})
puts hsh[:msg]
end
A key thing to remember is that you can only do the syntax where you leave out the hash characters {}, if the hash parameter is the last parameter of a function. So you can do what Allyn did, and that will work. Also
def login_success(name, hsh)
puts "User #{name} logged in with #{hsh[:some_hash_key]}"
end
And you can call it with
login_success "username", :time => Time.now, :some_hash_key => "some text"
But if the hash is not the last parameter you have to surround the hash elements with {}.
With the advent of Keyword Arguments in Ruby 2.0 you can now do
def login_success(msg:"Default", gotourl:"http://example.com")
puts msg
redirect_to gotourl
end
In Ruby 2.1 you can leave out the default values,
def login_success(msg:, gotourl:)
puts msg
redirect_to gotourl
end
When called, leaving out a parameter that has no default value will raise an ArgumentError
Use one single argument. Ruby will transform the named values into a hash:
def login_success arg
# Your code here
end
login_success :msg => 'Success!', :gotourl => user_url
# => login_success({:msg => 'Success!', :gotourl => user_url})
If you really want to make sure you get a hash, instead of the default ruby duck typing, then you would need to control for it. Something like, for example:
def login_success arg
raise Exception.new('Argument not a Hash...') unless arg.is_a? Hash
# Your code here
end
Related
My class has this #run method that so far is just this, to test the testing:
def run
puts "Enter 'class' to create a new class."
input = $stdin.gets.chomp
binding.pry
And in the tests so far I've got
allow($stdin).to receive(:gets).and_return 'class'
cli.run
Doing it this way I am able to see, in the pry session, that input has been set to 'class', as intended.
Is there a way to do with without adding $stdin to my call to gets in my method itself? i.e., input = gets.chomp
I've tried allow(cli.run).to receive(:gets).and_return 'class'
But then in the pry session, input is equal to the first line of the spec file!
You can avoid this as such:
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
describe 'gets' do
it 'belongs to Kernel' do
allow_any_instance_of(Kernel).to receive(:gets).and_return('class')
expect(run).to eq('class')
end
end
The method gets actually belongs to the Kernel module. (method(:gets).owner == Kernel). Since Kernel is included in Object and almost all ruby objects inherit from Object this will work.
Now if run is an instance method scoped in a Class I would recommend scoping the stubbing a bit more such that:
class Test
def run
puts "Enter 'class' to create a new class."
input = gets.chomp
end
end
describe 'gets' do
it 'can be stubbed lower than that' do
allow_any_instance_of(Test).to receive(:gets).and_return('class')
expect(Test.new.run).to eq('class')
end
# or even
it 'or even lower than that' do
cli = Test.new
allow(cli).to receive(:gets).and_return('class')
expect(cli.run).to eq('class')
end
end
Example
I have the following code from understanding computation book. The intention is to change the inspect behavior.
class Number < Struct.new(:value)
def inspect
"<<#{self}>>"
end
def to_s
value.to_s
end
end
It works as expected when I use irb:
irb(main):014:0> Number.new(1)
=> <<1>>
but it does not when I use pry:
[8] pry(main)> n = Number.new(1)
=> #<struct Number value=1>
The Pry is version 0.10.3 on Ruby 2.0.0. Why does it not work?
Pry doesn't just use inspect to display the return value. It calls a proc called print object that is defined in configuration. In lib/pry.rb, you can find that it is set to:
class Pry
# The default print
DEFAULT_PRINT = proc do |output, value, _pry_|
_pry_.pager.open do |pager|
pager.print _pry_.config.output_prefix
Pry::ColorPrinter.pp(value, pager, Pry::Terminal.width! - 1)
end
end
end
In order to use inspect as in irb, set it like this as instructed here:
Pry.config.print = proc {|output, value| output.puts "=> #{value.inspect}"}
Then you will get:
pry(main)> n = Number.new(1)
=> <<1>>
I use Pry version 0.10.4.
I've just added the following lines in my .pryrc file (I think, that is a good
place for such code):
if defined?(BigDecimal)
BigDecimal.class_eval do
def inspect
"<#{to_s('+3F')}>"
end
end
end
And result:
balance: <+100.0>,
commission_amount: <+0.15>
Sawa is right in that Pry uses its own printer, but if you look closer at the source you can see that it actually uses Ruby's PP behind the scenes, and PP defines its own behaviour for pretty printing Structs:
class Struct # :nodoc:
def pretty_print(q) # :nodoc:
q.group(1, sprintf("#<struct %s", PP.mcall(self, Kernel, :class).name), '>') {
q.seplist(PP.mcall(self, Struct, :members), lambda { q.text "," }) {|member|
q.breakable
q.text member.to_s
q.text '='
q.group(1) {
q.breakable ''
q.pp self[member]
}
}
}
end
def pretty_print_cycle(q) # :nodoc:
q.text sprintf("#<struct %s:...>", PP.mcall(self, Kernel, :class).name)
end
end
It's worth checking out the documentation (though it is only brief) if you are interested in learning more about this.
So in your struct you could also define your own pretty_print and pretty_print_cycle methods, which would mean Pry could print these how you want, without having to override their DEFAULT_PRINT proc.
I am using DataMapper for Database access. My goal is to send the models to an webservice as read-only object. This is my current try:
class User
include DataMapper::Resource
def to_yaml(opts = {})
mini_me = OpenStruct.new
instance_variables.each do |var|
next if /^#_/ =~ var.to_s
mini_me.send("#{var.to_s.gsub(/^#/, '')}=", instance_variable_get(var))
end
mini_me.to_yaml(opts)
end
....
end
YAML::ENGINE.yamler = 'psych'
u = User.get("hulk")
p u.to_yaml
# => "--- !ruby/object:OpenStruct\ntable:\n :uid: hulk\n :uidNumber: 1000\n :gidNumber: 1001\n :email: hulk#example.com\n :dn: uid=hulk,ou=People,o=example\n :name: Hulk\n :displayName: Hulk\n :description: Hulk\n :homeDirectory: /home/hulk\n :accountFlags: ! '[U ]'\n :sambaSID: S-1-5-21-......\nmodifiable: true\n"
p [ u ].to_yaml # TypeError: can't dump anonymous class Class
Any ideas how to make this work and get rid of the exception?
Thanks,
krissi
Using to_yaml is deprecated in Psych, and from my testing it seems to be actually broken in cases like this.
When you call to_yaml directly on your object, your method gets called and you get the result you expect. When you call it on the array containing your object, Psych serializes it but doesn’t correctly handle your to_yaml method, and ends up falling back onto the default serialization. In your case this results in an attempt to serialize an anonymous Class which causes the error.
To fix this, you should use the encode_with method instead. If it’s important that the serialized form is tagged as an OpenStruct object in the generated yaml you can use the represent_object (that first nil parameter doesn’t seem to be used):
def encode_with(coder)
mini_me = OpenStruct.new
instance_variables.each do |var|
next if /^#_/ =~ var.to_s
mini_me.send("#{var.to_s.gsub(/^#/, '')}=", instance_variable_get(var))
end
coder.represent_object(nil, mini_me)
end
If you were just using OpenStruct for convenience, an alternative could be something like:
def encode_with(coder)
instance_variables.each do |var|
next if /^#_/ =~ var.to_s
coder[var.to_s.gsub(/^#/, '')]= instance_variable_get(var)
end
end
Note that Datamapper has its own serializer plugin that provides yaml serialization for models, it might be worth looking into.
This is a description of how to create a helper method in Rspec taken from the Rspec book (page 149). This example assumes that there is a method called 'set_status' which is triggered when the 'Thing' object is created.
Both sets of code create a new 'Thing' object, set the status, then do 'fancy_stuff'. The first set of code is perfect clear to me. One of the 'it' statements it triggered, which then calls the 'create_thing' method with options. A new 'Thing' object is created and the 'set_status' method is called with the 'options' attribute as the parameter.
The second set of code is similar. One of the 'it' statements is triggered, which then calls the 'given_thing_with' method while passing ':status' hash assignment as a parameter. Within the 'given_thing_with' method the 'yield' is triggered taking the 'Thing.new' as a parameter. This is where I am having trouble. When I try to run this code I get an error of "block given to yield". I understand that whatever attributes that are passed by yield will be returned to the 'thing' in pipe brace from the 'it' statement that called the 'given_thing_with' method. I can get the new
What I don't understand is why the code block is not called in the 'given_thing_with' method after the 'yield' command. In other words, I can't code in that block to run.
Thanks in advance for your help.
The remainder of this question is quoted directly from the Rspec book:
describe Thing do
def create_thing(options)
thing = Thing.new
thing.set_status(options[:status])
thing
end
it "should do something when ok" do
thing = create_thing(:status => 'ok')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
it "should do something else when not so good" do
thing = create_thing(:status => 'not so good')
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
One idiom you can apply to clean this up even more is to yield self from initializers in your objects. Assuming that Thing's initialize() method does this and set_status() does as well, you can write the previous like this:
describe Thing do
def given_thing_with(options)
yield Thing.new do |thing|
thing.set_status(options[:status])
end
end
it "should do something when ok" do
given_thing_with(:status => 'ok') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
it "should do something else when not so good" do
given_thing_with(:status => 'not so good') do |thing|
thing.do_fancy_stuff(1, true, :move => 'left', :obstacles => nil)
...
end
end
end
The example in the book is a bit confusing because the implementation of Thing is not shown. To make this work you need to write Thing like so:
class Thing
def initialize
yield self
end
end
When given_thing_with is called it yields a new Thing, which will yield itself when it is constructed. This means that when the inner code block (the one containing thing.set_status) is executed it will have a reference to he newly built Thing.
There are 2 issues with the code from book.
1. Setting up the initializer to yield itself
When the Thing object is created, it needs an initializer and need yield itself.
class Thing
def initialize
yield self
end
end
However, this alone will still causes an error, at least on my system, which is Ruby 1.9.3. Specifically, the error is 'block given to yield (SyntaxError)'. This doesn't make much sense, since that is what we want it to do. Regarless, that is the error I get.
2. Fixing the 'block given to yield' error
This is not as obvious and has something to do with either Ruby or the 'yield' statement, but creating a block using 'do...end' as was written in the book and is shown below causes the error.
yield Thing.new do |thing|
thing.set_status(options[:status])
end
Fixing this error is simlpy a matter of creating the block using braces, '{...}', as is shown below.
yield Thing.new { |thing|
thing.set_status(options[:status])
}
This is not good form for multiline Ruby code, but it works.
Extra. How the series of yields works to set the parameters of the 'Thing' object
The problem is already fixed, but this explains how it works.
the "caller block" calls 'given_thing_with' method with a parameter
that method yields back to the "caller block" a new "Thing" and a block (I'll call it the "yield block")
to execute the "yield block", the Thing class needs the initialization and 'yield self', otherwise the 'set_status' method will never be run because the block will be ignored
the new "Thing" is already in the "caller block" and has it's status set and now the relevant method is executed
Take the following class:
class Automator
def fill_specific_form(fields)
fields.each_pair do |key, value|
puts "Setting '#{key}' to '#{value}'"
end
end
end
a = Automator.new
a.fill_specific_form :first_name => "Mads", :last_name => "Mobæk"
# => Setting 'first_name' to 'Mads'
# => Setting 'last_name' to 'Mobæk'
Is it possible to do the same without a hash? Since all parameters are required, I want a method with the following signature:
fill_specific_form(first_name, last_name)
In my mind this would be possible by having the method body reflect and iterate over its parameters, thus achieving the same result.
How would you implement this? Does a pattern/idiom for this exist already? Two obvious benefits would be parameter information in IDEs and not having to check if all hash keys are supplied.
What I want to avoid is:
puts "Setting first_name to #{first_name}"
puts "Setting last_name to #{last_name}"
# and so on
If you set no other local variables inside the method, local_variables will give you a list of the method's parameter names (if you do set other variables you can just call local_variables first thing and remember the result). So you can do what you want with local_variables+eval:
class Automator
def fill_specific_form(first_name, last_name)
local_variables.each do |var|
puts "Setting #{var} to #{eval var.to_s}"
end
end
end
Automator.new().fill_specific_form("Mads", "Mobaek")
Be however advised that this is pure evil.
And at least for your example
puts "Setting first_name to #{first_name}"
puts "Setting last_name to #{last_name}"
seems much more sensible.
You could also do fields = {:first_name => first_name, :last_name => last_name} at the beginning of the method and then go with your fields.each_pair code.
I don't quite understand. Do you want to receive all parameters within a single array?
def fill_specific_form *args
#Process args
end
To reflect on a method's (or Proc's) parameters, you can use Proc#parameters, Method#parameters or UnboundMethod#parameters:
->(m1, o1=nil, *s, m2, &b){}.parameters
# => [[:req, :m1], [:opt, :o1], [:rest, :s], [:req, :m2], [:block, :b]]
However, in your case, I don't see why you need reflection, since you already know the names of the parameters anyway.