I'm running into an issue when trying to create an open struct with an attribute that has the same name as one of the OpenStruct instance methods. Specifically, i'd like to create an open struct that has an attribute capture. I'm using this as a stub in an rspec test, so i can't change the name of the method (it must be capture)
#=> OpenStruct.new(capture: true).capture
#=> ArgumentError: wrong number of arguments (0 for 1)
looking at the OpenStruct methods, it has a method capture and it is this method that is getting called. Is there a way to instantiate an open struct with an attribute of the same name as one of its methods?
for clarity, i specifically need the method capture, which i've confirmed breaks on rails 4.0.x but not rails 5, but this situation holds true for any method openstruct might have.
#=> OpenStruct.new(class: true).class
#=> OpenStruct
This works just fine for me in pry (running ruby 2.3, by the way)
[9] pry(main)> OpenStruct.new(capture: 1).capture
=> 1
Here's another way to do it:
[15] pry(main)> a = OpenStruct.new capture: 1
=> #<OpenStruct capture=1>
[22] pry(main)> a.singleton_class.class_exec { def capture; self[:capture] + 1; end }
=> :capture
[23] pry(main)> a.capture
=> 2
i don't know what testing library you're using, but if it's RSpec, you could use this mocking approach as well:
a = OpenStruct.new capture: 0
allow(a).to receive(:capture).and_return(a[:capture])
a.capture # => 0
Related
I'm trying to understand a mixin precedence situation. If I run foo.bar I'm trying to figure out exactly which bar will be executed. Here is a snipped irb session where I tried to find out where an array's assoc method is defined.
2.3.0 :032 > ray = ['cat', nil, 'dog']
=> ["cat", nil, "dog"]
2.3.0 :033 > ray.methods
=> [:fill, :assoc, :rassoc, :uniq, ...
2.3.0 :034 > ray.method("assoc").class_or_module_name
NoMethodError: undefined method `class_or_module_name' for #<Method: Array#assoc>
from (irb):34
from /Users/mark/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
2.3.0 :035 > ray.methods("assoc").class_or_module_name
NoMethodError: undefined method `class_or_module_name' for #<Array:0x007ffb8aa286a8>
from (irb):35
from /Users/mark/.rvm/rubies/ruby-2.3.0/bin/irb:11:in `<main>'
2.3.0 :036 > ray.methods("assoc")
=> [:fill, :assoc, :rassoc, :uniq, :uniq!, :compact, ...
If possible I would like to see the methods and their source location that lost precedence.
To find out which class or module defines a method (rather than what file), use Method#owner.
For example,
[].method(:last).owner #=> Array
[].method(:flat_map).owner #=> Enumerable
You'll actually find that a lot of the methods that Array could get from Enumerable, it actually defines itself (presumably to provide a more efficient implementation):
irb(main):018:0> Enumerable.instance_methods.select {|meth_name| [].method(meth_name).owner == Array }
=> [:to_a, :to_h, :sort, :count, :find_index, :select, :reject, :collect, :map, :first, :include?, :reverse_each, :zip, :take, :take_while, :drop, :drop_while, :cycle]
In general, to know where a method is defined you can use source_location(docs), e.g.
ray.method("assoc").source_location
although in this case it will return nil since assoc is a native method. It will work for other methods, e.g.
ray.methods.map{ |m| [m, ray.method(m).source_location] }
For native methods you can use Pry, e.g.
pry(main)> ? ray.assoc
From: array.c (C Method):
Owner: Array
Visibility: public
[...]
? is a shorthand for show-doc.
The Background:
I'm trying to use cucumber to do some test-driven (or behavior-driven) development around an interface to AWS, in ruby.
So, I have a step definition that looks like this:
Then(/^the mock object should have had :(.*?) called, setting "(.*?)" to "(.*?)"$/) do |method, param, value|
expect(#mock).to receive(method.to_sym).with(hash_including(param, value))
end
Where #mock was previously set using:
#mock = instance_double(AWS::AutoScaling::Client)
And where I invoke this step definition with a feature line like:
And the mock object should have had :update_auto_scaling_group called, setting "auto_scaling_group_name" to "Some-test-value"
When that step gets run, it gets the following error (leaving out the full error, as I believe this is the most relevant part):
AWS::AutoScaling::Client does not implement: update_auto_scaling_group (RSpec::Mocks::MockExpectationError)
I see that indeed, the checks that RSpec runs (as traced back from where the RSpec::Mocks::MockExpectationError gets thrown) are at least correctly reporting the information that they get from the class:
[1] pry(main)> require 'aws-sdk'
=> true
[2] pry(main)> klass = AWS::AutoScaling::Client
=> AWS::AutoScaling::Client
[3] pry(main)> klass.public_method_defined? "update_auto_scaling_group"
=> false
[4] pry(main)> klass.private_method_defined? "update_auto_scaling_group"
=> false
[5] pry(main)> klass.protected_method_defined? "update_auto_scaling_group"
=> false
And yet, if we ask an actual instance, it lets us know that this is a method it would respond to:
[6] pry(main)> x = klass.new
=> #<AWS::AutoScaling::Client::V20110101>
[7] pry(main)> x.respond_to? "update_auto_scaling_group"
=> true
Even while it doesn't say that about just anything:
[8] pry(main)> x.respond_to? "bogus"
=> false
First questions:
So... is this a bug in the AWS::AutoScaling::Client code (or really, probably here), for not defining the methods in a way that the extant checks ({public,private,protected}_method_defined?) would come back true?
Or perhaps a bug in RSpec's "doubles", for not doing all the checking it could do to try to find out that this is indeed a method that's callable in an instance of that class?
Or perhaps it's simply something that I'm doing wrong here? Other?
More generally:
How can I write tests for the code I'm writing, to ensure that it's making calls to what will be an AWS::AutoScaling::Client instance, with the correct parameters (as defined in several checks that I have)? Are there alternate ways I can write my step definitions that would make this work? Alternative ways to create my mock objects? Other?
I've found a way to dynamically mix in the methods I needed to mock
You could do this with empty methods and then stub them, or just include the stubs in the mixin
require 'rails_helper'
RSpec.describe "users/sessions/new.html.erb", :type => :view do
it "displays login form" do
module DeviseUserBits
def resource
#_DeviseUserBitsUser ||= User.new
end
def resource_name
:user
end
def devise_mapping
Devise.mappings[:user]
end
end
view.class.include DeviseUserBits
render
expect(rendered).to match /form/
end
end
It just adds methods on/after instantiating. It's pretty legal, all ruby classes/objects are open.
Proper answer - you do not want to test what you are trying to test in duck-typed language with open classes and objects. It just does not make sense.
The version 1 AWS SDK for Ruby uses #method_missing as a delegate for building and sending requests. The methods a client responds to are defined in an API definition. This eliminates boiler-plate code, but causes problems if you are trying to reflect the available methods at runtime.
Option A: Use a regular double and apply your assertions on the test double.
Option B: Use the mocking feature of the SDK via AWS.stub! When stubbing is enabled, all clients constructed will respond to their regular methods, but will return dummy responses (empty hashes and arrays). This approach provides the useful ability to specify the data to return from a stub. You can even create a stub response for the express purpose of returning from an assertion.
Going with Option B:
# use `:stub_requests` or call Aws.stub!
as = AWS::AutoScaling::Client.new(:stub_requests: true)
# validates parameters as normal, but returns empty response data
as.update_auto_scaling_group(auto_scaling_group_name: 'name')
#=> {}
# You can access the stub response for any operation by name:
stub = as.stub_for(:describe_auto_scaling_groups)
stub.data[:auto_scaling_group_names] = ["Group1", "Group2"]
# Now calling that operation will return the stubbed data
resp = as.describe_auto_scaling_groups
resp.auto_scaling_group_names
#=> ['Group1', 'Group2']
If you need to assert a method is called against the client, you can do so normally, returning the stubbed response:
expect(#client).to receive(:describe_auto_scaling_groups).
with(hash_including(param, value)).
and_return(#client.stub_for(:describe_auto_scaling_groups))
Can I create something in my model to do something like:
MyModel::TYPE::ONE
MyModel::TYPE::TWO
where ONE and TWO are strings? I placed them in a constant in my model like:
class MyModel
TYPE = ['ONE', 'TWO']
end
so I can access MyModel::Type and get the array, but how do I get one more level?
You can get the syntax you desire with:
[~]$ irb
irb(main):001:0> module MyModel
irb(main):002:1> module TYPE
irb(main):003:2> ONE = 1
irb(main):004:2> TWO = 2
irb(main):005:2> end
irb(main):006:1> end
=> 2
irb(main):007:0> MyModel::TYPE::ONE
=> 1
irb(main):008:0> MyModel::TYPE::TWO
=> 2
This has the disadvantage, or maybe the advantage, of allowing extra "attributes" on the enum, not unlike what Java gives you. You can make the values of ONE and TWO be maps if you like, which is similar to Java's enum objects.
EDIT: You can also get the values like this:
irb(main):009:0> MyModel::TYPE::constants
=> [:ONE, :TWO]
I am willing to call a method when trying to display an object, but I don't find which method is used, for example:
[41] pry(main)> u
=> {"id"=>3}
[42] pry(main)> u.inspect
=> "#<User id=3>"
[43] pry(main)> u.to_s
=> "#<User id=3>"
[44] pry(main)> puts c
#<User id=3>
=> nil
I would like to know which method is called for the first case.
User is not an ActiveRecord class, it inherits from Hashie.
Thanks for your help!
It's probably the rails method attributes - though it is a feature of Pry, not of the standard rails console which would give you ruby-1.9.2-p290 :047 > u
=> #<u:0x8a2f6cc #id=3>
I am using Ruby on Rails 3.0.7 and I would like to retrieve the class name, also if it is namespaced. For example, if I have a class named User::Profile::Manager I would retrieve the Manager string from that using some unknown to me Ruby or Ruby on Rails method and in a secure way.
BTW: What other "usefull" information that are "commonly" used can I get for the class?
Some useful simple metaprogramming calls:
user = User::Profile::Manager.new(some_params)
user.class # => User::Profile::Manager
user.class.class # => Class
user.class.name # => "User::Profile::Manager"
user.class.name.class # => String
# respond_to? lets you know if you can call a method on an object or if the method you specify is undefined
user.respond_to?(:class) # => true
user.respond_to?(:authenticate!) # => Might be true depending on your authentication solution
user.respond_to?(:herpderp) # => false (unless you're the best programmer ever)
# class.ancestors is an array of the class names of the inheritance chain for an object
# In rails 3.1 it yields this for strings:
"string".class.ancestors.each{|anc| puts anc}
String
JSON::Ext::Generator::GeneratorMethods::String
Comparable
Object
PP::ObjectMixin
JSON::Ext::Generator::GeneratorMethods::Object
ActiveSupport::Dependencies::Loadable
Kernel
BasicObject
If you want the lowest-level class from User::Profile::Manager I'd probably do the following [using a regex for this seems like overkill to me ;)]:
user = User::Profile::Manager.new
class_as_string = user.class.name.split('::').last # => "Manager"
class_as_class = class_name.constantize # => Manager
Edit:
If you actually want to look through some more metaprogramming calls, check the docs for the Object and Module classes, and check out the google results for "Ruby Metaprogramming".
Have you tried class method:
class A
class B
end
end
myobject = A::B.new
myobject.class
=> A::B
To expand on #JCorcuera's answer, some other useful information can be found with kind_of? and methods
class A
class B
def foo
end
end
end
myobject = A::B.new
p myobject.class
=> A::B
p myobject.kind_of? A::B
=> true
p myobject.methods
=> [:foo, :nil?, :===, :=~, ...
p myobject.methods.include? :foo
=> true