#33
May 18, 2007

Making a Plugin

You can sometimes remove a lot of duplication by generating methods dynamic. In this episode I will show you how to create a plugin which does exactly that.
Download (22.2 MB, 7:55)
alternative download for iPod & Apple TV (12.3 MB, 7:55)
# models/task.rb
stringify_time :due_at

def validate
  errors.add(:due_at, "is invalid") if due_at_invalid?
end

# stringify_time.rb
module StringifyTime
  def stringify_time(*names)
    names.each do |name|
      define_method "#{name}_string" do
        read_attribute(name).to_s(:db) unless read_attribute(name).nil?
      end
      
      define_method "#{name}_string=" do |time_str|
        begin
          write_attribute(name, Time.parse(time_str))
        rescue ArgumentError
          instance_variable_set("@#{name}_invalid", true)
        end
      end
      
      define_method "#{name}_invalid?" do
        instance_variable_get("@#{name}_invalid")
      end
    end
  end
end

# init.rb
class ActiveRecord::Base
  extend StringifyTime
end

RSS Feed for Episode Comments 31 comments

1. Ryan Bates May 17, 2007 at 19:55

Just heard (at RailsConf up here) that the "require" statement isn't necessary in the init.rb file, so you can probably remove it.


2. Phuwanart Larpmark May 18, 2007 at 02:05

I like this episode and hope you make screencast about plugin because it hard for me.


3. chineseGuy May 18, 2007 at 02:39

Module#define_method and Object#instance_variable_set are advanced for me, your screencast show a good example how to use them,now I think I underground them,thanks !~


4. InMan May 18, 2007 at 03:32

Screencast what i needed :)


5. Michael Cindric May 18, 2007 at 04:38

Great webcast that has helped alot keep them coming


6. BrianC May 18, 2007 at 06:34

Great episode.

Is there any way around having to restart the server to get it to reload the plugin? Any special "i'm writing a plugin now, please reload it's files" mode you can run in?

We're all spoiled by the magic reloading that rails does for us :)


7. Ryan Bates May 18, 2007 at 07:41

BrianC, I think you can have plugins auto-reload by adding this line to your plugin's init.rb file:

Dependencies.load_once_paths.delete(lib_path)

See the post by technoweenie for details:

http://weblog.techno-weenie.net/2007/1/26/understanding-the-rails-plugin-initialization-process


8. Rob May 18, 2007 at 08:03

Great screencast - I always wanted to know about plugins but couldn't find much information on them. This helped.


9. Trueke May 18, 2007 at 08:06

Hi!

Watching that webcast I got a question;

It's possible to extend nil object to make nil.anything returns nil?

I mean,

a = nil
b = a.anything
result: nil, not an error.

Did I explain myself?


10. infrid May 18, 2007 at 08:28

found these today and am so overwhelmed that I put down tools and gave myself an afternoon of training matrix style.

WOW - almost every single one has been a gem (no pun intended).

great length, great topics, really elegant solutions. I'd happily pay to view longer screencasts, if you do them!

Thanks so much. Really really appreciate your work.


11. Ryan Bates May 18, 2007 at 08:52

@Trueke, you can probably do this by overriding method_missing on NilClass, but I don't recommend it. This may break some things and changes the behavior of Ruby that other programs rely on. Instead, just do a nil check anytime you need to call a method on an object that could be nil.

@infrid, glad you enjoy the screencasts and thanks for the encouraging comments! I enjoy doing them. :)


12. Carmen Ferrara May 18, 2007 at 09:38

Ryan,

Brilliant as usual. Thank you for putting this great resource out there. Your commitment to the RoR community and learning/teaching in general is to be highly commended.

Off topic, how are creating 'railscasts' while at RailsConf? Your ability to crank out high quality content at such a rapid pace never ceases to amaze me.

Keep up the great work.


13. Yaroslav Dmitriev May 18, 2007 at 09:58

Thanks a lot!


14. Jake May 18, 2007 at 10:08

Thanks for another great one Ryan, I'm really impressed how quickly and regularly you release these at such high quality. I always get excited when I see the little (1) next to railscasts in my RSS reader.


15. Geoff Buesing May 18, 2007 at 10:56

Great stuff!

You could use module_eval/class_eval instead of define_method, right? Might make the code a little more readable (i.e., no calls to instance_variable_set, write_attribute, etc.)


16. Ross May 18, 2007 at 12:18

This was by far the best episode so far. This is a topic I was going to delve into very soon but you gave me a huge head start. One thing that might be neat to cover would be taking this a step further and adding a validates_X method to ActiveRecord::Base as well to validate the date string (validates_date_string or something like that). I would think of this as a more elegant solution than simply using the validate hook (though I guess this is a fairly straightforward process given the information already presented so maybe this isn't really necessary).


17. Ryan Bates May 18, 2007 at 13:17

@Geoff, good point. I don't like putting code into a string and I didn't realize you could pass a block to class_eval and module_eval. You're right, that would be cleaner. Thanks for the suggestion. :)

Update: actually I need to define the name of the method, so I don't think it's possible to do this with module_eval without passing a string. I avoid that whenever possible so I guess define_method is the right way to go for me.

@Ross, thanks for the encouraging comments. Creating a validates_* method is a great idea. I'll try it out and consider creating an episode on it.


18. Radarek May 18, 2007 at 15:12

@Ryan, your screencasts are as great as ruby/rails language and framework! Keep Your good work :). Greetings from Poland.


19. Geoff Buesing May 19, 2007 at 13:28

Right, module_eval would require a string evaluation for this case, so if you're uncomfortable with that, define_method is indeed the way to go.

For comparison, here's a module_eval version:

      module_eval <<-EOV
        def #{name}_string
          #{name}.to_s(:db)
        end
      EOV

The Rails source code uses module_eval/class_eval to define macros like acts_as_list, for whatever that's worth...


20. Nicolás Sanguinetti May 19, 2007 at 15:47

Great screencast! :)
Making plugins is actually easier than I thought :D (which doesn't surprise me all that much, considering the whole philosophy of rails).

One question though, why use write_attribute(name) and read_attribute(name) instead of send(name.to_sym) and send("#{name}=".to_sym)? That way if the user is further rewriting due_at and due_at= for some preprocessing they wouldn't loose there changes :)

(Maybe I'm missing on something about why it's not useful to do so, though)

Again, congrats on the great screencast :)


21. Ryan Bates May 19, 2007 at 16:05

Excellent point Nicolás! Doing "send" is better because of the point that you mentioned. Thanks for bringing that up.


22. darryl west Jul 04, 2007 at 10:53

I try to use plugins inside and outside of rails so putting the reference to ActiveRecord::Base at the bottom of the plugin/lib file rather than in the init.rb is a bit more portable.

Also, I agree with Nicolas about using either send or respond_to? rather than read/write_attribute. this de-couples the plugin from the database and makes it easier to unit test.

thanks for your great work!


23. Mike Stramba Jul 10, 2007 at 21:46

Ryan,
Is there a way to display the generated code from define_method ?

Mike


24. Mike Stramba Jul 10, 2007 at 21:47

I didn't phrase that too well.

I mean display the code that define_method generates.

Mike


25. Ryan Bates Jul 11, 2007 at 07:28

Display it where? Do you mean out put it as a string to the log or something? I don't think that's possible with define_method.


26. Mike Stramba Jul 11, 2007 at 11:34

Yes, that's what I meant, i.e. access the generated code to output it anywhere.

Mike


27. Ryan Bates Jul 11, 2007 at 15:30

@Mike, although you can't do that with define_method, you can put the code in a big string to begin with and then use class_eval when you want to evaluate it. That way it's in a string so you can output it however you want. See Geoff's comment above for an example.


28. Mike Stramba Jul 12, 2007 at 02:48

Well actually with Geof's example the string is still holding the "source" / meta code.

What I want is the resulting "compiled" / generated code

Mike


29. Ryan Bates Jul 12, 2007 at 07:27

@mike, the variables in the string are evaluated, so the string contains the resulting code. See this pastie for example:
http://pastie.caboo.se/78317


30. tom Apr 25, 2008 at 14:53

I am writing a plugin and would. My friends also would like to use it, however I haven't found any tutorials on how to "correctly" deploy a plugin.

Any pointers?


31. kino May 23, 2008 at 01:51

Because of the relation between reason and our ampliative judgements, it remains a mystery why our understanding, in other words, is by its very nature contradictory.

Add your comment:

(SKIP THIS ONE)

(required)

(not shown)


(use pastie or gist for code)

sponsored by:
if you want to help:
required:
Get Quicktime Player