Reflection on method parameters in Ruby - ruby

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.

Related

Can you choose watir commands through a variable?

I'm fairly new to programming, and I'm not sure what keywords I should be looking for.
I'm doing something like this right now:
def click(text, type)
b.span(:text=> text).click if type == 'span'
b.button(:name=> text).click if type == 'button'
b.image(:src=>text).click if type == 'image'
b.button(:title=>text).click if type == 'title'
end
I don't like it because it isn't scaling very well. I want to do something like:
def click(text,type)
b.type(:text=> text).click
end
It throws an undefined method error if I try to enter the type without quotes, but it's definitely not a string. How do I tell the script to use watir-webdriver span/button/image/etc?
It's hard to figure out exactly what it is you want to do with this method or why it's even necessary--or why your type parameter would ever be anything other than a string--but here's a way to help you clean up your code that's similar to what orde suggested.
Note that it's unclear what you're implying when you say "it's definitely not a string." If it's not a string, what is it? Where is it coming from that you are sticking it into this method's parameters without knowing what type of Object it is?
So... I'm assuming your type doesn't have to be a String object, so I made it so it takes symbols...
def click(text, type)
types={span: :text, button: :name, image: :src, title: :title }
#b.send(type, {types[type]=>text}).click
end
I'm not sure how you are calling your click method in your scripts, but here is a contrived example that seems to work:
require 'watir-webdriver'
def click_method(element, text)
#b.element(:text => "#{text}").click
end
#b = Watir::Browser.new
#b.goto "http://www.iana.org/domains/reserved"
click_method("link", "Domains")
EDIT:
require 'watir-webdriver'
def method_not_named_click(el, locator, locator_val)
if locator_val.is_a? String
#b.send(el, locator => "#{locator_val}").click
elsif locator_val.is_a? Integer
#b.send(el, locator => locator_val).click
end
end
#b = Watir::Browser.new
#b.goto "http://www.iana.org/domains/reserved"
method_not_named_click(:a, :text, "Domains")
method_not_named_click(:a, :index, 3)

Is it possible to ask Factory Girl what associations a given factory has?

Factory Girl is incredibly useful for functional testing, but has one annoying property that makes it slightly harder to use in unit tests, where I don't want to rely on the test database. I often use Factory.build to create a factory that I can then pass around or assign to an ActiveRecord.find call using flexmock:
require 'test_helper'
require 'flexmock'
class SomeMixinTest < ActiveSupport::TestCase
include FlexMock::TestCase
def setup
#foo = Factory.build(:foo, :id => 123,
:bar => Factory.build(:bar, :id => 456,
:baz => Factory.build(:baz, :id => 789)
)
)
flexmock Foo, :find => #foo
end
def test_find_by_reverse_id
assert_equal #foo, Foo.find_by_reverse_id(321)
end
end
This pattern is very nice, since it cares not about the presence of the database, and runs much faster than if the objects had to actually be persisted. However, it is a bit annoying to have to build the associated objects manually. If you don't, the associated objects are actually created in the database by the build call, as if you had used create instead.
assert_equal [], Foo.all
foo = Factory.build :foo # build my associations too, please
assert_equal [], Foo.all # look Ma, no mocks!
assert_equal [], Bar.all # <=== ASSERTION FAILED
assert_equal [], Baz.all
This is non-intuitive to say the least, and causes an actual problem when I'm testing a few classes that need to play nicely with a mixin. I want to be able to do this:
KLASSES_UNDER_TEST = [Foo, Bar, Baz]
def test_find_by_reverse_id
KLASSES_UNDER_TEST.each do |klass|
objects = (123..456).map do |id|
Factory.build klass.to_s.downcase.to_sym, :id => id
end
flexmock klass, :all => objects
objects.each do |object|
assert_equal object, klass.find_by_reverse_id(object.id.to_s.reverse), "#{klass} #{object.id}"
end
end
But this has the nasty side effect of creating 333 Bars and 666 Bazes ("Baz" does sound kind of like a demon's nickname, so maybe that's fitting) in the database, making this test slower than molasses flowing uphill in the winter.
I'd like to create a helper method like this:
def setup_mocks(klass)
klass_sym = klass.to_s.downcase.to_sym
objects = (123..456).map{|id|
associated_objects = Hash[
Factory.associations(klass_sym).map do |association|
[ association, setup_mocks(association, 1) ]
end
]
Factory.build klass_sym, associated_objects
end
flexmock klass, :all => objects
objects
end
So, does anything like Factory.associations exist?
I've not tested this, but looking at the source it seems that something like this should work:
FactoryGirl.find(:factory_name).associations

"Here methods" in Ruby?

I'm writing a few helpers to DRY up my tests. I pictured something like:
class ActiveSupport::TestCase
def self.test_presence_validation_of model, attribute
test "should not save #{model.to_s} with null #{attribute.to_s}", <<-"EOM"
#{model.to_s} = Factory.build #{model.to_sym}, #{attribute.to_sym} => nil
assert !#{model.to_s}.save, '#{model.to_s.capitalize} with null #{attribute.to_s} saved to the Database'
EOM
# Another one for blank attribute.
end
end
So that this:
class MemberTest < ActiveSupport::TestCase
test_presence_validation_of :member, :name
end
Executes exactly this at MemberTest class scope:
test 'should not save member with null name' do
member = Factory.build :member, :name => nil
assert !member.save, 'Member with null name saved to the Database'
end
Is it possible to do it this way (with a few adaptations, of course; I doubt my "picture" works), or do I have to use class_eval?
Have you seen Shoulda? It's great for testing common Rails functionality such as validations, relationships etc. https://github.com/thoughtbot/shoulda-matchers
In this case, it seems class_eval is necessary since I want to interpolate variable names into actual code.
Illustrated here.

Ruby - Method call to object in array

I'm working with a Ruby project for school, and have sadly not been able to find an answer to this question in my literature.
I have an array of camping lots, each containing a guest. I initialize the lots like this:
lots = Array.new
for i in (1..36)
lots[i] = Lot.new(i)
end
Further down I create a Guest object, initialize it, and now I want to add the Guest to my Lot. The method in the class Lot looks like this:
def AddGuest(guest)
#guest = guest
end
The problem comes when I want to call the method, as the Lot is in an Array.
lots[lotnumber].AddGuest(guest)
This call gives me the error:
undefined method `+#' for #<Guest:0x2c1ff14> (NoMethodError)
I have used require, so the classes know about each other. I've had quite a hard time understanding Ruby, could my error be that I try to access the AddGuest method in the Array class? I'm used to doing things like this in C++.
Below is the full source (the relevant parts at least).
Entire Lot class:
class Lot
def initialize(number)
#gauge = rand(2000) + 2000
#number = number
#guest = false
end
def Occupied()
return #guest
end
def AddGuest(guest)
#guest = guest
end
def RemoveGuest()
#guest = false
end
end
Parts of main.rb
#includes
require 'guest'
require 'lot'
#initiate comparison variables
userInput = "0"
numberOfGuests = 0
foundLot = false
guests = Array.new
lots = Array.new
#initialize lot list
for i in (1..36)
lots[i] = Lot.new(i)
end
Player input omitted
#make sure lot is not taken
while foundLot == false do
lotnumber = rand(35)+1
if lots[lotnumber].Occupied() == false then
foundLot = "true"
end
end
foundLot = false
guest = Guest.new(firstName, lastName, adress, phone, arrival, lotnumber)
guests.insert(numberOfGuests, guest)
numberOfGuests++
lots[lotnumber].AddGuest(guest) #this is where error hits
end
end
end
The error appears to be related to your use of the ++ operator, which is, quite naturally, supported in C++, but is not supported in Ruby.
The equivalent is:
numberOfGuests += 1
A couple little tips...
[1]
A slightly more idiomatic way to write this...
for i in (1..36)
lots[i] = Lot.new(i)
end
would be...
(1..36).each { |i| lots[i] << Lot.new(i) }
[2]
To remove a Guest from a Lot, you might want to set it to nil rather than false. This would be my suggestion...
class Lot
def initialize(number)
#gauge = rand(2000) + 2000
#number = number
# Don't need to set #guest -- it's nil by default.
end
# In Ruby, methods that return a boolean often have a "?".
# Makes it "read better" when you call the method. (See
# usage sample.)
def occupied?
! #guest.nil?
end
# There's a more commonplace way to do this. See below...
def add_guest(guest)
#guest = guest
end
def remove_guest()
#guest = nil
end
end
Example of usage:
>> lot = Lot.new(2)
=> #<Lot:0x1300920 #number=2, #gauge=3444>
>> lot.occupied
=> false
>> lot.add_guest('A guest')
=> "A guest"
>> lot.occupied?
=> true
>> lot.remove_guest
=> nil
>> lot.occupied?
=> false
Take two...
It's conventional to use attr_accessor methods in your class definition. They automatically add getter and setter methods to your class. You could do that instead of add_guest and remove_guest if you wanted to follow the common Ruby pattern...
class Lot
attr_accessor :number, :gauge, :guest
def initialize(number)
#gauge = rand(2000) + 2000
#number = number
end
def occupied?
! #guest.nil?
end
end
Usage...
irb(main):017:0> lot = Lot.new(3)
=> #<Lot:0xb7f7fca8 #gauge=3186, #number=3>
Set the Guest of a Lot (like add_guest)...
irb(main):019:0> lot.guest = 'A guest'
=> "A guest"
irb(main):020:0> lot.occupied?
=> true
Get the Guest for a Lot...
irb(main):025:0> lot.guest
=> "A guest"
Remove the Guest...
irb(main):021:0> lot.guest = nil
=> nil
irb(main):023:0> lot.occupied?
=> false
Generally Ruby method names are not capitalized. The convention are simply: ClassName, CONSTANT, method_name.
Since you have an Array of Lot objects, the following should be true:
lots.class # => Array
lots[1].class # => Lot
The method called should be defined for Lot.

Create a ruby method that accepts a hash of parameters

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

Resources