Apparently Object.to_a will eventually be obsolete (not sure when). I was using it in the following context:
def foo(args) args.to_a.join(',') end
According to this very old ruby-lang thread, this [*args] is a suitable alternative:
def foo(args) [*args].join(',') end
Ruby’s Object#send allows dynamic calling of a method. This is very useful, but what if we wanted to call several levels deep on an object? For instance:
# Normal call chain post.comments.first.commented_at # Dynamically with send? Have to call three times. post.send(:comments).send(:first).send(:commented_at)
What if the number of calls to send is variable depending on what we’re trying to show? In one case we might need post.posted_at for the date, and in another case we might need post.comments.first.commented_at for the date.
How could we dynamically craft the definition of the methods to send if we don’t know how many calls to Object#send we’ll have? We need a way to define an arbitrary number of method calls.
Behold, a recursive send: Object#rsend
class Object def rsend(*args, &block) obj = self args.each do |a| b = (a.is_a?(Array) && a.last.is_a?(Proc) ? a.pop : block) obj = obj.__send__(*a, &b) end obj end alias_method :__rsend__, :rsend end
Each argument passed to Object#rsend is an array with the symbols and arguments that will be passed on to Object#send:
post.rsend([:comments],[:first],[:commented_at])
If there are no arguments to be passed on to send, the array brackets can be omitted:
post.rsend(:comments, :first, :commented_at)
Of course, in practice you’ll probably be defining your method call chain in one part of your code, putting it in a variable, and sending it to rsend with a splat*:
the_date = [:comments, :first, :commented_at] #...somewhere else in your code you've passed the_date along: post.rsend(*the_date)
With arguments:
a = [0,1,2,3,4,5,6,7,8,9] a.rsend([:slice, 2, 8]) #=> [2, 3, 4, 5, 6, 7, 8, 9] a.rsend([:slice, 2, 8], [:slice, 1, 3]) #=> [3, 4, 5]
Object#send accepts a block. What about blocks? Pass in a proc:
a.rsend([:map, (proc { |x| x*2 })]) #=> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18] a.rsend([:map, (proc { |x| x*2 })], [:select, (proc { |x| x % 4 == 0})]) #=> [0, 4, 8, 12, 16]
And, in an effort to make Object#rsend behave like Object#send for the simple case, you can send a regular block:
a.rsend(:map) { |x| x*2 } #=> [0, 2, 4, 6, 8, 10, 12, 14, 16, 18]
Caveat: For the case needing parameters, Object#rsend does require an array, so:
a.rsend(:slice, 2, 8) # wrong, does not work like Object#send a.rsend([:slice, 2, 8]) # right
A quirk that I’ve left in for fun, but it might (and maybe should) change: If providing a single block, that block will be called on every call unless you’ve already passed in a proc:
a.rsend(:map, :map) { |x| x*2 } #=> [0, 4, 8, 12, 16, 20, 24, 28, 32, 36] a.rsend(:map, [:map, (proc { |x| x+5 })], :map) { |x| x*2 } #=> [10, 14, 18, 22, 26, 30, 34, 38, 42, 46] #outer block was called on first and third :map
Can anyone come up with a good use for this call-the-block-each-time behavior?
Has anyone done this already? I searched for such a thing and came up empty. Maybe this method should be called something else? I named it based on each call recursing down the chain of methods with a new object being returned for the next method to be sent to.
Suggestions and comments are welcome.
Ruby on Rails and Ruby are an amazing combo. Here’s another example of why.
ActionView’s TextHelper methods are useful, but I often need to use them in my controller or my model. For several of the TextHelper methods that expect a string as input, it makes sense to extend the String class.
So, if I want to strip HTML tags, auto link any URLs, and then simple format a comment (in that order) before I save it in the database I can do:
class Comment < ActiveRecord::Base #... def before_save self.text = self.text.strip_tags.auto_link.simple_format end end
This method is much cleaner than including ActionView::Helpers::TextHelper in whatever class I’m in and passing the string as an argument to each method.
Below is the magic code. Since TextHelper is a module, we create a Singleton class to reference the methods, create the wrapper methods in their own module, and finally include that module in the String class. Note that not all TextHelper methods are included–just the ones that make sense. Drop this code into a file and require it in your environment or within a plugin.
# ActionView Text Helpers are great! # Let's extend the String class to allow us to call # some of these methods directly on a String. # Note: # - cycle-related methods are not included # - concat is not included # - pluralize is not included because it is in # ActiveSupport String extensions already # (though they differ). # - markdown requires BlueCloth # - textilize methods require RedCloth # Example: # "<b>coolness</b>".strip_tags -> "coolness" require 'singleton' # Singleton to be called in wrapper module class TextHelperSingleton include Singleton include ActionView::Helpers::TextHelper include ActionView::Helpers::TagHelper #tag_options needed by auto_link end # Wrapper module module MyExtensions #:nodoc: module CoreExtensions #:nodoc: module String #:nodoc: module TextHelper def auto_link(link = :all, href_options = {}, &block) TextHelperSingleton.instance.auto_link(self, link, href_options, &block) end def excerpt(phrase, radius = 100, excerpt_string = "...") TextHelperSingleton.instance.excerpt(self, phrase, radius, excerpt_string) end def highlight(phrase, highlighter = '<strong class="highlight">\1</strong>') TextHelperSingleton.instance.highlight(self, phrase, highlighter) end begin require_library_or_gem 'bluecloth' def markdown TextHelperSingleton.instance.markdown(self) end rescue LoadError # do nothing. method will be undefined end def sanitize TextHelperSingleton.instance.sanitize(self) end def simple_format TextHelperSingleton.instance.simple_format(self) end def strip_tags TextHelperSingleton.instance.strip_tags(self) end begin require_library_or_gem 'redcloth' def textilize TextHelperSingleton.instance.textilize(self) end def textilize_without_paragraph TextHelperSingleton.instance.textilize_without_paragraph(self) end rescue LoadError # do nothing. methods will be undefined end def truncate(length = 30, truncate_string = "...") TextHelperSingleton.instance.truncate(self, length, truncate_string) end def word_wrap(line_width = 80) TextHelperSingleton.instance.word_wrap(self, line_width) end end end end end # extend String with the TextHelper functions class String #:nodoc: include MyExtensions::CoreExtensions::String::TextHelper end
This idea and code was somewhat inspired by Gabriel’s post on using helpers inside a controller. Thanks Gabriel!
I’ve been using PHP about 8 years now for web development. I remember when I first discovered PHP. I had been writing CGI applications in C/C++ at the time, and PHP was a breath of fresh air. It was so powerful, yet so simple. I can’t describe it exactly, but there was a good feeling about PHP.
Fast forward to present day where I’ve been learning and using Ruby, and the feeling is very similar. For me, Ruby is the next breath of fresh air in programming languages. It just feels right.
Obviously, other programmers have different opinions, and that’s okay with me. I’m not here to win your heart for Ruby. Other languages may create the right feel for you. If python makes you happy, then I say “Congratulations, on finding a language that you love!”
Well, it’s been almost a month now since the baby came. We’re doing well, but very tired. Adding to my things-to-read-when-I’m-not-so-tired list:
Ruby Code & Style – a new online magazine from Artima about the Ruby language.