carly.rb), and run it a bunch of times, or you can just paste it into irb and run just the last line a bunch of times.
me = ->{42}
class Proc
def maybe
rand(2)==0 ? self : ->{}
end
end
def call(it)
it.call
end
puts call me.maybe
Adventures of a dinosaur evolving to meet today's challenges.
MOST OLD CONTENT HAS MOVED; see new home at
https://www.codosaur.us/blog/
carly.rb), and run it a bunch of times, or you can just paste it into irb and run just the last line a bunch of times.
me = ->{42}
class Proc
def maybe
rand(2)==0 ? self : ->{}
end
end
def call(it)
it.call
end
puts call me.maybe
That probably makes most of you go WTF, and you probably figure the author meant "if status == 1 || status == 2 || status == 3" but somehow screwed up to the point of insanity, and rightly so. But bear with me, it's a fun little exploration of some bizarre Ruby usage.if status == status = 1 || status = 2 || status = 3
$ irb
:001 > x == x = 1
NameError: undefined local variable or method `x' for main:Object
Did you mean? x
from (irb):1 [...]
:002 > x
=> nil
:003 > x == x = 1
=> false
:004 > x
=> 1
:005 > x == x = 1
=> true
:006 >
At line 001, we get a NameError because we haven't told it about x. At 002, x now exists (but is nil), because referencing it brought it to Ruby's attention. At 003, there is no NameError, so it can get to the assignment, but the old value was nil, so the comparison is effectively "nil == 1". At 004, we can see that it did indeed get assigned 1. At 005, the values finally match.be" (or "equal", which
does exactly the same thing) produces distracting and usually meaningless extra
verbiage, while "eq" (or "eql", which is a little
different but close enough for this purpose) does not.
This will work fine, and so long as the status actually is 200, then all is right with the world.
expect(response.status).to be 200
All you really needed is this:
Failure/Error: expect(response.status).to be 200 expected #<Fixnum:401> => 200 got #<Fixnum:1001> => 500 Compared using equal?, which compares object identity, but expected and actual are not the same object. Use `expect(actual).to eq(expected)` if you don't care about object identity in this example.
(Okay, granted, you didn't really need to know it was compared using "
expected: 200 got: 500 (compared using ==)
==", but still, that's not too bad... at least it's a lot
less distracting verbiage to wade through than the prior message.)
==" instead of "be", especially if you're used to
RSpec's older "should" syntax rather than the new
"expect" syntax (which I'll ass-u-me you are now using)... but
then you'd see:
But despair not, dear reader, all is not lost! It's still very simple. Instead of "
Failure/Error: expect(response.status).to == 200 ArgumentError: The expect syntax does not support operator matchers, so you must pass a matcher to `#to`.
be", use "eq".
Then you get:
As you may recall from my Ruby Gotchas presentation, there are several ways to compare things in Ruby:
Failure/Error: expect(response.status).to eq 200 expected: 200 got: 500 (compared using ==)
==" compares the value, without regard for the actual
class(es) of the objects. So, frex, "1.0 == 1" is
true. This is what RSpec uses when you say
"eq" (or "==" in the older syntax); hence the
message "(compared using ==)"..eql?" compares the value and class! So,
"1.0.eql? 1" is false!.equal?" is object identity. That is, even if
two things have the same value, and the same class, if they're not the
exact same object, they are not ".equal?". One of the
oddities of the Ruby object system is that an object is created for each new
unique Float value, so the result of:
is indeed
x = 1.0 y = 1.0 x.equal? y
true! However, if we use a more complex class,
like String, we can see that "'foo'.equal? 'foo'"
is false.expect(response.status).to eql 200"? Pretty much the same
thing, just now it says "(compared using eql?)".
expect(response.status).to equal 200"... then you're back to
Square One. That shouldn't surprise any of you who paid close attention
to the original verbose error message: it told you that when we used
"be", it "Compared using equal?, which compares object
identity".
Thing with
names like:
- find_with_connector_and_part_type(conn_type,
part_type)
- find_with_connector_and_any_part_types(conn_type,
part_types)
- find_with_connector_and_all_part_types(conn_type,
part_types)
- find_with_connector_and_part_category(conn_type,
part_category)
- find_with_connector_and_any_part_categories(conn_type,
part_categories)
- find_with_connector_and_all_part_categories(conn_type,
part_categories)
- find_without_connector_and_part_type(conn_type,
part_type)
- find_without_connector_and_any_part_types(conn_type,
part_types)
- find_without_connector_and_all_part_types(conn_type,
part_types)
- find_without_connector_and_part_category(conn_type,
part_category)
- find_without_connector_and_any_part_categories(conn_type,
part_categories)
- find_without_connector_and_all_part_categories(conn_type,
part_categories)
I kept the code as DRY as I could, making the negatives simply call the
positives and invert the finding, and making the singles pass a single-element
set to the "any" case. That still left a lot of structural
duplication. Since they were class-methods used as scopes, they all
looked like this:
def self.find_with_connector_and_part_SOMETHING(conn_type,
SOMETHINGS)
id_query = assorted_magic_happens_here(several_lines)
where(id: id_query)
# negative version: where("id NOT IN (?)", id_query)
end
That got me thinking about how to combine this set of literally a
dozen different functions into one, something like
Thing.find_with_parts(part_options), where
part_options would include what Connector, what Types (and whether
we want all or just any, which would be the default), what
Categories (ditto), and whether to invert the finding. When the
client later said they didn't in fact want a bunch of separate methods,
I was ready, and had a lot of the idea already thought out.
def self.find_with_connector_and_part_type(conn_type,
part_type)
self.find_with_parts(connector_type: conn_type,
part_types: [part_type])
end
and reran its tests. Of course it failed, as I hadn't written
find_with_parts yet... but that came pretty easily, based on the
logic that had previously found Things having Parts of any of several Types,
used with the given Connector. That test quickly passed.
find_with_parts for the guts of a
specific method.find_with_parts! You always want to start with a
failing test!find_with_parts to make the test pass.find_with_parts.method_missing and
respond_to_missing? in sync.
class HookLyingSyncer
def initialize(object, matcher, &block)
@object = object
@matcher = matcher
@block = block
end
private
def respond_to_missing?(sym, include_all=false)
matches = find_matches(sym)
matches.any? ? true : @object.send(:respond_to?, sym, include_all)
end
def method_missing(sym, *args, &blk)
matches = find_matches(sym)
if matches.any?
@block.call(@object, matches, *args)
else
@object.send(sym, *args, &blk)
end
end
def find_matches(sym)
result = @matcher.call(sym)
result ? result : []
end
end
The tests contain some examples, with further usage explanation
in the README. Long story short, it can be used on
instances and classes, to add or override method definitions,
including overriding new so as to add methods to all new instances
of a class.