{"id":31,"date":"2009-07-31T16:22:36","date_gmt":"2009-07-31T19:22:36","guid":{"rendered":"http:\/\/blog.plataformatec.com.br\/?p=31"},"modified":"2010-04-22T14:57:45","modified_gmt":"2010-04-22T17:57:45","slug":"creating-your-own-generators-with-thor","status":"publish","type":"post","link":"https:\/\/blog.plataformatec.com.br\/2009\/07\/creating-your-own-generators-with-thor\/","title":{"rendered":"Creating your own generators with Thor"},"content":{"rendered":"
This summer I was selected with Josh Peek<\/a>, Emilio Tagua<\/a> and Nelson Crespo<\/a> to work with Rails on Google Summer of Code (GSoC), which Nelson named as the Rails Summer Quartet<\/a>. \ud83d\ude42<\/p>\n Here, at Plataforma<\/a>, we use a set of tools on our projects, which includes Inherited Resources<\/a>, Remarkable<\/a> and Formtastic<\/a>. At some point, we were planning on creating generators for each of those tools but still they couldn’t play along. If I wanted a generator that uses all three projects, I needed to create a inherited_remarkable_formtastic generator which is not DRY at all.<\/p>\n For example, for those who wants to use Datamapper with Rspec, they need to call “ruby script\/generate dm_rspec_model” instead of “ruby script\/generate model”. Since Rails 3.0 is moving towards agnosticism, my GSoC proposal was exactly bring it to rails generators.<\/p>\n One day before the official coding period start, I was staring at this Thor post<\/a> by Yehuda Katz. Thor<\/a> is a rake replacement with support to options parsing:<\/p>\n And then can be invoked as:<\/p>\n At that point, I realized that a generator is nothing more than a scripting task (like rake or thor) with some extra methods which makes the creation and copy of files easy. Thor had several features which convinced me that it was the best solution to build generators on top of:<\/p>\n Then I was able to create a ROADMAP to Thor:<\/p>\n Let’s see an example on how you can create your own generators with Thor. For example, a generators that stubs out a new gem structure:<\/p>\n You can see from the example above that we are inheriting from Thor::Group and not Thor. In Thor, each method corresponds to a task, which can be invoked on its own. In Thor::Group, you invoke all tasks at once, in the order they are declared. This is interesting because you split your script\/generator into several methods. It improves readability and allows anyone to inherit from your generator and change just one step in the process.<\/p>\n The next step, on lines 4 and 5, is to define arguments and options for the class. Arguments are required to be given right after the executable while options are given with switches. The newgem above can be invoked as:<\/p>\n And it will create two files: “remarkable\/lib\/remarkable.rb”, “remarkable\/test\/remarkable_test.rb” and prompt the user (with the use of the method yes?<\/em>) if we wants to copy the MITLICENSE. If you want to change the test framework, you can give it as an option:<\/p>\n Now it generates “remarkable\/lib\/remarkable.rb” and “remarkable\/spec\/remarkable_spec.rb”.<\/p>\n The generation methods are kept into the Thor::Actions module, which is included on top of our class. It holds all the scripting methods, which are: copy_file<\/em>, create_file<\/em>, directory<\/em>, empty_directory<\/em>, get<\/em>, inject_into_file<\/em> and template<\/em>. All those actions can be revoked, so Thor knows how to do and undo the process (like in script\/generate and script\/destroy).<\/p>\n Even more, some of Rails templates methods was moved to Thor, like: inside<\/em>, run<\/em>, run_ruby_script<\/em>, gsub_file<\/em>, append_file <\/em>and prepend_file<\/em>. So whenever creating scripts with Thor, those methods will be available to make your life easier.<\/p>\n Finally, all user iteration methods are handled by Thor::Shell classes by say<\/em>, ask<\/em>, yes? <\/em>and no?<\/em> methods. Thor ships with two Shell implementations: Basic and Color. If you mantain an IDE for Rails, you can build your own shell and make the user interaction through it.<\/p>\n Thor is used as base in Rails::Generators, where Rails extend it to provide Rails specific functionalities, as hooks for ORM, Test framework and so on. This will be my talk subject on Rails Summit Latin America<\/a>, 13th October, in S\u00e3o Paulo, Brazil.<\/p>\n If you can join us or not, be sure to grab our RSS feed and keep on checking, we will discuss about it here too.<\/p>\n","protected":false},"excerpt":{"rendered":" This summer I was selected with Josh Peek, Emilio Tagua and Nelson Crespo to work with Rails on Google Summer of Code (GSoC), which Nelson named as the Rails Summer Quartet. \ud83d\ude42 Here, at Plataforma, we use a set of tools on our projects, which includes Inherited Resources, Remarkable and Formtastic. At some point, we … \u00bb<\/a><\/p>\n","protected":false},"author":4,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[1],"tags":[10,7,60,9],"aioseo_notices":[],"jetpack_sharing_enabled":true,"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/31"}],"collection":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/users\/4"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/comments?post=31"}],"version-history":[{"count":16,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/31\/revisions"}],"predecessor-version":[{"id":912,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/posts\/31\/revisions\/912"}],"wp:attachment":[{"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/media?parent=31"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/categories?post=31"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.plataformatec.com.br\/wp-json\/wp\/v2\/tags?post=31"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}1. So, what about Thor?!<\/h3>\n
class Speak < Thor\r\n desc \"name\", \"the name to say hello to\"\r\n method_options :loudly => false\r\n\r\n def hello(name)\r\n name.upcase! if options[:loudly]\r\n puts \"Hello #{name}\"\r\n end\r\nend<\/pre>\n
> thor speak:hello jose\r\nHello jose\r\n\r\n> thor speak:hello jose \u2013-loudly\r\nHello JOSE<\/pre>\n
\n
\n
2. An example<\/h3>\n
class NewgemGenerator < Thor::Group\r\n include Thor::Actions\r\n\r\n # Define arguments and options\r\n argument :name\r\n class_option :test_framework, :default => :test_unit\r\n\r\n def self.source_root\r\n File.dirname(__FILE__)\r\n end\r\n\r\n def create_lib_file\r\n create_file \"#{name}\/lib\/#{name}.rb\" do\r\n \"class #{name.camelize}\\nend\"\r\n end\r\n end\r\n\r\n def create_test_file\r\n test = options[:test_framework] == \"rspec\" ? :spec : :test\r\n create_file \"#{name}\/#{test}\/#{name}_#{test}.rb\"\r\n end\r\n\r\n def copy_licence\r\n if yes? \"Use MIT license?\"\r\n # Make a copy of the MITLICENSE file at the source root\r\n copy_file \"MITLICENSE\", \"#{name}\/MITLICENSE\"\r\n else\r\n say \"Shame on you\u2026\", :red\r\n end\r\n end\r\nend<\/pre>\n
newgem remarkable<\/pre>\n
newgem remarkable --test-framework=rspec<\/pre>\n
3. What is more?<\/h3>\n