Skip to main content

Mobomo webinars-now on demand! | learn more.

There’s been a lot of talk about method_missing lately. Let’s do a little example that leverages this freaky but neat little method in our quest to bend Rails to our will. The following code is a handy trick that lets you access key values in a Hash as regular class methods.

class Hash   def method_missing(method, *params)     method = method.to_sym     return self[method] if self.keys.collect(&:to_sym).include?(method)     super   end end

If you have a key called :name, then my_hash[:name] and my_hash.name will now give the same result. If you call some key that doesn’t exist, then the super call tells Object to throw a generic NoMethodError. Neat, no?

P.S. the collect(&:to_sym) shortcut used above actually evaluates to collect{|x| x.to_sym} in Rails. This functionality is not available in Ruby, but it can easily be replicated by overriding the Symbol class, like so:

class Symbol   def to_proc(*args)     Proc.new { |*args| args.shift.__send__(self, *args) }   end end

Bending Ruby will be a series of posts that will build on one another.

Categories
Author

So you want to clear your live search fields the moment someone clicks on a result. Here’s a little bit of code that will let you do so in your RJS template:

page.replace_html 'search_results', '' page[:live_search_bar].value = '' </filter:jsode>  However, suppose you want to use this same live search clearing scheme over multiple controllers. You can either do re-use the above code for each respective controller method, or you can write the following in your RJS views, thereby DRY-ing up your code:  <pre name="code" class="ruby"> page.clear_my_live_search

How, oh how ever do I implement this for my app? Well, this works because in Ruby, it’s quite easy to add or override methods to any class or module. So let’s hack into ActionView, which is the part of Rails that gives you all those neat-o functions in your views (for example: the much-loved link_to).

The trick to overriding Rails code is to always have a copy of Edge Rails checked out to refer to, so you can get the namespace right. For this example, we have established that we want to extend the page.* methods that are prevalent through RJS templates and “render :update” calls. First we need to find out where the code lives. Let’s run a text search on our edge rails copy for ‘insert_html’, which is a commonly used RJS method. We then find out that it’s at action_pack/lib/action_view/helpers/prototype_helper.rb. Well, this is a start!

Next, we look at the namespace of the code in prototype_helper.rb_, and try to find inserthtml. We then see that it’s nested like so:

module ActionView   module Helpers     module PrototypeHelper       class JavascriptGenerator         module GeneratorMethods            def insert_html               # .... code ...            end          end       end    end end

Now, in lib/actionview_hacks.rb, copy this namespace and replace the insert_html method with a method of your choice:

module ActionView   module Helpers     module PrototypeHelper       class JavascriptGenerator         module GeneratorMethods                        def clear_my_live_search               page.replace_html 'search_results', ''               page[:live_search_bar].value = ''            end           end       end    end end

Next, update your environment.rb file by adding this somewhere:

require ‘actionview_hacks’

That’s it! You can now call page.clear_my_live_search from any RJS template or render :update method. You can also use update_page_tag to insert the page. methods we just generated into your views as javascript.

Also, this example is quite basic. Some might call it overkill to override Rails helpers to get this running. Another good way of doing the same is to define a new method in one of your helpers that uses update_page, like so:

module LiveSearchHelper   def clear_my_live_search     update_page do |page|       page.replace_html 'search_results', ''       page[:live_search_bar].value = ''     end   end end

You can then use this helper all over the place, too.

This article barely touches the surface of what’s possible when extending Rails — most existing Rails plugins probably got their start by practically the same process as what we followed here.

Categories
Author

I know that most Rails developers have come across the problem of notifying people by email that they have an internal message on your app’s own messaging system. Usually this is easy enough, and elegantly done in your model by using

class InternalMessage < ActiveRecord::Base   after_save :notify_person_of_new_internal_message      protected   def notify_person_of_new_internal_message      MyMailer.deliver_new_posting(self.from_person, self.message)   end end 

However, let’s consider a situation where you need duplication of data on two different processes. For example, if you would like to generate a notification message when some posts to a common forum. The quick way to do this would be by calling two separate methods in your controller, like so:

subject = "Ted Stevens has posted a new topic" body = "From Ted Stevens: " +         "'I hope you get this, the tubes have been acting up recently!'" @person.save_to_inbox(subject, body) MyMailer.deliver_new_posting(@person, subject, body) 

This requires you to generate the subject and the body of the message and use the methods to work out the logic.

However, with a little ActionMailer hackery, you can make this scheme much cleaner.

MyMailer.notify(person, :new_posting, from_person, note) 

In your model,

class MyMailer < ActionMailer::Base   class << self      def notify(person, method, *params)         new_mailer = new         new_mailer.create!(method, *params)         mail = new_mailer.mail         person.save_to_inbox(mail.subject, mail.body)         deliver(mail) if person.wants_email_notification?      end   end    def new_posting(from_person, note)      from from_person.email      subject "#{from_person.name} has posted a new topic"      @body[:from] = from_person      @body[:note] = note   end end 

Next, in views/my_mailer/new_posting.rhtml

From <%= @from_person %>: <%= @note %> 

The following three lines generate a new MyMailer object, create the email specified by the method (in this case, :notify_posting) using ActionMailer’s create! method (which in turn uses Ruby’s glorious send method), and pull out the TMail object that ActionMailer sends to the SMTP server. Conveniently, the TMail object lets you extract out the subject and body, which lets you map them to your InternalMessage class.

new_mailer = new new_mailer.create!(method, *params) mail = new_mailer.mail 

The above code generates the email, pulls out the generated subject and the body and uses it as the subject and body of the internal message. The idea is that the content of both the email notification and the internal email match up. This method does not require you to setup your subject and body. Instead, this method lets you use ActionMailer as a rendering framework for generating text/html for all of your internal messages. DRY is Good!

Categories
Author
1
Subscribe to Hacks