ChatGPT解决这个技术问题 Extra ChatGPT

Common Ruby Idioms

One thing I love about ruby is that mostly it is a very readable language (which is great for self-documenting code)

However, inspired by this question: Ruby Code explained and the description of how ||= works in ruby, I was thinking about the ruby idioms I don't use, as frankly, I don't fully grok them.

So my question is, similar to the example from the referenced question, what common, but not obvious, ruby idioms do I need to be aware of to be a truly proficient ruby programmer?

By the way, from the referenced question

a ||= b 

is equivalent to

if a == nil || a == false
  a = b
end

(Thanks to Ian Terrell for the correction)

Edit: It turns out this point is not totally uncontroversial. The correct expansion is in fact

(a || (a = (b))) 

See these links for why:

http://DABlog.RubyPAL.Com/2008/3/25/a-short-circuit-edge-case/

http://DABlog.RubyPAL.Com/2008/3/26/short-circuit-post-correction/

http://ProcNew.Com/ruby-short-circuit-edge-case-response.html

Thanks to Jörg W Mittag for pointing this out.

it's often more concisely stated as: foo ||= bar is equivalent to foo || foo = bar
I think it's more clear to say it's equivalent to foo = foo || bar, in the same way that foo *= bar is equivalent to foo = foo * bar.
Chuck - except that's not true. The difference is in cases like obj.foo ||= bar If it was obj.foo = obj.foo || bar then obj.foo= would be called even if obj.foo was falseish (and obj.foo= might have side effects). But that isn't how it works
Very readable? Compared to what? I find that Ruby lends itself well to stupid code tricks (35 ways to write a loop! blocks and lambdas and other things) but the idioms make it quite unreadable. However, since there are so many ways to do things, it's always easy to find a way (or several) to do something...
"grok" is also the name of a Python web framework. to remove confusion, I removed the tag from your question

M
Marcus Buffett

The magic if clause that lets the same file serve as a library or a script:

if __FILE__ == $0
  # this library may be run as a standalone script
end

Packing and unpacking arrays:

# put the first two words in a and b and the rest in arr
a,b,*arr = *%w{a dog was following me, but then he decided to chase bob}
# this holds for method definitions to
def catall(first, *rest)
  rest.map { |word| first + word }
end
catall( 'franken', 'stein', 'berry', 'sense' ) #=> [ 'frankenstein', 'frankenberry', 'frankensense' ]

The syntatical sugar for hashes as method arguments

this(:is => :the, :same => :as)
this({:is => :the, :same => :as})

Hash initializers:

# this
animals = Hash.new { [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {}
# is not the same as this
animals = Hash.new { |_animals, type| _animals[type] = [] }
animals[:dogs] << :Scooby
animals[:dogs] << :Scrappy
animals[:dogs] << :DynoMutt
animals[:squirrels] << :Rocket
animals[:squirrels] << :Secret
animals #=> {:squirrels=>[:Rocket, :Secret], :dogs=>[:Scooby, :Scrappy, :DynoMutt]}

metaclass syntax

x = Array.new
y = Array.new
class << x
  # this acts like a class definition, but only applies to x
  def custom_method
     :pow
  end
end
x.custom_method #=> :pow
y.custom_method # raises NoMethodError

class instance variables

class Ticket
  @remaining = 3
  def self.new
    if @remaining > 0
      @remaining -= 1
      super
    else
      "IOU"
    end
  end
end
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> Ticket
Ticket.new #=> "IOU"

Blocks, procs, and lambdas. Live and breathe them.

 # know how to pack them into an object
 block = lambda { |e| puts e }
 # unpack them for a method
 %w{ and then what? }.each(&block)
 # create them as needed
 %w{ I saw a ghost! }.each { |w| puts w.upcase }
 # and from the method side, how to call them
 def ok
   yield :ok
 end
 # or pack them into a block to give to someone else
 def ok_dokey_ok(&block)
    ok(&block)
    block[:dokey] # same as block.call(:dokey)
    ok(&block)
 end
 # know where the parentheses go when a method takes arguments and a block.
 %w{ a bunch of words }.inject(0) { |size,w| size + 1 } #=> 4
 pusher = lambda { |array, word| array.unshift(word) }
 %w{ eat more fish }.inject([], &pusher) #=> ['fish', 'more', 'eat' ]

+1 - Thanks, this is a great post. Can you expand for me on what the '&' represents in '&block' ?
The & means that block is a proc parameter.
it depends. & as the prefix to the final argument to a method CALL means to take the Proc object in that variable and unpack it into a block to pass to the method.
[continued] & as the prefix to the final argument to a method DEFINITION means pack the block passed to that method into an object, and assign it to that variable
Doesn't the the @remaining in the Ticket example need to be @@remaining so it is a class variable?
t
trondozer

This slideshow is quite complete on the main Ruby idioms, as in:

Swap two values: x, y = y, x

Parameters that, if not specified, take on some default value def somemethod(x, y=nil)

Batches up extraneous parameters into an array def substitute(re, str, *rest)

And so on...


C
Chubas

Some more idioms:

Use of the %w, %r and %( delimiters

%w{ An array of strings %}
%r{ ^http:// }
%{ I don't care if the string has 'single' or "double" strings }

Type comparison in case statements

def something(x)
  case x
    when Array
      # Do something with array
    when String
      # Do something with string
    else
      # You should really teach your objects how to 'quack', don't you?
  end
end

... and overall abuse of the === method in case statements

case x
  when 'something concrete' then ...
  when SomeClass then ...
  when /matches this/ then ...
  when (10...20) then ...
  when some_condition >= some_value then ...
  else ...
end

Something that should look natural to Rubyists, but maybe not so to people coming from other languages: the use of each in favor of for .. in

some_iterable_object.each{|item| ... }

In Ruby 1.9+, Rails, or by patching the Symbol#to_proc method, this is becoming an increasingly popular idiom:

strings.map(&:upcase)

Conditional method/constant definition

SOME_CONSTANT = "value" unless defined?(SOME_CONSTANT)

Query methods and destructive (bang) methods

def is_awesome?
  # Return some state of the object, usually a boolean
end

def make_awesome!
  # Modify the state of the object
end

Implicit splat parameters

[[1, 2], [3, 4], [5, 6]].each{ |first, second| puts "(#{first}, #{second})" }

According to David A. Black, "! does not mean that the method changes its receiver." - dablog.rubypal.com/2007/8/15/…
D
Daemin

I like this:

str = "Something evil this way comes!"
regexp = /(\w[aeiou])/

str[regexp, 1] # <- This

Which is (roughly) equivalent to:

str_match = str.match(regexp)
str_match[1] unless str_match.nil?

Or at least that's what I've used to replace such blocks.


u
ucron

I would suggest reading through the code of popular and well designed plugins or gems from people you admire and respect.

Some examples I've run into:

if params[:controller] == 'discussions' or params[:controller] == 'account'
  # do something here
end

corresponding to

if ['account', 'discussions'].include? params[:controller]
  # do something here
end

which later would be refactored to

if ALLOWED_CONTROLLERS.include? params[:controller]
  # do something here
end

Not really idiomatic ruby IMO. This has been widely used in PHP (CakePHP example): in_array(array('controller1', 'controller2'), $this->params['controller']))
prefer using '||' instead of 'or' in ruby, because at ruby these things will work different way - if you using '||' ruby interpreter will not go to second case if first case is 'true', but then you are using 'or' interpreter will always check second case
I
Ian Terrell

By the way, from the referenced question a ||= b is equivalent to if a == nil a = b end

That's subtly incorrect, and is a source of bugs in newcomers' Ruby applications.

Since both (and only) nil and false evaluate to a boolean false, a ||= b is actually (almost*) equivalent to:

if a == nil || a == false
  a = b
end

Or, to rewrite it with another Ruby idiom:

a = b unless a

(*Since every statement has a value, these are not technically equivalent to a ||= b. But if you're not relying on the value of the statement, you won't see a difference.)


I'm sorry, but your examples are wrong. This has been discussed numerous times on pretty much every mailinglist, newsgroup, forum and blog imaginable and the "best" answer (at least so far, until somebody finds yet another counterexample) is: a ||= b is equivalent to (a || (a = (b)))
No need to be sorry. My true point is that people forget that if you're working with booleans your false value can get overwritten. My examples aren't (in spirit) about rephrasing the idiom; they're about making it as expressive as possible so that people remember not just the nil case, but false.
What I'll never understand is why Ruby programmers seem so intent on using an idiom like this. Just write the code explicitly and save everyone else the trouble of deciphering all your silly edge cases.
M
Mike Woodhouse

Here's a few, culled from various sources:

use "unless" and "until" instead of "if not" and "while not". Try not to use "unless" when an "else" condition exists, though.

Remember you can assign multiple variables at once:

a,b,c = 1,2,3

and even swap variable without a temp:

a,b = b,a

Use trailing conditionals where appropriate, e.g.

do_something_interesting unless want_to_be_bored?

Be aware of a commonly-used but not instantly obvious (to me at least) way of defining class methods:

class Animal
  class<<self
    def class_method
      puts "call me using Animal.class_method"
    end
  end
end

Some references:

http://cbcg.net/talks/rubyidioms/index.html

http://www.therailsway.com/2006/12/8/idiomatic-ruby

http://www.therailsway.com/2007/1/21/more-idiomatic-ruby

http://www.caliban.org/ruby/rubyguide.shtml


t
tokland

I maintain a wiki page that covers some Ruby idioms and formatting:

https://github.com/tokland/tokland/wiki/RubyIdioms


This page returns a 404
D
Danny

I always forget the exact syntax of this shorthand if else statement (and the name of the operator. comments anyone?) I think it's widely used outside of ruby, but in case someone else wants the syntax here it is:

refactor < 3 ? puts("No need to refactor YET") : puts("You need to refactor this into a  method")

expands to

if refactor < 3
  puts("No need to refactor YET")
else
  puts("You need to refactor this into a  method")
end

update

called the ternary operator:

return myvar ? myvar.size : 0


Ö
Özgür

You can deepcopy with Marshaling object easily. - taken from The Ruby Programming Language

def deepcopy(o)
  Marshal.load(Marshal.dump(o))
end

Note that files and I/O streams, as well as Method and Binding objects, are too dynamic to be marshaled; there would be no reliable way to restore their state.


j
jakeonrails
a = (b && b.attribute) || "default"

is roughly:

if ( ! b.nil? && ! b == false) && ( ! b.attribute.nil? && ! b.attribute.false) a = b
else a = "default"

I use this when b is a record which may or may not have been found, and I need to get one of its attributes.


m
marcel massana

I like how If-then-elses or case-when could be shortened because they return a value:

if test>0
  result = "positive"
elsif test==0
  result = "zero"
else
  result = "negative"
end

could be rewriten

result = if test>0
  "positive"
elsif test==0
  "zero"
else
  "negative"
end

The same could be applied to case-when:

result = case test
when test>0 ; "positive"
when test==0 ; "zero"
else "negative"
end

result = (test>0)? "positive" : (test==0 ? "zero" : "negative")
In order to avoid the semi–colon you could also write when test > 0 then "positive". (@Salil I find nested ternary operators really hard to read.)
i
imtk

Nice question!

As I think the more intuitive & faster the code is, a better software we’re building. I will show you how I express my thoughts using Ruby in little snippets of code. Read more here

Map

We can use map method in different ways:

user_ids = users.map { |user| user.id }

Or:

user_ids = users.map(&:id)

Sample

We can use rand method:

[1, 2, 3][rand(3)]

Shuffle:

[1, 2, 3].shuffle.first

And the idiomatic, simple and easiest way... sample!

[1, 2, 3].sample

Double Pipe Equals / Memoization

As you said in the description, we can use memoization:

some_variable ||= 10
puts some_variable # => 10

some_variable ||= 99
puts some_variable # => 10

Static Method / Class Method

I like to use class methods, I feel it is a really idiomatic way to create & use classes:

GetSearchResult.call(params)

Simple. Beautiful. Intuitive. What happens in the background?

class GetSearchResult
  def self.call(params)
    new(params).call
  end

  def initialize(params)
    @params = params
  end

  def call
    # ... your code here ...
  end
end

For more info to write idiomatic Ruby code, read here


S
SztupY

Array.pack and String.unpack for working with binary files:

# extracts four binary sint32s to four Integers in an Array
data.unpack("iiii") 

e
edikgat

method missing magick

class Dummy  
  def method_missing(m, *args, &block)  
    "You just called method with name #{m} and arguments- #{args}"  
  end  
end

Dummy.new.anything(10, 20)
=> "You just called method with name anything and arguments- [10, 20]"

if you call methods that not exists in ruby objects, ruby interpreter will call method called 'method_missing' if its defined, you could user this for some tricks, like writing api wrappers, or dsl, where you don;t know all methods and parameters names


Please provide some explanation as well.