I need to check whether a object respond to an arbitrary number of methods.
And i'm tired of doing this:
if a.respond_to?(:foo) && a.respond_to?(:bar) && a.respond_to?(:blah)
What would be a more "correct" DRY way to do this?
You can always wrap it in a helper method:
def has_methods?(obj, *methods)
methods.all?{|method| obj.respond_to? method}
end
Try this if you have nothing against monkeypatching:
class Object
def respond_to_all? *meths
meths.all? { |m| self.respond_to?(m) }
end
def respond_to_any? *meths
meths.any? { |m| self.respond_to?(m) }
end
end
p 'a'.respond_to_all? :upcase, :downcase, :capitalize
#=> true
p 'a'.respond_to_all? :upcase, :downcase, :blah
#=> false
p 'a'.respond_to_any? :upcase, :downcase, :blah
#=> true
p 'a'.respond_to_any? :upcaze, :downcaze, :blah
#=> false
UPDATE: using meths.all? and meths.any?. #MarkThomas, thanks for refreshing my mind.
UPDATE: fixing responsd typo.
Check the Active Support extension from Rails.
It has method try. It's hard to say how you can use this method because of lack of context, maybe something like this:
if a.try(:foo) && a.try(:bar) && a.try(:blah)
In order to use this method you should
require 'active_support/core_ext/object/try.rb'
Also check my version of this method the tryit:
The 'correct' way (or one of many, anyway), is Tell Don't Ask, meaning that if you're sending the object a message, you expect it to respond without complaining. This is also known as Duck Typing (if it can quack, it's a duck).
I can't give you any specific advice, because you haven't asked a specific question. If you're testing for three different methods it seems like you don't know what kind of object a is, which can be an interesting case to deal with. Post more code!
Related
This simple method on a class just run the status method using the safe navigation operator.
def current_status
account&.status
end
But reek report this warning:
MyClass#current_status performs a nil-check [https://github.com/troessner/reek/blob/master/docs/Nil-Check.md]
How can I properly write methods like this to avoid Nil Check?
I've also verified this post from thoughtbot but it seem like "too much" for just a safe navigation operator.
Ruby 2.3.1
The advice from "Example 4" in the linked post is verbose but pretty good :
class MyClass
def initialize(with_account = nil)
#account = Account.new if with_account
end
def current_status
account.status
end
def account
#account || NilAccount.new
end
end
class Account
def status
"Up!"
end
end
class NilAccount
def status
"Down!"
end
end
puts MyClass.new(:with_account).current_status
#=> "Up!"
puts MyClass.new.current_status
#=> "Down!"
If it's "too much" for you, account&.status might be just fine.
Whatever you do : you'll need to test your code as much as possible!
well, tell-dont-ask looks pretty good, but Example 4 looks like an overkill to resolve this specific case.
#andredurao I think, we can use this workaround to pass checks, for some reason reek is fine with it:
def current_status
return unless account
account.status
end
I have seen a lot of examples of Ruby classes that utilize the Hash method of delete and I am not sure what the advantage of using it would be.
Example:
class Example
def initialize(default_params = {})
#foo = default_params.delete(:bar)
end
end
Any insight would be extremely helpful! Thanks!
Hash#delete is useful in the following situation:
def method(options)
if options.delete(:condition)
# Do something if options[:condition] is true
else
# Otherwise do something else
end
# Now options doesn't have the :conditions key-value pair.
another_method_that_doesnt_use_the_condition(options)
end
I'm unsure if the specific example you pulled should be using Hash#delete.
How can I do something like:
it { should have_constant(:FIXED_LIST) }
In my model (active record) I have FIXED_LIST = 'A String'
It's not a db attribute or a method and I haven't been able to use responds_to or has_attribute to test for it (they fail). What can I use the to check for it. - btw I have the shoulda-matchers installed.
Based on David Chelimsky's answer I've got this to work by slightly modifying his code.
In a file spec/support/utilities.rb (or some other in spec/support) you can put:
RSpec::Matchers.define :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
Note the use of "RSpec::Matchers.define" in stead of "matchers"
This allows to test for constants in your specs, like:
it "should have a fixed list constant" do
YourModel.should have_constant(:FIXED_LIST)
end
Note the use of "have_constant" in stead of "have_const"
It reads a little silly, but:
describe MyClass do
it { should be_const_defined(:VERSION) }
end
The reason is that Rspec has "magic" matchers for methods starting with be_ and have_. For example, it { should have_green_pants } would assert that the has_green_pants? method on the subject returns true.
In the same fashion, an example such as it { should be_happy } would assert that the happy? method on the subject returns true.
So, the example it { should be_const_defined(:VERSION) } asserts that const_defined?(:VERSION) returns true.
If you want to say have_constant you can define a custom matcher for it:
matcher :have_constant do |const|
match do |owner|
owner.const_defined?(const)
end
end
MyClass.should have_const(:CONST)
If you're trying to use the one-liner syntax, you'll need to make sure the subject is a class (not an instance) or check for it in the matcher:
matcher :have_constant do |const|
match do |owner|
(owner.is_a?(Class) ? owner : owner.class).const_defined?(const)
end
end
See http://rubydoc.info/gems/rspec-expectations/RSpec/Matchers for more info on custom matchers.
HTH,
David
Another option to simply make sure the constant is defined – not worrying about what it's defined with:
it 'has a WHATEVER constant' do
expect(SomeClass::WHATEVER).not_to be_nil
end
A warning to anyone trying to test that constants are defined: If your code references an undefined constant while defining a class, then your specs will crash before they get to your test.
This can lead you to believe that
expect { FOO }.to_not raise_error
is failing to catch the NameError, because you'll get a big stack trace, instead of a nice "expected not to raise error, but raised NameError."
Amidst the huge stack trace, it can be difficult to notice that your test is actually crashing on line 1: requre "spec/spec_helper" because your entire application is failing to load before it gets to your actual test.
This can happen if you have dynamically defined constants, such as is done by ActiveHash::Enum, and you then use them in the definition of another constant. Don't bother testing that they exist, every spec in your app will crash if one of them fails to be defined.
You could use
defined? YOUR_MODEL::FIXED_LIST
In RSpec 2, I was able to get this to work in one line as follows:
it { subject.class.should be_const_defined(:MY_CONST) }
That is, check against the class, instead of the instance.
In My model
class Role < ActiveRecord::Base
ROLE_ADMIN = "Administrador"
end
In My rspec
RSpec.describe Role, type: :model do
let(:fake_class) { Class.new }
describe "set constants" do
before { stub_const("#{described_class}", fake_class) }
it { expect(described_class::ROLE_ADMIN).to eq("Administrador") }
end
end
For ruby 2.1.5 and rspec 3.5.0 I am able to test that constant SEARCH_CHARS_TO_IGNORE is defined in the class DiffAlertsDatatable as follows:
expect(DiffAlertsDatatable.const_defined?(:SEARCH_CHARS_TO_IGNORE)).to eq(true)
I am calling an API from a Rails model and I would like to raise an error if the API returns on non-200 code. Otherwise I want to cache/lazy-load the data. This is my method:
def data
#data ||= SNL.get_list(name)
raise StandardError, #data.inspect unless #data.success?
#data
end
This works but I was wondering if I can accomplish this in one line. I tried using the and operator combined with an unless but couldn't get it to work.
Update: I have accepted tokland's answer because I asked for one line and he/she provided two very good solutions. In the end I am actually going to use
def data
#data ||= SNL.get_list(name)
#data.success? ? #data : (raise StandardError, #data.inspect)
end
for readability. I just hated having a third line just to return #data, since an exception will rarely be raised. I feel odiszapc's answer is a the best compromise of brevity and readability. Thanks everyone.
I wouldn't strain to write a one-liner, but you can use tap if you absolutely must:
def data
(#data ||= SNL.get_list(name)).tap { |d| d.success? or raise StandardError.new(d.inspect) }
end
Also with short-circuited logic:
def data
(#data ||= SNL.get_list(name)).success? && #data or
raise StandardError.new(#data.inspect) }
end
You can just use a terneray operator. However, I think its really important to keep your code as readable as possible. And generally in my experience, code that spreads a bit too much horizontally is generally a bit tough to follow.
There is one thing you need to be sure of. If SNL.get_list(name) returns nil and you're trying to use the and operator along with it, it won't work.
This issue has happened with me numerous times. A sample example:
nil and puts 'hello'
try this in your irb. It won't work. This issue has occurred with me numerous times.
Maybe
def data
#data ||= SNL.get_list(name)
#data.success? ? #data : (raise StandardError, #data.inspect)
end
Or I'm not sure, something like:
def data
(#data ||= SNL.get_list(name)).success? ? #data : (raise StandardError, #data.inspect)
end
try
def data
(#data ||= SNL.get_list(name)).success? ? #data : raise(StandardError, #data.inspect)
end
Again #Sohaib's point is valid this is not quite readable! and not a rubbish's way, lot of parentheses
I want to eliminate "=" sign for a particular reason. It might looks like this:
cat_that_has_name("Kelly").as(:kelly)
kelly.do_something
The "as" method here is used to generate a method "kelly" that reference my cat. Could anyone help me with this?
Any suggestions will be appreciated.
Update:
Jorg was right, I've add a simple test to demonstrate my intention:
require "test/unit"
class AsTest < Test::Unit::TestCase
def setup
#cats = ["Kelly", "Tommy"]
end
def teardown
end
def test_as
kelly1 = get_cat("Kelly")
get_cat("Kelly").as(:kelly2)
assert_equal(kelly1.object_id, kelly2.object_id)
end
private
def get_cat(name)
#cats.each do |cat|
if cat.to_s==name
return cat
end
end
return nil
end
end
It's kind of hard to figure out what you actually want. If you want some sensible answers, you will have to provide a complete code example of what you want to achieve (for example, the code you posted is missing definitions for the cat_that_has_name and so_something methods). You will also need to post a complete specification of what exactly you expect the as method to do, with usage examples and ideally also with a testsuite. After all, how do we know if our answer is correct if you haven't defined what "correct" means?
The best I could decipher from your cryptic question is something like this:
class Object
def as(name)
s = self
Object.send(:define_method, name) { s }
Object.send(:private, name)
end
end
But there is no way of knowing whether this works, because if I try to run your code example, I get a NoMethodError for cat_that_has_name and another NoMethodError for so_something.
Note also that your question is self-inconsistent: in your subject line you ask about a method to replace = (i.e. creating variables) but in your question you talk about creating methods, which would mean that you are looking for a replacement for def and not for =. Again, it would be much easier to answer correctly if there were a testsuite.