diff --git a/Manifest b/Manifest new file mode 100644 index 0000000..5684f64 --- /dev/null +++ b/Manifest @@ -0,0 +1,19 @@ +README +Rakefile +generators/nested_restful_scaffold/USAGE +generators/nested_restful_scaffold/nested_restful_scaffold_generator.rb +generators/nested_restful_scaffold/templates/controller.rb +generators/nested_restful_scaffold/templates/fixtures.yml +generators/nested_restful_scaffold/templates/functional_test.rb +generators/nested_restful_scaffold/templates/helper.rb +generators/nested_restful_scaffold/templates/layout.html.erb +generators/nested_restful_scaffold/templates/migration.rb +generators/nested_restful_scaffold/templates/model.rb +generators/nested_restful_scaffold/templates/style.css +generators/nested_restful_scaffold/templates/unit_test.rb +generators/nested_restful_scaffold/templates/view_edit.html.erb +generators/nested_restful_scaffold/templates/view_index.html.erb +generators/nested_restful_scaffold/templates/view_new.html.erb +generators/nested_restful_scaffold/templates/view_show.html.erb +nested_restful_scaffold.gemspec +Manifest diff --git a/README b/README new file mode 100644 index 0000000..bea95c0 --- /dev/null +++ b/README @@ -0,0 +1,96 @@ += Nested Restful Scaffold + +== What is NestedRestfulScaffold +NestedRestfulScaffold is a gem built by BadrIT (http://www.badrit.com) for easily generating controller, views, model and routes of nested resources. + +== Why NestedRestfulScaffold +The story begins when I was working on a project at BadrIT (http://www.badrit.com) using Ruby on Rails. +We needed to generate simple scaffold controller, views, models and routes for many nested resources. +There were a huge number of resources and nested resources and sometimes the length of nesting was more than two. +The problem I faced that I need to create them with rails scaffold generator then I need to update all generated controllers, views and models. +* I need to update routes.rb to handle nested paths like /libraries/1/books +* In the controller I need to be sure I am accessing the correct resource in the nested chain. +* All ActiveRecord calls in the controller must be scoped. +* Views will have a lot of work to support nested forms and links. +* Create the Active Record associations in my models. +This was a big overhead to do all of that with all resources, so I decided to create a generator to do all that work for me. + +== How to install +To install QuickMagick just type at your command line: + gem install nested_restful_scaffold +... and it's done. +You don't have to install any libraries or compile code from source. + +== How to use +Usage: + script/generate nested_restful_scaffold ModelName [field:type, field:type, resource1,resource2,...:resources] + +Lets start with an example of a library resource and each library has books and each book has pages. + +=== Library Resource +Use the following command to create library resource + script/generate nested_restful_scaffold library name:string address:text +It will use rails scaffold generator because there isn't any nested resources included in the previous command. + +=== Book & Page Resources +Use the following command to create library resource + script/generate nested_restful_scaffold book name:string description:text library:references library:resources + script/generate nested_restful_scaffold page contents:text book:references library,book:resources + +As it is shown, we used library:resources for books resources and library,book:resources for pages resources. +They will do the same job of rails scaffold generator in addition to the following: N.B. I will explain the result of the pages generation and it will be the same for book resources. + +* It will update the routes.rb file with libraries, books and pages routing to be as the following: + map.resources :libraries do |library| + library.resources :books do |book| + book.resources :pages + end + end +If you run rake routes, we can see the pages routes as the following: + library_book_pages GET /libraries/:library_id/books/:book_id/pages {:controller=>"pages", :action=>"index"} + formatted_library_book_pages GET /libraries/:library_id/books/:book_id/pages.:format {:controller=>"pages", :action=>"index"} + POST /libraries/:library_id/books/:book_id/pages {:controller=>"pages", :action=>"create"} + POST /libraries/:library_id/books/:book_id/pages.:format {:controller=>"pages", :action=>"create"} + new_library_book_page GET /libraries/:library_id/books/:book_id/pages/new {:controller=>"pages", :action=>"new"} + formatted_new_library_book_page GET /libraries/:library_id/books/:book_id/pages/new.:format {:controller=>"pages", :action=>"new"} + edit_library_book_page GET /libraries/:library_id/books/:book_id/pages/:id/edit {:controller=>"pages", :action=>"edit"} +formatted_edit_library_book_page GET /libraries/:library_id/books/:book_id/pages/:id/edit.:format {:controller=>"pages", :action=>"edit"} + library_book_page GET /libraries/:library_id/books/:book_id/pages/:id {:controller=>"pages", :action=>"show"} + formatted_library_book_page GET /libraries/:library_id/books/:book_id/pages/:id.:format {:controller=>"pages", :action=>"show"} + PUT /libraries/:library_id/books/:book_id/pages/:id {:controller=>"pages", :action=>"update"} + PUT /libraries/:library_id/books/:book_id/pages/:id.:format {:controller=>"pages", :action=>"update"} + DELETE /libraries/:library_id/books/:book_id/pages/:id {:controller=>"pages", :action=>"destroy"} + DELETE /libraries/:library_id/books/:book_id/pages/:id.:format {:controller=>"pages", :action=>"destroy"} + +* Pages controller has new method book to get its parent resource + protected + def book + @book ||= Library.find(params[:library_id]).books.find(params[:book_id]) + end +* All ActiveRecord calls for page resource will be through its book to be well scoped. + @pages = book.pages +* The most bunch of work in in views files + <%= link_to 'Show', library_book_page_path(page.book.library,page.book,page) %> + <%= link_to 'Edit', edit_library_book_page_path(page.book.library,page.book,page) %> + <%= link_to 'Destroy', library_book_page_path(page.book.library,page.book,page), :confirm => 'Are you sure?', :method => :delete %> + and two links at the end of the html page to new page and to return to books page + <%= link_to 'New page', new_library_book_page_path %> + <%= link_to 'Return to books', library_books_path %> +* In book.rb model file, there will be new line added to define association + has_many :pages + and in page.rb model file, also there will be new line added + belongs_to :book + +== Conclusion +NestedRestfulScaffold is very easy to install, very easy to use and allows you to generate all that work for you. + +It will be very suitable for you if you are building a RESTful API or application with nested resources. + +It will reduce the overhead of updating controller, views, models and routes. + + +For more information on nested resources check: + +http://adam.blog.heroku.com/past/2007/12/20/nested_resources_in_rails_2 + +http://www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..224326f --- /dev/null +++ b/Rakefile @@ -0,0 +1,14 @@ +require 'rubygems' +require 'rake' +require 'echoe' + +Echoe.new('nested_restful_scaffold', '0.1.0') do |p| + p.description = "NestedRestfulScaffold allows you to generate controller, views, model and routes of nested resources." + p.url = "http://nestedrestfulscaffold.rubyforge.org/" + p.author = "Mahmoud Khaled" + p.email = "mahmoud.khaled@badrit.com" + p.project = "nestedrestscaff" +end + +#Dir["#{File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext } + diff --git a/doc/created.rid b/doc/created.rid new file mode 100644 index 0000000..21b81d6 --- /dev/null +++ b/doc/created.rid @@ -0,0 +1 @@ +Thu, 17 Sep 2009 15:21:26 +0200 diff --git a/doc/files/README.html b/doc/files/README.html new file mode 100644 index 0000000..8133075 --- /dev/null +++ b/doc/files/README.html @@ -0,0 +1,289 @@ + + + + + + File: README + + + + + + + + + + +
+

README

+ + + + + + + + + +
Path:README +
Last Update:Thu Sep 17 15:21:24 +0200 2009
+
+ + +
+ + + +
+ +
+

Nested Restful Scaffold

+

What is NestedRestfulScaffold

+

+NestedRestfulScaffold is a gem built by BadrIT (www.badrit.com) for easily generating +controller, views, model and routes of nested resources. +

+

Why NestedRestfulScaffold

+

+The story begins when I was working on a project at BadrIT (www.badrit.com) using Ruby on Rails. We +needed to generate simple scaffold controller, views, models and routes for +many nested resources. There were a huge number of resources and nested +resources and sometimes the length of nesting was more than two. The +problem I faced that I need to create them with rails scaffold generator +then I need to update all generated controllers, views and models. +

+
    +
  • I need to update routes.rb to handle nested paths like /libraries/1/books + +
  • +
  • In the controller I need to be sure I am accessing the correct resource in +the nested chain. + +
  • +
  • All ActiveRecord calls in the controller must be scoped. + +
  • +
  • Views will have a lot of work to support nested forms and links. + +
  • +
  • Create the Active Record associations in my models. + +
  • +
+

+This was a big overhead to do all of that with all resources, so I decided +to create a generator to do all that work for me. +

+

How to install

+

+To install QuickMagick just type at your command line: +

+
+  gem install nested_restful_scaffold
+
+

+… and it‘s done. You don‘t have to install any libraries +or compile code from source. +

+

How to use

+

+Usage: +

+
+  script/generate nested_restful_scaffold ModelName [field:type, field:type, resource1,resource2,...:resources]
+
+

+Lets start with an example of a library resource and each library has books +and each book has pages. +

+

Library Resource

+

+Use the following command to create library resource +

+
+  script/generate nested_restful_scaffold library name:string address:text
+
+

+It will use rails scaffold generator because there isn‘t any nested +resources included in the previous command. +

+

Book & Page Resources

+

+Use the following command to create library resource +

+
+  script/generate nested_restful_scaffold book name:string description:text library:references library:resources
+  script/generate nested_restful_scaffold page contents:text book:references library,book:resources
+
+

+As it is shown, we used library:resources for books resources and +library,book:resources for pages resources. They will do the same +job of rails scaffold generator in addition to the following: N.B. I will +explain the result of the pages generation and it will be the same for book +resources. +

+
    +
  • It will update the routes.rb file with libraries, books and pages routing +to be as the following: + +
    +  map.resources :libraries do |library|
    +    library.resources :books do |book|
    +      book.resources :pages
    +    end
    +  end
    +
    +
  • +
+

+If you run rake routes, we can see the pages routes as the following: +

+
+ library_book_pages GET    /libraries/:library_id/books/:book_id/pages                  {:controller=>"pages", :action=>"index"}
+    formatted_library_book_pages GET    /libraries/:library_id/books/:book_id/pages.:format          {:controller=>"pages", :action=>"index"}
+                                 POST   /libraries/:library_id/books/:book_id/pages                  {:controller=>"pages", :action=>"create"}
+                                 POST   /libraries/:library_id/books/:book_id/pages.:format          {:controller=>"pages", :action=>"create"}
+           new_library_book_page GET    /libraries/:library_id/books/:book_id/pages/new              {:controller=>"pages", :action=>"new"}
+ formatted_new_library_book_page GET    /libraries/:library_id/books/:book_id/pages/new.:format      {:controller=>"pages", :action=>"new"}
+          edit_library_book_page GET    /libraries/:library_id/books/:book_id/pages/:id/edit         {:controller=>"pages", :action=>"edit"}
+
+

+formatted_edit_library_book_page GET +/libraries/:library_id/books/:book_id/pages/:id/edit.:format +{:controller=>"pages", :action=>"edit"} +

+
+               library_book_page GET    /libraries/:library_id/books/:book_id/pages/:id              {:controller=>"pages", :action=>"show"}
+     formatted_library_book_page GET    /libraries/:library_id/books/:book_id/pages/:id.:format      {:controller=>"pages", :action=>"show"}
+                                 PUT    /libraries/:library_id/books/:book_id/pages/:id              {:controller=>"pages", :action=>"update"}
+                                 PUT    /libraries/:library_id/books/:book_id/pages/:id.:format      {:controller=>"pages", :action=>"update"}
+                                 DELETE /libraries/:library_id/books/:book_id/pages/:id              {:controller=>"pages", :action=>"destroy"}
+                                 DELETE /libraries/:library_id/books/:book_id/pages/:id.:format      {:controller=>"pages", :action=>"destroy"}
+
+
    +
  • Pages controller has new method book to get its parent resource + +
    + protected
    +  def book
    +    @book ||= Library.find(params[:library_id]).books.find(params[:book_id])
    +  end
    +
    +
  • +
  • All ActiveRecord calls for page resource will be through its book to be +well scoped. @pages = book.pages + +
  • +
  • The most bunch of work in in views files + +
    +  <td><%= link_to 'Show', library_book_page_path(page.book.library,page.book,page) %></td>
    +  <td><%= link_to 'Edit', edit_library_book_page_path(page.book.library,page.book,page) %></td>
    +  <td><%= link_to 'Destroy', library_book_page_path(page.book.library,page.book,page), :confirm => 'Are you sure?', :method => :delete %></td>
    +
    +

    +and two links at the end of the html page to new page and to return to +books page +

    +
    +  <%= link_to 'New page', new_library_book_page_path %>
    +  <%= link_to 'Return to books', library_books_path %>
    +
    +
  • +
  • In book.rb model file, there will be new line added to define association +has_many :pages and in page.rb model file, also there will be new line +added belongs_to :book + +
  • +
+

Conclusion

+

+NestedRestfulScaffold is very easy to install, very easy to use and allows +you to generate all that work for you. +

+

+It will be very suitable for you if you are building a RESTful API or +application with nested resources. +

+

+It will reduce the overhead of updating controller, views, models and +routes. +

+

+For more information on nested resources check: +

+

+adam.blog.heroku.com/past/2007/12/20/nested_resources_in_rails_2 +

+

+www.akitaonrails.com/2007/12/12/rolling-with-rails-2-0-the-first-full-tutorial +

+ +
+ + +
+ + +
+ + + + +
+ + + + + + + + + + + +
+ + +
+

[Validate]

+
+ + + \ No newline at end of file diff --git a/doc/fr_class_index.html b/doc/fr_class_index.html new file mode 100644 index 0000000..49ce9e2 --- /dev/null +++ b/doc/fr_class_index.html @@ -0,0 +1,26 @@ + + + + + + + + Classes + + + + + +
+

Classes

+
+
+
+ + \ No newline at end of file diff --git a/doc/fr_file_index.html b/doc/fr_file_index.html new file mode 100644 index 0000000..94ecb65 --- /dev/null +++ b/doc/fr_file_index.html @@ -0,0 +1,27 @@ + + + + + + + + Files + + + + + +
+

Files

+
+ README
+
+
+ + \ No newline at end of file diff --git a/doc/fr_method_index.html b/doc/fr_method_index.html new file mode 100644 index 0000000..cdfab83 --- /dev/null +++ b/doc/fr_method_index.html @@ -0,0 +1,26 @@ + + + + + + + + Methods + + + + + +
+

Methods

+
+
+
+ + \ No newline at end of file diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 0000000..2046ed8 --- /dev/null +++ b/doc/index.html @@ -0,0 +1,24 @@ + + + + + + + Nested_restful_scaffold + + + + + + + + + + + \ No newline at end of file diff --git a/doc/rdoc-style.css b/doc/rdoc-style.css new file mode 100644 index 0000000..44c7b3d --- /dev/null +++ b/doc/rdoc-style.css @@ -0,0 +1,208 @@ + +body { + font-family: Verdana,Arial,Helvetica,sans-serif; + font-size: 90%; + margin: 0; + margin-left: 40px; + padding: 0; + background: white; +} + +h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; } +h1 { font-size: 150%; } +h2,h3,h4 { margin-top: 1em; } + +a { background: #eef; color: #039; text-decoration: none; } +a:hover { background: #039; color: #eef; } + +/* Override the base stylesheet's Anchor inside a table cell */ +td > a { + background: transparent; + color: #039; + text-decoration: none; +} + +/* and inside a section title */ +.section-title > a { + background: transparent; + color: #eee; + text-decoration: none; +} + +/* === Structural elements =================================== */ + +div#index { + margin: 0; + margin-left: -40px; + padding: 0; + font-size: 90%; +} + + +div#index a { + margin-left: 0.7em; +} + +div#index .section-bar { + margin-left: 0px; + padding-left: 0.7em; + background: #ccc; + font-size: small; +} + + +div#classHeader, div#fileHeader { + width: auto; + color: white; + padding: 0.5em 1.5em 0.5em 1.5em; + margin: 0; + margin-left: -40px; + border-bottom: 3px solid #006; +} + +div#classHeader a, div#fileHeader a { + background: inherit; + color: white; +} + +div#classHeader td, div#fileHeader td { + background: inherit; + color: white; +} + + +div#fileHeader { + background: #057; +} + +div#classHeader { + background: #048; +} + + +.class-name-in-header { + font-size: 180%; + font-weight: bold; +} + + +div#bodyContent { + padding: 0 1.5em 0 1.5em; +} + +div#description { + padding: 0.5em 1.5em; + background: #efefef; + border: 1px dotted #999; +} + +div#description h1,h2,h3,h4,h5,h6 { + color: #125;; + background: transparent; +} + +div#validator-badges { + text-align: center; +} +div#validator-badges img { border: 0; } + +div#copyright { + color: #333; + background: #efefef; + font: 0.75em sans-serif; + margin-top: 5em; + margin-bottom: 0; + padding: 0.5em 2em; +} + + +/* === Classes =================================== */ + +table.header-table { + color: white; + font-size: small; +} + +.type-note { + font-size: small; + color: #DEDEDE; +} + +.xxsection-bar { + background: #eee; + color: #333; + padding: 3px; +} + +.section-bar { + color: #333; + border-bottom: 1px solid #999; + margin-left: -20px; +} + + +.section-title { + background: #79a; + color: #eee; + padding: 3px; + margin-top: 2em; + margin-left: -30px; + border: 1px solid #999; +} + +.top-aligned-row { vertical-align: top } +.bottom-aligned-row { vertical-align: bottom } + +/* --- Context section classes ----------------------- */ + +.context-row { } +.context-item-name { font-family: monospace; font-weight: bold; color: black; } +.context-item-value { font-size: small; color: #448; } +.context-item-desc { color: #333; padding-left: 2em; } + +/* --- Method classes -------------------------- */ +.method-detail { + background: #efefef; + padding: 0; + margin-top: 0.5em; + margin-bottom: 1em; + border: 1px dotted #ccc; +} +.method-heading { + color: black; + background: #ccc; + border-bottom: 1px solid #666; + padding: 0.2em 0.5em 0 0.5em; +} +.method-signature { color: black; background: inherit; } +.method-name { font-weight: bold; } +.method-args { font-style: italic; } +.method-description { padding: 0 0.5em 0 0.5em; } + +/* --- Source code sections -------------------- */ + +a.source-toggle { font-size: 90%; } +div.method-source-code { + background: #262626; + color: #ffdead; + margin: 1em; + padding: 0.5em; + border: 1px dashed #999; + overflow: hidden; +} + +div.method-source-code pre { color: #ffdead; overflow: hidden; } + +/* --- Ruby keyword styles --------------------- */ + +.standalone-code { background: #221111; color: #ffdead; overflow: hidden; } + +.ruby-constant { color: #7fffd4; background: transparent; } +.ruby-keyword { color: #00ffff; background: transparent; } +.ruby-ivar { color: #eedd82; background: transparent; } +.ruby-operator { color: #00ffee; background: transparent; } +.ruby-identifier { color: #ffdead; background: transparent; } +.ruby-node { color: #ffa07a; background: transparent; } +.ruby-comment { color: #b22222; font-weight: bold; background: transparent; } +.ruby-regexp { color: #ffa07a; background: transparent; } +.ruby-value { color: #7fffd4; background: transparent; } \ No newline at end of file diff --git a/generators/nested_restful_scaffold/USAGE b/generators/nested_restful_scaffold/USAGE new file mode 100644 index 0000000..59f6e8f --- /dev/null +++ b/generators/nested_restful_scaffold/USAGE @@ -0,0 +1,31 @@ +Description: + Scaffolds an entire resource, from model and migration to controller and + views, along with a full test suite. The resource is ready to use for your + restful, resource-oriented application. + +Usage: + Pass the name of the model, either CamelCased or under_scored, as the first + argument, an optional list of attribute pairs, and an optional list of + resources tree comma separated. + + Attribute pairs are column_name:sql_type arguments specifying the + model's attributes. Timestamps are added by default, so you don't have to + specify them by hand as 'created_at:datetime updated_at:datetime'. + + You don't have to think up every attribute up front, but it helps to + sketch out a few so you can start working with the resource immediately. + + Resources list is an ordered list of nested parent resources. They are comma + separated ordered from root to child. 'library,book,page:resources' + + For example, `nested_restful_scaffold page page_number:integer contents:text + book:references library,book:resources` + gives you a model with attributes page_number, contents and book_id, a controller that handles + the create/show/update/destroy, forms to create and edit your posts, and + an index that lists them all, as well as a book.resources :pages + declaration in config/routes.rb. + +Examples: + `script/generate nested_restful_scaffold library name:string address:text` + `script/generate nested_restful_scaffold book name:string description:text library:references library:resources ` + `script/generate nested_restful_scaffold page page_number:integer contents:text book:references library,book:resources` \ No newline at end of file diff --git a/generators/nested_restful_scaffold/nested_restful_scaffold_generator.rb b/generators/nested_restful_scaffold/nested_restful_scaffold_generator.rb new file mode 100644 index 0000000..bb3d5e1 --- /dev/null +++ b/generators/nested_restful_scaffold/nested_restful_scaffold_generator.rb @@ -0,0 +1,285 @@ +class NestedRestfulScaffoldGenerator < Rails::Generator::NamedBase + default_options :skip_timestamps => false, :skip_migration => false + + attr_reader :controller_name, + :controller_class_path, + :controller_file_path, + :controller_class_nesting, + :controller_class_nesting_depth, + :controller_class_name, + :controller_underscore_name, + :controller_singular_name, + :controller_plural_name + alias_method :controller_file_name, :controller_underscore_name + alias_method :controller_table_name, :controller_plural_name + + def initialize(runtime_args, runtime_options = {}) + super + + @controller_name = @name.pluralize + + base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name) + @controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name) + @controller_singular_name=base_name.singularize + if @controller_class_nesting.empty? + @controller_class_name = @controller_class_name_without_nesting + else + @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}" + end + + # Remove resources from attributes + # and push them in new instance varialbe @resources + # to separate between resources attributes and other attributes + @resources = [] + attributes.delete_if{|a| a.type.to_s == 'resources' and @resources = a.name.split(',').collect{|e| e.strip} } + @resources = nil if @resources.empty? + end + + def manifest + record do |m| + unless command_has_resources + m.dependency "scaffold", [name] + @args + else + # Check for class naming collisions. + m.class_collisions(controller_class_path, "#{controller_class_name}Controller", "#{controller_class_name}Helper") + m.class_collisions(class_path, "#{class_name}") + m.class_collisions class_path, class_name, "#{class_name}Test" + + # Controller, helper, views, test and stylesheets directories. + m.directory(File.join('app/models', class_path)) + m.directory(File.join('app/controllers', controller_class_path)) + m.directory(File.join('app/helpers', controller_class_path)) + m.directory(File.join('app/views', controller_class_path, controller_file_name)) + m.directory(File.join('app/views/layouts', controller_class_path)) + m.directory(File.join('test/functional', controller_class_path)) + m.directory(File.join('test/unit', class_path)) + m.directory(File.join('public/stylesheets', class_path)) + + # Model, test, and fixture directories. + m.directory File.join('app/models', class_path) + m.directory File.join('test/unit', class_path) + m.directory File.join('test/fixtures', class_path) + + + for action in scaffold_views + m.template( + "view_#{action}.html.erb", + File.join('app/views', controller_class_path, controller_file_name, "#{action}.html.erb") + ) + end + + # Layout and stylesheet. + m.template('layout.html.erb', File.join('app/views/layouts', controller_class_path, "#{controller_file_name}.html.erb")) + m.template('style.css', 'public/stylesheets/scaffold.css') + + m.template( + "controller.rb", File.join('app/controllers', controller_class_path, "#{controller_file_name}_controller.rb") + ) + + m.template('functional_test.rb', File.join('test/functional', controller_class_path, "#{controller_file_name}_controller_test.rb")) + m.template('helper.rb', File.join('app/helpers', controller_class_path, "#{controller_file_name}_helper.rb")) + + # add routes + generate_routes m + + # genearte model, unit_test, fixtures and migration + generate_model m + end + end + end + + def resources + @resources + end + + def command_has_resources + !resources.nil? + end + + # Return parent resource name + def parent_resource_name + command_has_resources ? resources.last : '' + end + + # Generate find statement to be used in the controller + def generate_find_statement + first_resource = resources.first + statement = "#{first_resource.classify}.find(params[:#{first_resource}_id])" + + statement = resources[1..-1].inject(statement){|s, resource| s += ".#{resource.pluralize}.find(params[:#{resource}_id])"} + end + + # Generate singular resource + # prefix can be 'edit' to generate edit_resource_path + # add_params will be used to generate params with the medthod + # instance_object if true, it will add "@" to be instance variable + # plural if true, pluralize the last resource. It is useful for index actions + # reject_last if true, doesn't add last resource to the path. It is used in index view for "Return to parent resource" link + def nested_resource_path name, params = {} + params = {:prefix => "", :add_params => true, :instance_object => false, + :plural => false, :reject_last => false}.merge(params) + result = "" + + result += resources[0..-2] * "_" + result += "_" unless resources[0..-2].empty? + + # used only in index view for link "Return to" + result += params[:plural] && params[:reject_last] ? resources.last.pluralize : resources.last + result += '_' + + if params[:reject_last] + result += "path" + elsif command_has_resources + result += params[:plural] ? name.pluralize : name + result += "_path" + else + result = name + end + + if !params[:prefix].empty? && !command_has_resources + result += "_path" + end + + force_params = params[:prefix]=="edit" + + # add parameters to the method + param_name = name + param_name = "@" + param_name if params[:instance_object] + result += nested_resource_path_params(param_name, force_params, params[:plural]) if params[:add_params] || force_params + + # add prefix "edit" or "new" + result = params[:prefix] + "_" + result unless params[:prefix].empty? + result + end + + # Return method parameters + def nested_resource_path_params name, force_params, plural + result = "" + attr_params = resources.collect{|r| r} + + if command_has_resources || force_params + attr_params << name + i = resources.size + while i > 0 + attr_params[i-1] = attr_params[i] + "." + attr_params[i-1] + i -= 1 + end + + # skip last attribute if plural + attr_params = attr_params[0..-2] if plural + + result = "(" + attr_params.join(',') + ")" + end + + result + end + + # Generate conditions for find statement to be used in view with input select + def generate_conditions owner, name + result = "" + parent = nil + has_resource = false + resources.each do |resource| + if resource == name + has_resource = true + break + end + + parent = resource if resource != name + end + + result = ", :conditions => [\"#{parent}_id = ?\", @#{owner}.#{name}.#{parent}.id]" if has_resource && parent + result + end + + # Add routes to file routes.rb + def generate_routes m + # routes + unless command_has_resources + # add routes like unnested scaffold + # eg. map.resources books + m.route_resources controller_file_name + else + resource_list = controller_file_name.map { |r| r.to_sym.inspect }.join(', ') + parent_resource = parent_resource_name + + path = destination_path('config/routes.rb') + content = File.read(path) + + logger.route "resources #{resource_list}" + + # map.resources :parents do |parent| + # parent.resources :parents do |parent| + sentinel = "\.resources(.*)?:#{parent_resource.pluralize}(.*)do(.*)\\|#{parent_resource}\\|" + + if content =~ /#{sentinel}/ + gsub_file 'config/routes.rb', sentinel do |match| + "#{match}\n #{parent_resource}.resources :#{table_name}" + end + else + # without do block + # map.resources :parents + # parent.resources :parents + sentinel = "\.resources(.*):#{parent_resource.pluralize}" + if content =~ /#{sentinel}/ + gsub_file 'config/routes.rb', sentinel do |match| + "#{match} do |#{parent_resource}|\n #{parent_resource}.resources :#{table_name}\n end" + end + end + end + end + end + + # Genearte model, unit_test, fixtures, migration and add has_many relationshipt to parents + def generate_model m + # Model class, unit test, and fixtures. + m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb") + m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb") + + unless options[:skip_fixture] + m.template 'fixtures.yml', File.join('test/fixtures', "#{table_name}.yml") + end + + unless options[:skip_migration] + m.migration_template 'migration.rb', 'db/migrate', :assigns => { + :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}" + }, :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}" + end + + # add has_many to referenced + attributes.find_all{|a| a.type.to_s == "references"}.each do |parent| + gsub_file "app/models/#{parent.name}.rb", "class #{parent.name.camelize} < ActiveRecord::Base" do |match| + "#{match}\n has_many :#{table_name}" + end + end + end + + protected + # Override with your own usage banner. + def banner + "Usage: #{$0} nested_restful_scaffold ModelName [field:type, field:type, resource1,resource2,...:resources]" + end + + def add_options!(opt) + opt.separator '' + opt.separator 'Options:' + opt.on("--skip-timestamps", + "Don't add timestamps to the migration file for this model") { |v| options[:skip_timestamps] = v } + opt.on("--skip-migration", + "Don't generate a migration file for this model") { |v| options[:skip_migration] = v } + end + + def gsub_file(relative_destination, regexp, *args, &block) + path = destination_path(relative_destination) + content = File.read(path).gsub(/#{regexp}/, *args, &block) + File.open(path, 'wb') { |file| file.write(content) } + end + + def scaffold_views + %w[ index show new edit ] + end + + def model_name + class_name.demodulize + end +end diff --git a/generators/nested_restful_scaffold/templates/.tmp_view_edit.html.erb.92014~ b/generators/nested_restful_scaffold/templates/.tmp_view_edit.html.erb.92014~ new file mode 100644 index 0000000..8386eb1 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/.tmp_view_edit.html.erb.92014~ @@ -0,0 +1,22 @@ +

Editing <%= singular_name %>

+ +<%%= error_messages_for :<%= singular_name %> %> + +<%% form_for(@<%= singular_name %><%= ", :url => " + nested_resource_path(singular_name, :instance_object => true) if command_has_resources %>) do |f| %> +<% for attribute in attributes -%> +

+ <%= attribute.column.human_name %>
+ <% if attribute.type.to_s != "references" || options[:skip_select] %> + <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> + <% else %> + <%%= select("<%= singular_name %>", "<%= attribute.name %>_id", <%= attribute.name.classify %>.find(:all<%= generate_conditions singular_name, attribute.name %>).collect{|p| [p.name, p.id]}) %> + <% end %> +

+<% end -%> +

+ <%%= f.submit "Update" %> +

+<%% end %> + +<%%= link_to 'Show', <%= nested_resource_path singular_name, :instance_object => true %> %> | +<%%= link_to 'Back', <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> %> diff --git a/generators/nested_restful_scaffold/templates/.tmp_view_index.html.erb.16528~ b/generators/nested_restful_scaffold/templates/.tmp_view_index.html.erb.16528~ new file mode 100644 index 0000000..df8dcae --- /dev/null +++ b/generators/nested_restful_scaffold/templates/.tmp_view_index.html.erb.16528~ @@ -0,0 +1,26 @@ +

Listing <%= plural_name %>

+ + + +<% for attribute in attributes -%> + +<% end -%> + + +<%% for <%= singular_name %> in @<%= plural_name %> %> + +<% for attribute in attributes -%> + +<% end -%> + + + + +<%% end %> +
<%= attribute.column.human_name %>
<%%=h <%= singular_name %>.<%= attribute.name %><%= ".name" if attribute.type.to_s == "references" && !options[:skip_select] %> %><%%= link_to 'Show', <%= nested_resource_path singular_name %> %><%%= link_to 'Edit', <%= nested_resource_path singular_name, :prefix => "edit" %> %><%%= link_to 'Destroy', <%= nested_resource_path singular_name %>, :confirm => 'Are you sure?', :method => :delete %>
+ +
+ +<%%= link_to 'New <%= singular_name %>', <%= nested_resource_path singular_name, :prefix => "new", :add_params => false %> %> +
+<%%= link_to 'Return to <%= parent_resource_name.pluralize %>', <%= nested_resource_path singular_name, :add_params => false, :plural => true, :reject_last => true %> %> diff --git a/generators/nested_restful_scaffold/templates/.tmp_view_new.html.erb.70054~ b/generators/nested_restful_scaffold/templates/.tmp_view_new.html.erb.70054~ new file mode 100644 index 0000000..8498a2e --- /dev/null +++ b/generators/nested_restful_scaffold/templates/.tmp_view_new.html.erb.70054~ @@ -0,0 +1,21 @@ +

New <%= singular_name %>

+ +<%%= error_messages_for :<%= singular_name %> %> + +<%% form_for(@<%= singular_name %><%= ", :url => " + nested_resource_path(singular_name, :instance_object => true, :plural => true) if command_has_resources %>) do |f| %> +<% for attribute in attributes -%> +

+ <%= attribute.column.human_name %>
+ <% if attribute.type.to_s != "references" || options[:skip_select] %> + <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> + <% else %> + <%%= select("<%= singular_name %>", "<%= attribute.name %>_id", <%= attribute.name.classify %>.find(:all<%= generate_conditions singular_name, attribute.name %>).collect{|p| [p.name, p.id]}) %> + <% end %> +

+<% end -%> +

+ <%%= f.submit "Create" %> +

+<%% end %> + +<%%= link_to 'Back', <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> %> diff --git a/generators/nested_restful_scaffold/templates/.tmp_view_show.html.erb.31581~ b/generators/nested_restful_scaffold/templates/.tmp_view_show.html.erb.31581~ new file mode 100644 index 0000000..9200800 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/.tmp_view_show.html.erb.31581~ @@ -0,0 +1,9 @@ +<% for attribute in attributes -%> +

+ <%= attribute.column.human_name %>: + <%%=h @<%= singular_name %>.<%= attribute.name %><%= ".name" if attribute.type.to_s == "references" && !options[:skip_select] %> %> +

+<% end -%> + +<%%= link_to 'Edit', <%= nested_resource_path(singular_name, :prefix => "edit", :instance_object => true) %> %> | +<%%= link_to 'Back', <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> %> diff --git a/generators/nested_restful_scaffold/templates/controller.rb b/generators/nested_restful_scaffold/templates/controller.rb new file mode 100644 index 0000000..3d23b01 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/controller.rb @@ -0,0 +1,94 @@ +class <%= controller_class_name %>Controller < ApplicationController + # GET /<%= table_name %> + # GET /<%= table_name %>.xml + <% resource_name = parent_resource_name %> + def index + @<%= table_name %> = <%= resource_name %>.<%= table_name %> + + respond_to do |format| + format.html # index.html.erb + format.xml { render :xml => @<%= table_name %> } + end + end + + # GET /<%= table_name %>/1 + # GET /<%= table_name %>/1.xml + def show + @<%= file_name %> = <%= resource_name %>.<%= table_name %>.find(params[:id]) + + respond_to do |format| + format.html # show.html.erb + format.xml { render :xml => @<%= file_name %> } + end + end + + # GET /<%= table_name %>/new + # GET /<%= table_name %>/new.xml + def new + @<%= file_name %> = <%= class_name %>.new + @<%= file_name %>.<%= resource_name %> = <%= resource_name %> + + respond_to do |format| + format.html # new.html.erb + format.xml { render :xml => @<%= file_name %> } + end + end + + # GET /<%= table_name %>/1/edit + def edit + @<%= file_name %> = <%= resource_name %>.<%= table_name %>.find(params[:id]) + end + + # POST /<%= table_name %> + # POST /<%= table_name %>.xml + def create + @<%= file_name %> = <%= class_name %>.new(params[:<%= file_name %>]) if params[:<%= file_name %>][:<%= resource_name %>_id].nil? || params[:<%= file_name %>][:<%= resource_name %>_id] == <%= resource_name %>.id.to_s + @<%= file_name %>.<%= resource_name %>_id = <%= resource_name %>.id + + respond_to do |format| + if @<%= file_name %>.save + flash[:notice] = '<%= class_name %> was successfully created.' + format.html { redirect_to(<%= nested_resource_path singular_name, :instance_object => true %>) } + format.xml { render :xml => @<%= file_name %>, :status => :created, :location => @<%= file_name %> } + else + format.html { render :action => "new" } + format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity } + end + end + end + + # PUT /<%= table_name %>/1 + # PUT /<%= table_name %>/1.xml + def update + @<%= file_name %> = <%= resource_name %>.<%= table_name %>.find(params[:id]) + + respond_to do |format| + if @<%= file_name %>.update_attributes(params[:<%= file_name %>]) + flash[:notice] = '<%= class_name %> was successfully updated.' + format.html { redirect_to(<%= nested_resource_path singular_name, :instance_object => true %>) } + format.xml { head :ok } + else + format.html { render :action => "edit" } + format.xml { render :xml => @<%= file_name %>.errors, :status => :unprocessable_entity } + end + end + end + + # DELETE /<%= table_name %>/1 + # DELETE /<%= table_name %>/1.xml + def destroy + @<%= file_name %> = <%= resource_name %>.<%= table_name %>.find(params[:id]) + url_path = <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> + @<%= file_name %>.destroy + + respond_to do |format| + format.html { redirect_to(url_path) } + format.xml { head :ok } + end + end + + protected + def <%= resource_name %> + @<%= resource_name %> ||= <%= generate_find_statement %> + end +end diff --git a/generators/nested_restful_scaffold/templates/fixtures.yml b/generators/nested_restful_scaffold/templates/fixtures.yml new file mode 100644 index 0000000..c210351 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/fixtures.yml @@ -0,0 +1,19 @@ +# Read about fixtures at http://ar.rubyonrails.org/classes/Fixtures.html + +<% unless attributes.empty? -%> +one: +<% for attribute in attributes -%> + <%= attribute.name %>: <%= attribute.default %> +<% end -%> + +two: +<% for attribute in attributes -%> + <%= attribute.name %>: <%= attribute.default %> +<% end -%> +<% else -%> +# one: +# column: value +# +# two: +# column: value +<% end -%> diff --git a/generators/nested_restful_scaffold/templates/functional_test.rb b/generators/nested_restful_scaffold/templates/functional_test.rb new file mode 100644 index 0000000..3b430a2 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/functional_test.rb @@ -0,0 +1,45 @@ +require 'test_helper' + +class <%= controller_class_name %>ControllerTest < ActionController::TestCase + def test_should_get_index + get :index + assert_response :success + assert_not_nil assigns(:<%= table_name %>) + end + + def test_should_get_new + get :new + assert_response :success + end + + def test_should_create_<%= file_name %> + assert_difference('<%= class_name %>.count') do + post :create, :<%= file_name %> => { } + end + + assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) + end + + def test_should_show_<%= file_name %> + get :show, :id => <%= table_name %>(:one).id + assert_response :success + end + + def test_should_get_edit + get :edit, :id => <%= table_name %>(:one).id + assert_response :success + end + + def test_should_update_<%= file_name %> + put :update, :id => <%= table_name %>(:one).id, :<%= file_name %> => { } + assert_redirected_to <%= file_name %>_path(assigns(:<%= file_name %>)) + end + + def test_should_destroy_<%= file_name %> + assert_difference('<%= class_name %>.count', -1) do + delete :destroy, :id => <%= table_name %>(:one).id + end + + assert_redirected_to <%= table_name %>_path + end +end diff --git a/generators/nested_restful_scaffold/templates/helper.rb b/generators/nested_restful_scaffold/templates/helper.rb new file mode 100644 index 0000000..9bd821b --- /dev/null +++ b/generators/nested_restful_scaffold/templates/helper.rb @@ -0,0 +1,2 @@ +module <%= controller_class_name %>Helper +end diff --git a/generators/nested_restful_scaffold/templates/layout.html.erb b/generators/nested_restful_scaffold/templates/layout.html.erb new file mode 100644 index 0000000..5c1f304 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/layout.html.erb @@ -0,0 +1,17 @@ + + + + + + <%= controller_class_name %>: <%%= controller.action_name %> + <%%= stylesheet_link_tag 'scaffold' %> + + + +

<%%= flash[:notice] %>

+ +<%%= yield %> + + + diff --git a/generators/nested_restful_scaffold/templates/migration.rb b/generators/nested_restful_scaffold/templates/migration.rb new file mode 100644 index 0000000..382fd11 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/migration.rb @@ -0,0 +1,16 @@ +class <%= migration_name %> < ActiveRecord::Migration + def self.up + create_table :<%= table_name %> do |t| +<% for attribute in attributes -%> + t.<%= attribute.type %> :<%= attribute.name %> +<% end -%> +<% unless options[:skip_timestamps] %> + t.timestamps +<% end -%> + end + end + + def self.down + drop_table :<%= table_name %> + end +end diff --git a/generators/nested_restful_scaffold/templates/model.rb b/generators/nested_restful_scaffold/templates/model.rb new file mode 100644 index 0000000..5650dba --- /dev/null +++ b/generators/nested_restful_scaffold/templates/model.rb @@ -0,0 +1,5 @@ +class <%= class_name %> < ActiveRecord::Base +<% for attribute in attributes -%> +<%= "belongs_to :#{attribute.name}" if attribute.type.to_s == "references" %> +<% end -%> +end diff --git a/generators/nested_restful_scaffold/templates/style.css b/generators/nested_restful_scaffold/templates/style.css new file mode 100644 index 0000000..093c209 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/style.css @@ -0,0 +1,54 @@ +body { background-color: #fff; color: #333; } + +body, p, ol, ul, td { + font-family: verdana, arial, helvetica, sans-serif; + font-size: 13px; + line-height: 18px; +} + +pre { + background-color: #eee; + padding: 10px; + font-size: 11px; +} + +a { color: #000; } +a:visited { color: #666; } +a:hover { color: #fff; background-color:#000; } + +.fieldWithErrors { + padding: 2px; + background-color: red; + display: table; +} + +#errorExplanation { + width: 400px; + border: 2px solid red; + padding: 7px; + padding-bottom: 12px; + margin-bottom: 20px; + background-color: #f0f0f0; +} + +#errorExplanation h2 { + text-align: left; + font-weight: bold; + padding: 5px 5px 5px 15px; + font-size: 12px; + margin: -7px; + background-color: #c00; + color: #fff; +} + +#errorExplanation p { + color: #333; + margin-bottom: 0; + padding: 5px; +} + +#errorExplanation ul li { + font-size: 12px; + list-style: square; +} + diff --git a/generators/nested_restful_scaffold/templates/unit_test.rb b/generators/nested_restful_scaffold/templates/unit_test.rb new file mode 100644 index 0000000..96bd34a --- /dev/null +++ b/generators/nested_restful_scaffold/templates/unit_test.rb @@ -0,0 +1,8 @@ +require 'test_helper' + +class <%= class_name %>Test < ActiveSupport::TestCase + # Replace this with your real tests. + def test_truth + assert true + end +end diff --git a/generators/nested_restful_scaffold/templates/view_edit.html.erb b/generators/nested_restful_scaffold/templates/view_edit.html.erb new file mode 100644 index 0000000..8551f59 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/view_edit.html.erb @@ -0,0 +1,20 @@ +

Editing <%= singular_name %>

+ +<%%= error_messages_for :<%= singular_name %> %> + +<%% form_for(@<%= singular_name %><%= ", :url => " + nested_resource_path(singular_name, :instance_object => true) if command_has_resources %>) do |f| %> +<% for attribute in attributes -%> +

+ <%= attribute.column.human_name %>
+ <% if attribute.type.to_s != "references" %> + <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> + <% end %> +

+<% end -%> +

+ <%%= f.submit "Update" %> +

+<%% end %> + +<%%= link_to 'Show', <%= nested_resource_path singular_name, :instance_object => true %> %> | +<%%= link_to 'Back', <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> %> diff --git a/generators/nested_restful_scaffold/templates/view_index.html.erb b/generators/nested_restful_scaffold/templates/view_index.html.erb new file mode 100644 index 0000000..c5035da --- /dev/null +++ b/generators/nested_restful_scaffold/templates/view_index.html.erb @@ -0,0 +1,26 @@ +

Listing <%= plural_name %>

+ + + +<% for attribute in attributes -%> + +<% end -%> + + +<%% for <%= singular_name %> in @<%= plural_name %> %> + +<% for attribute in attributes -%> + +<% end -%> + + + + +<%% end %> +
<%= attribute.column.human_name %>
<%%=h <%= singular_name %>.<%= attribute.name %> %><%%= link_to 'Show', <%= nested_resource_path singular_name %> %><%%= link_to 'Edit', <%= nested_resource_path singular_name, :prefix => "edit" %> %><%%= link_to 'Destroy', <%= nested_resource_path singular_name %>, :confirm => 'Are you sure?', :method => :delete %>
+ +
+ +<%%= link_to 'New <%= singular_name %>', <%= nested_resource_path singular_name, :prefix => "new", :add_params => false %> %> +
+<%%= link_to 'Return to <%= parent_resource_name.pluralize %>', <%= nested_resource_path singular_name, :add_params => false, :plural => true, :reject_last => true %> %> diff --git a/generators/nested_restful_scaffold/templates/view_new.html.erb b/generators/nested_restful_scaffold/templates/view_new.html.erb new file mode 100644 index 0000000..f99a40a --- /dev/null +++ b/generators/nested_restful_scaffold/templates/view_new.html.erb @@ -0,0 +1,19 @@ +

New <%= singular_name %>

+ +<%%= error_messages_for :<%= singular_name %> %> + +<%% form_for(@<%= singular_name %><%= ", :url => " + nested_resource_path(singular_name, :instance_object => true, :plural => true) if command_has_resources %>) do |f| %> +<% for attribute in attributes -%> +

+ <%= attribute.column.human_name %>
+ <% if attribute.type.to_s != "references" %> + <%%= f.<%= attribute.field_type %> :<%= attribute.name %> %> + <% end %> +

+<% end -%> +

+ <%%= f.submit "Create" %> +

+<%% end %> + +<%%= link_to 'Back', <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> %> diff --git a/generators/nested_restful_scaffold/templates/view_show.html.erb b/generators/nested_restful_scaffold/templates/view_show.html.erb new file mode 100644 index 0000000..1883003 --- /dev/null +++ b/generators/nested_restful_scaffold/templates/view_show.html.erb @@ -0,0 +1,9 @@ +<% for attribute in attributes -%> +

+ <%= attribute.column.human_name %>: + <%%=h @<%= singular_name %>.<%= attribute.name %> %> +

+<% end -%> + +<%%= link_to 'Edit', <%= nested_resource_path(singular_name, :prefix => "edit", :instance_object => true) %> %> | +<%%= link_to 'Back', <%= nested_resource_path singular_name, :instance_object => true, :plural => true %> %>