grimen-dry_scaffold 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.textile +303 -0
- data/Rakefile +47 -0
- data/TODO.textile +12 -0
- data/generators/USAGE +8 -0
- data/generators/dry_scaffold/dry_scaffold_generator.rb +250 -0
- data/generators/dry_scaffold/templates/controller_inherited_resources.rb +40 -0
- data/generators/dry_scaffold/templates/controller_standard.rb +242 -0
- data/generators/dry_scaffold/templates/controller_test_standard.rb +84 -0
- data/generators/dry_scaffold/templates/helper_standard.rb +3 -0
- data/generators/dry_scaffold/templates/helper_test_standard.rb +5 -0
- data/generators/dry_scaffold/templates/prototypes/controller_inherited_resources.rb +23 -0
- data/generators/dry_scaffold/templates/prototypes/controller_standard.rb +132 -0
- data/generators/dry_scaffold/templates/prototypes/controller_test_standard.rb +64 -0
- data/generators/dry_scaffold/templates/prototypes/helper_standard.rb +3 -0
- data/generators/dry_scaffold/templates/prototypes/helper_test_standard.rb +5 -0
- data/generators/dry_scaffold/templates/prototypes/layout.html.haml +19 -0
- data/generators/dry_scaffold/templates/prototypes/view__form.html.haml +3 -0
- data/generators/dry_scaffold/templates/prototypes/view__item.html.haml +9 -0
- data/generators/dry_scaffold/templates/prototypes/view_edit.html.haml +10 -0
- data/generators/dry_scaffold/templates/prototypes/view_index.html.haml +17 -0
- data/generators/dry_scaffold/templates/prototypes/view_new.html.haml +10 -0
- data/generators/dry_scaffold/templates/prototypes/view_show.html.haml +13 -0
- data/generators/dry_scaffold/templates/view__form.html.haml +13 -0
- data/generators/dry_scaffold/templates/view__item.html.haml +10 -0
- data/generators/dry_scaffold/templates/view_edit.html.haml +18 -0
- data/generators/dry_scaffold/templates/view_index.html.haml +20 -0
- data/generators/dry_scaffold/templates/view_layout.html.haml +19 -0
- data/generators/dry_scaffold/templates/view_new.html.haml +18 -0
- data/generators/dry_scaffold/templates/view_show.html.haml +13 -0
- data/rails/init.rb +3 -0
- data/tasks/dry_scaffold.rake +51 -0
- metadata +88 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Jonas Grimfelt
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.textile
ADDED
@@ -0,0 +1,303 @@
|
|
1
|
+
h1. DRY SCAFFOLD
|
2
|
+
|
3
|
+
A Rails scaffold generator that generates DRYer, cleaner, and more useful code.
|
4
|
+
|
5
|
+
h2. Description
|
6
|
+
|
7
|
+
DryScaffold is a replacement for the Rails scaffold generator that generates code that most people end up deleting or rewriting anyway because of the unusable code. The scaffold concept is powerful, but it has more potential than generating messy and almost useless code. The goal with DryScaffold is to generate DRY, beautiful, and standards compliant code based on common patterns without adding a lot of magic.
|
8
|
+
|
9
|
+
h4. Key concepts:
|
10
|
+
|
11
|
+
* Controllers that are DRY, RESTful, no code-smells, following conventions, and implementing VERY common patterns
|
12
|
+
* Views that are DRY, semantic, standards compliant, valid, and more useful in development
|
13
|
+
* Generator that gets smart with additional arguments - but not stupid without them
|
14
|
+
* Any Rails developer should be able to switch generator with no effort - follow current conventions, but extend them
|
15
|
+
|
16
|
+
h2. Dependencies
|
17
|
+
|
18
|
+
h3. Required:
|
19
|
+
|
20
|
+
* "*haml*":http://github.com/nex3/haml - ERB sucks like PHP, end of story
|
21
|
+
|
22
|
+
h3. Optional:
|
23
|
+
|
24
|
+
* "*will_paginate*":http://github.com/mislav/will_paginate - Pagination
|
25
|
+
* "*formtastic*":http://github.com/justinfrench/formtastic - DRY and semantic forms
|
26
|
+
* "*inherited_resources*":http://github.com/josevalim/inherited_resources - DRY/Resourceful controllers
|
27
|
+
|
28
|
+
h2. Features
|
29
|
+
|
30
|
+
h3. Overview
|
31
|
+
|
32
|
+
The most characteristic features:
|
33
|
+
|
34
|
+
* Generates DRY controllers + functional tests.
|
35
|
+
* Generates DRY, semantic, and standard compliant views in HAML - not just HAMLized templates.
|
36
|
+
* Generates formtastic - very DRY - forms (using "formtastic" by Justin French et. al.) by default. Note: Can be turned off.
|
37
|
+
* Generates resourceful - even DRYer - controllers (using the "inherited_resources" by José Valim) by default. Note: Can be turned off.
|
38
|
+
* Collection pagination using will_paginate by default. Note: Can be turned off.
|
39
|
+
* Optionally specify what actions/views to generate (stubs for specified REST-actions will be generated).
|
40
|
+
* Optionally specify what respond_to-formats to generate (stubs for the most common respond_to-formats will be generated).
|
41
|
+
* Generates default helpers/models/migrations, and REST-routes.
|
42
|
+
|
43
|
+
h3. Formtastic Forms
|
44
|
+
|
45
|
+
Quick and dirty; Formtastic makes your form views cleaner, and your life as a Rails developer easier (for real). Formtastic forms can be turned off, but I would recommend any Rails developer to consider using it - there is really no good reason not to if you not running very old version of Rails.
|
46
|
+
|
47
|
+
h4. Default HAML (without Formtastic):
|
48
|
+
|
49
|
+
<pre>- form_for(@<%= singular_name %>) do |f|
|
50
|
+
= f.error_messages
|
51
|
+
%ul
|
52
|
+
%li
|
53
|
+
= f.label :title, 'Title'
|
54
|
+
= f.text_field :title
|
55
|
+
%li
|
56
|
+
= f.label :description, 'Description'
|
57
|
+
= f.text_field :description
|
58
|
+
%p.buttons
|
59
|
+
= f.submit 'Create'</pre>
|
60
|
+
|
61
|
+
h4. Formtastic + HAML:
|
62
|
+
|
63
|
+
<pre>- semantic_form_for(@resource) do |f|
|
64
|
+
- f.inputs do
|
65
|
+
= f.input :title
|
66
|
+
= f.input :description
|
67
|
+
- f.buttons do
|
68
|
+
= f.commit_button 'Create'</pre>
|
69
|
+
|
70
|
+
Find out more about *formtastic*: "http://github.com/justinfrench/formtastic":http://github.com/justinfrench/formtastic
|
71
|
+
|
72
|
+
h3. Resourceful Controllers
|
73
|
+
|
74
|
+
Quick and dirty; InheritedResources makes your controllers controllers cleaner, and might make experienced Rails developer's life DRYer code wise. This is possible because of REST as a convention in Rails, and therefore the patterns that arise in controllers can be DRYed up A LOT. Resourceful - InheritedResources-based - controllers can be turned off, but I would recommend any experienced Rails developer to consider using it - there is really no good reason not to unless maybe if you are a Rails beginner, then you should consider get used to the basic Rails building blocks first.
|
75
|
+
|
76
|
+
h4. Default (without InheritedResources)
|
77
|
+
|
78
|
+
<pre>def new
|
79
|
+
@duck = Duck.new
|
80
|
+
|
81
|
+
respond_to do |format|
|
82
|
+
format.html # new.html.haml
|
83
|
+
format.xml { render :xml => @duck }
|
84
|
+
format.json { render :json => @duck }
|
85
|
+
end
|
86
|
+
end</pre>
|
87
|
+
|
88
|
+
h4. Resourceful (InheritedResources)
|
89
|
+
|
90
|
+
<pre>actions :new
|
91
|
+
respond_to :html, :xml, :json</pre>
|
92
|
+
|
93
|
+
Find out more about *inherited_resources*: "http://github.com/josevalim/inherited_resources":http://github.com/josevalim/inherited_resources
|
94
|
+
|
95
|
+
h3. Pagination
|
96
|
+
|
97
|
+
Pagination is such a common feature that always seems to be implemented anyway, so dry_scaffold will generate a DRY solution for this in each controller that you can tweak - even thought that will not be needed in most cases. See DRY Patterns beneath for more details how it's done.
|
98
|
+
|
99
|
+
Find out more about *will_paginate*: "http://github.com/mislav/will_paginate":http://github.com/mislav/will_paginate
|
100
|
+
|
101
|
+
h3. DRY Patterns
|
102
|
+
|
103
|
+
Either if you choosing default or resourceful controllers, this pattern is used to DRYing up controllers a bit more, and at the same time loading resources in the same place using before_filter while adding automatic pagination for collections.
|
104
|
+
|
105
|
+
h4. Default (ActionController, without InheritedResources)
|
106
|
+
|
107
|
+
<pre>before_filter :load_resource, :only => [:show, :edit, :update, :destroy]
|
108
|
+
before_filter :load_and_paginate_resources, :only => [:index]
|
109
|
+
|
110
|
+
protected
|
111
|
+
|
112
|
+
def collection
|
113
|
+
paginate_options ||= {}
|
114
|
+
paginate_options[:page] ||= (params[:page] || 1)
|
115
|
+
paginate_options[:per_page] ||= (params[:per_page] || 20)
|
116
|
+
@collection = @resources ||= Duck.paginate(paginate_options)
|
117
|
+
end
|
118
|
+
alias :load_and_paginate_resources :collection
|
119
|
+
|
120
|
+
def resource
|
121
|
+
@resource = @resource = ||= Duck.find(params[:id])
|
122
|
+
end
|
123
|
+
alias :load_resource :resource</pre>
|
124
|
+
|
125
|
+
h4. Resourceful (InheritedResources)
|
126
|
+
|
127
|
+
<pre>protected
|
128
|
+
|
129
|
+
def collection
|
130
|
+
paginate_options ||= {}
|
131
|
+
paginate_options[:page] ||= (params[:page] || 1)
|
132
|
+
paginate_options[:per_page] ||= (params[:per_page] || 20)
|
133
|
+
@collection = @resources ||= end_of_association_chain.paginate(paginate_options)
|
134
|
+
end
|
135
|
+
|
136
|
+
def resource
|
137
|
+
@resource = @resource ||= end_of_association_chain.find(params[:id])
|
138
|
+
end</pre>
|
139
|
+
|
140
|
+
h3. View Partials
|
141
|
+
|
142
|
+
A very common pattern is to break up views in partials, wich is also what DryScaffold does:
|
143
|
+
|
144
|
+
* new/edit => _form
|
145
|
+
* index => _item
|
146
|
+
|
147
|
+
h3. More To Come... (TODO)
|
148
|
+
|
149
|
+
These are things I have in mind:
|
150
|
+
|
151
|
+
* Generation of factory stubs instead of fixture stubs that Rails model generator generates
|
152
|
+
* Generation of Rails builder stubs (RSS/Atom/...)
|
153
|
+
* Break up into multiple generators as dependencies (like Rails generators): dry_controller, dry_views, dry_scaffold, ...
|
154
|
+
|
155
|
+
h2. Setup
|
156
|
+
|
157
|
+
Installing DryScaffold is easy as 1-2-3...nah, really:
|
158
|
+
|
159
|
+
h3. 1. Installation
|
160
|
+
|
161
|
+
Install DryScaffold...
|
162
|
+
|
163
|
+
h4. Gem (Recommended)
|
164
|
+
|
165
|
+
<pre>gem sources -a http://gems.github.com
|
166
|
+
sudo gem install grimen-dry_scaffold</pre>
|
167
|
+
|
168
|
+
h4. Plugin
|
169
|
+
|
170
|
+
<pre>./script/plugin install git://github.com/grimen/dry_scaffold.git</pre>
|
171
|
+
|
172
|
+
h3. 2. Configuration (Gem only)
|
173
|
+
|
174
|
+
In: @config/environments/development.rb@
|
175
|
+
|
176
|
+
<pre>config.gem 'grimen-dry_scaffold', :lib => 'dry_scaffold'</pre>
|
177
|
+
|
178
|
+
h3. 3. Install Dependencies (Partly optional)
|
179
|
+
|
180
|
+
Install dependencies to release the full power of dry_scaffold. Only HAML is really required of these, but how could anyone resist candy? =)
|
181
|
+
|
182
|
+
h4. Installation, i.e. get the gems
|
183
|
+
|
184
|
+
<pre>sudo gem install haml</pre>
|
185
|
+
<pre>sudo gem install will_paginate</pre>
|
186
|
+
<pre>sudo gem install justinfrench-formtastic</pre>
|
187
|
+
<pre>sudo gem install josevalim-inherited_resources</pre>
|
188
|
+
|
189
|
+
h4. Configuration
|
190
|
+
|
191
|
+
In: @config/environment.rb@
|
192
|
+
|
193
|
+
<pre>config.gem 'haml'</pre>
|
194
|
+
<pre>config.gem 'will_paginate'</pre>
|
195
|
+
<pre>config.gem 'justinfrench-formtastic', :lib => 'formtastic, :source => 'http://gems.github.com'</pre>
|
196
|
+
<pre>config.gem 'josevalim-inherited_resources', :lib => 'inherited_resources', :source => 'http://gems.github.com'</pre>
|
197
|
+
|
198
|
+
h2. Usage
|
199
|
+
|
200
|
+
<pre>./script/generate dry_scaffold ModelName [attribute:type attribute:type] [_actions:new,create,...] [_formats:html,json,...] [--skip-pagination] [--skip-resourceful] [--skip-formtastic] [--skip-views] [--skip-helpers] [--skip-tests] [--include-layout]</pre>
|
201
|
+
|
202
|
+
h3. Model Name
|
203
|
+
|
204
|
+
Example: <pre>Hello</pre>
|
205
|
+
|
206
|
+
Same as in the default scaffold/model generator; the name of a new/existing model.
|
207
|
+
|
208
|
+
h3. Model Attributes
|
209
|
+
|
210
|
+
Example: <pre>title:string description:text ...</pre>
|
211
|
+
|
212
|
+
Same as in the default scaffold/model generator; model attributes and database migration column types.
|
213
|
+
|
214
|
+
h3. Controller Actions
|
215
|
+
|
216
|
+
Usage: <pre>_actions:new,create,recent,index,...</pre>
|
217
|
+
|
218
|
+
You can override what actions that should be generated directly - including custom actions.
|
219
|
+
|
220
|
+
NOTE: Sorry for the a bit ugly prefix (_), but I had to trick the Rails generator a bit to get this working for now. This is definitely something I want to fix sooner or later, but I skipped that for now with this small hack.
|
221
|
+
|
222
|
+
h4. Default actions (REST)
|
223
|
+
|
224
|
+
If no actions are specified, the following REST-actions will be generated by default:
|
225
|
+
|
226
|
+
* @show@
|
227
|
+
* @index@
|
228
|
+
* @new@
|
229
|
+
* @edit@
|
230
|
+
* @create@
|
231
|
+
* @update@
|
232
|
+
* @destroy@
|
233
|
+
|
234
|
+
Default controller action stubs, controller action test stubs, and corresponding views (and required partials), are generated for all of these actions.
|
235
|
+
|
236
|
+
h4. Custom actions
|
237
|
+
|
238
|
+
The above REST actions are in many RESTful applications the only ones needed. Any other specified actions will generate empty action function stubs for manual implementation. No views will be generated for custom actions.
|
239
|
+
|
240
|
+
h3. Controller Formats
|
241
|
+
|
242
|
+
Usage: <pre>_formats:html,xml,txt,...</pre>
|
243
|
+
|
244
|
+
You can override what respond_to-formats that should be generated directly - only for REST-actions right now because I tried to avoid bad assumptions.
|
245
|
+
|
246
|
+
NOTE: Sorry for the a bit ugly prefix (_), but I had to trick the Rails generator a bit to get this working for now. This is definitely something I want to fix sooner or later, but I skipped that for now with this small hack.
|
247
|
+
|
248
|
+
h4. Default formats
|
249
|
+
|
250
|
+
If no formats are specified, the following formats will be generated by default:
|
251
|
+
|
252
|
+
* @html@ => Render: resource using view template (HTML/HAML)
|
253
|
+
* @js@ => Render: resource using view template (JS/RJS)
|
254
|
+
* @xml@ => Render: resource.to_xml
|
255
|
+
* @json@ => Render: resource.to_json
|
256
|
+
|
257
|
+
Default respond block stubs are generated for all of these formats - for each generated REST-action.
|
258
|
+
|
259
|
+
h4. Additional formats
|
260
|
+
|
261
|
+
Also, default respond block stubs are generated for any of these formats - for each generated REST-action - if they are specified:
|
262
|
+
|
263
|
+
* @yaml@ or @yml@ => Render: resource.to_yaml
|
264
|
+
* @txt@ or @text@ => Render: resource.to_s
|
265
|
+
|
266
|
+
h4. Custom formats
|
267
|
+
|
268
|
+
The above formats are the most commonly used ones, and respond blocks are already implemented using Rails default dependencies. Any other specified formats (such as PDF, CSV, etc.) will generate empty respond block stubs for manual implementation with help of additional dependencies.
|
269
|
+
|
270
|
+
h3. Options/Flags
|
271
|
+
|
272
|
+
Example: <pre>--skip-resourceful --include-layout</pre>
|
273
|
+
|
274
|
+
* @--skip-pagination@ - Don't generate pagination of collections in controller and views, i.e. don't use *will_paginate*.
|
275
|
+
* @--skip-resourceful@ - Don't generate resourceful controller, i.e. don't use *inherited_resources*.
|
276
|
+
* @--skip-formtastic@ - Don't generate formtastic forms in views, i.e. don't use *formtastic*.
|
277
|
+
* @--skip-views@ - Don't generate views.
|
278
|
+
* @--skip-helpers@ - Don't generate helpers.
|
279
|
+
* @--skip-tests@ - Don't generate tests (functional/unit/...).
|
280
|
+
* @--include-layout@ - Generate a neat application layout.
|
281
|
+
|
282
|
+
As DryScaffold is built upon Rails generator, the default generator options is available as well. For more details; see Rails documentation.
|
283
|
+
|
284
|
+
h2. Examples
|
285
|
+
|
286
|
+
No need for more samples here, just create a new Rails project, install dry_scaffold and it's dependencies, and try it out!
|
287
|
+
|
288
|
+
For your inspiration, you could try the following:
|
289
|
+
|
290
|
+
<pre>./script/generate dry_scaffold Zebra name:string about:text --skip-resourceful
|
291
|
+
./script/generate dry_scaffold Kangaroo name:string about:text --skip-formtastic
|
292
|
+
./script/generate dry_scaffold Duck name:string about:text _actions:new,create,index,quack
|
293
|
+
./script/generate dry_scaffold Parrot name:string about:text _formats:html,xml,yml</pre>
|
294
|
+
|
295
|
+
...or just go crazy!
|
296
|
+
|
297
|
+
h2. Bugs & Feedback
|
298
|
+
|
299
|
+
If you experience any issues/bugs or have feature requests, just file a GitHub-issue or send me a message.
|
300
|
+
If you think parts of my implementation could be implemented nicer somehow, please let me know...or just fork it and fix it yourself! =)
|
301
|
+
At last, positive feedback is always appreciated!
|
302
|
+
|
303
|
+
Copyright (c) 2009 Jonas Grimfelt
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
NAME = "dry_scaffold"
|
6
|
+
SUMMARY = %Q{A DRYer scaffold generator for Rails. Generates dry semantic and standards compliant views, and dry RESTful controllers.}
|
7
|
+
HOMEPAGE = "http://github.com/grimen/#{NAME}/tree/master"
|
8
|
+
AUTHOR = "Jonas Grimfelt"
|
9
|
+
EMAIL = "grimen@gmail.com"
|
10
|
+
|
11
|
+
begin
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
gem.name = NAME
|
15
|
+
gem.summary = SUMMARY
|
16
|
+
gem.description = SUMMARY
|
17
|
+
gem.homepage = HOMEPAGE
|
18
|
+
gem.author = AUTHOR
|
19
|
+
gem.email = EMAIL
|
20
|
+
|
21
|
+
gem.require_paths = %w{lib}
|
22
|
+
gem.files = %w(MIT-LICENSE README.textile TODO.textile Rakefile) + Dir.glob(File.join('{generators,rails,tasks}', '**', '*'))
|
23
|
+
gem.executables = %w()
|
24
|
+
gem.extra_rdoc_files = %w{README.textile}
|
25
|
+
end
|
26
|
+
rescue LoadError
|
27
|
+
puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
|
28
|
+
end
|
29
|
+
|
30
|
+
desc %Q{Run unit tests for "#{NAME}".}
|
31
|
+
task :default => :test
|
32
|
+
|
33
|
+
desc %Q{Run unit tests for "#{NAME}".}
|
34
|
+
Rake::TestTask.new(:test) do |test|
|
35
|
+
test.libs << 'lib'
|
36
|
+
test.pattern = 'test/**/*_test.rb'
|
37
|
+
test.verbose = true
|
38
|
+
end
|
39
|
+
|
40
|
+
desc %Q{Generate documentation for "#{NAME}".}
|
41
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = NAME
|
44
|
+
rdoc.options << '--line-numbers' << '--inline-source' << '--charset=UTF-8'
|
45
|
+
rdoc.rdoc_files.include('README.textile')
|
46
|
+
rdoc.rdoc_files.include(File.join('lib', '**', '*.rb'))
|
47
|
+
end
|
data/TODO.textile
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
h1. TODO
|
2
|
+
|
3
|
+
h2. Next
|
4
|
+
|
5
|
+
* Something like "model_generator_with_factories" (using machinist, factory_girl, ...). Source: http://github.com/vigetlabs/model_generator_with_factories/tree/master
|
6
|
+
* Break up in different generators: dry_controller, dry_views, etc., and use as dependencies in dry_scaffold
|
7
|
+
* Optionally generate builders (Atom/RSS/...)
|
8
|
+
|
9
|
+
h2. Maybe later
|
10
|
+
|
11
|
+
* Comment the code a bit more =)
|
12
|
+
* Check for overridden templates in: RAILS_ROOT/lib/dry_scaffold/templates/
|
data/generators/USAGE
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
NAME
|
2
|
+
dry_scaffold - A Rails scaffold generator that generates DRYer, cleaner, and more useful code.
|
3
|
+
|
4
|
+
DESCRIPTION
|
5
|
+
A replacement for the Rails scaffold generator that generates code that most people end up deleting or rewriting anyway because of the unusable code. The scaffold concept is powerful, but it has more potential than generating messy and almost useless code. The goal with dry_scaffold is to generate DRY, beautiful, and standards compliant code based on common patterns without adding a lot of magic.
|
6
|
+
|
7
|
+
EXAMPLE
|
8
|
+
./script/generate dry_scaffold ModelName [attribute:type attribute:type] [_actions:new,create,...] [_formats:html,json,...] [--skip-pagination] [--skip-resourceful] [--skip-formtastic] [--skip-views] [--skip-helpers] [--skip-tests] [--include-layout]
|
@@ -0,0 +1,250 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'formtastic'
|
5
|
+
FORMTASTIC = true
|
6
|
+
rescue
|
7
|
+
FORMTASTIC = false
|
8
|
+
end
|
9
|
+
begin
|
10
|
+
require 'inherited_resources'
|
11
|
+
INHERITED_RESOURCES = true
|
12
|
+
rescue
|
13
|
+
INHERITED_RESOURCES = false
|
14
|
+
end
|
15
|
+
begin
|
16
|
+
require 'will_paginate'
|
17
|
+
WILL_PAGINATE = true
|
18
|
+
rescue
|
19
|
+
WILL_PAGINATE = false
|
20
|
+
end
|
21
|
+
|
22
|
+
class DryScaffoldGenerator < Rails::Generator::NamedBase
|
23
|
+
|
24
|
+
DEFAULT_RESPOND_TO_FORMATS = [:html, :xml, :json].freeze
|
25
|
+
DEFAULT_MEMBER_ACTIONS = [:show, :new, :edit, :create, :update, :destroy].freeze
|
26
|
+
DEFAULT_MEMBER_AUTOLOAD_ACTIONS = (DEFAULT_MEMBER_ACTIONS - [:new, :create])
|
27
|
+
DEFAULT_COLLECTION_ACTIONS = [:index].freeze
|
28
|
+
DEFAULT_COLLECTION_AUTOLOAD_ACTIONS = DEFAULT_COLLECTION_ACTIONS
|
29
|
+
DEFAULT_CONTROLLER_ACTIONS = (DEFAULT_COLLECTION_ACTIONS + DEFAULT_MEMBER_ACTIONS)
|
30
|
+
|
31
|
+
DEFAULT_VIEW_TEMPLATE_FORMAT = :haml
|
32
|
+
|
33
|
+
CONTROLLERS_PATH = File.join('app', 'controllers').freeze
|
34
|
+
HELPERS_PATH = File.join('app', 'helpers').freeze
|
35
|
+
VIEWS_PATH = File.join('app', 'views').freeze
|
36
|
+
LAYOUTS_PATH = File.join(VIEWS_PATH, 'layouts').freeze
|
37
|
+
MODELS_PATH = File.join('app', 'models').freeze
|
38
|
+
FUNCTIONAL_TESTS_PATH = File.join('test', 'functional').freeze
|
39
|
+
UNIT_TESTS_PATH = File.join('test', 'unit', 'helpers').freeze
|
40
|
+
|
41
|
+
RESOURCEFUL_COLLECTION_NAME = 'collection'.freeze
|
42
|
+
RESOURCEFUL_SINGULAR_NAME = 'resource'.freeze
|
43
|
+
|
44
|
+
ARG_KEY_VALUE_DIVIDER = ':'.freeze
|
45
|
+
NON_ATTR_ARG_KEY_PREFIX = '_'.freeze
|
46
|
+
NON_ATTR_ARG_VALUE_DIVIDER = ','.freeze
|
47
|
+
|
48
|
+
# :{action} => [:{partial}, ...]
|
49
|
+
VIEW_TEMPLATES = {
|
50
|
+
:index => [:item],
|
51
|
+
:show => [],
|
52
|
+
:new => [:form],
|
53
|
+
:edit => [:form]
|
54
|
+
}.freeze
|
55
|
+
|
56
|
+
default_options :resourceful => INHERITED_RESOURCES,
|
57
|
+
:formtastic => FORMTASTIC,
|
58
|
+
:pagination => WILL_PAGINATE,
|
59
|
+
:skip_tests => false,
|
60
|
+
:skip_helpers => false,
|
61
|
+
:skip_views => false,
|
62
|
+
:include_layout => false
|
63
|
+
|
64
|
+
attr_reader :controller_name,
|
65
|
+
:controller_class_path,
|
66
|
+
:controller_file_path,
|
67
|
+
:controller_class_nesting,
|
68
|
+
:controller_class_nesting_depth,
|
69
|
+
:controller_class_name,
|
70
|
+
:controller_underscore_name,
|
71
|
+
:controller_singular_name,
|
72
|
+
:controller_plural_name,
|
73
|
+
:collection_name,
|
74
|
+
:model_singular_name,
|
75
|
+
:model_plural_name,
|
76
|
+
:view_template_format,
|
77
|
+
:actions,
|
78
|
+
:formats
|
79
|
+
|
80
|
+
alias_method :controller_file_name, :controller_underscore_name
|
81
|
+
alias_method :controller_table_name, :controller_plural_name
|
82
|
+
|
83
|
+
def initialize(runtime_args, runtime_options = {})
|
84
|
+
super
|
85
|
+
|
86
|
+
@controller_name = @name.pluralize
|
87
|
+
base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
|
88
|
+
@controller_class_name_without_nesting, @controller_underscore_name, @controller_plural_name = inflect_names(base_name)
|
89
|
+
@controller_singular_name = base_name.singularize
|
90
|
+
|
91
|
+
if @controller_class_nesting.empty?
|
92
|
+
@controller_class_name = @controller_class_name_without_nesting
|
93
|
+
else
|
94
|
+
@controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
|
95
|
+
end
|
96
|
+
|
97
|
+
@view_template_format = DEFAULT_VIEW_TEMPLATE_FORMAT
|
98
|
+
|
99
|
+
@attributes ||= []
|
100
|
+
@args_for_model ||= []
|
101
|
+
|
102
|
+
# Non-attribute args, i.e. "_actions:new,create". Add to options instead
|
103
|
+
@args.each do |arg|
|
104
|
+
arg_entities = arg.split(ARG_KEY_VALUE_DIVIDER)
|
105
|
+
if arg =~ /^#{NON_ATTR_ARG_KEY_PREFIX}actions/
|
106
|
+
@actions = arg_entities[1].split(NON_ATTR_ARG_VALUE_DIVIDER).compact.collect { |action| action.dowcase.to_sym }
|
107
|
+
elsif arg =~ /^#{NON_ATTR_ARG_KEY_PREFIX}(formats|respond_to)/
|
108
|
+
@formats = arg_entities[1].split(NON_ATTR_ARG_VALUE_DIVIDER).compact.collect { |format| format.dowcases.to_sym }
|
109
|
+
else
|
110
|
+
@attributes << Rails::Generator::GeneratedAttribute.new(*arg_entities)
|
111
|
+
@args_for_model << arg
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
@actions ||= DEFAULT_COLLECTION_ACTIONS + DEFAULT_MEMBER_ACTIONS
|
116
|
+
@formats ||= DEFAULT_RESPOND_TO_FORMATS
|
117
|
+
end
|
118
|
+
|
119
|
+
def manifest
|
120
|
+
record do |m|
|
121
|
+
# Check for class naming collisions.
|
122
|
+
m.class_collisions "#{controller_class_name}Controller", "#{controller_class_name}ControllerTest"
|
123
|
+
m.class_collisions "#{controller_class_name}Helper", "#{controller_class_name}HelperTest"
|
124
|
+
m.class_collisions "#{class_path}", "#{class_name}"
|
125
|
+
|
126
|
+
# Directories.
|
127
|
+
m.directory File.join(CONTROLLERS_PATH, controller_class_path)
|
128
|
+
m.directory File.join(HELPERS_PATH, controller_class_path) unless options[:skip_helpers]
|
129
|
+
m.directory File.join(VIEWS_PATH, controller_class_path, controller_file_name) unless options[:skip_views]
|
130
|
+
m.directory File.join(FUNCTIONAL_TESTS_PATH, controller_class_path) unless options[:skip_tests]
|
131
|
+
m.directory File.join(UNIT_TESTS_PATH, controller_class_path) unless options[:skip_tests]
|
132
|
+
|
133
|
+
# Controllers.
|
134
|
+
controller_template = options[:resourceful] ? 'inherited_resources' : 'standard'
|
135
|
+
m.template "controller_#{controller_template}.rb",
|
136
|
+
File.join(CONTROLLERS_PATH, controller_class_path, "#{controller_file_name}_controller.rb")
|
137
|
+
|
138
|
+
# Controller Tests.
|
139
|
+
unless options[:skip_tests]
|
140
|
+
m.template 'controller_test_standard.rb',
|
141
|
+
File.join(FUNCTIONAL_TESTS_PATH, controller_class_path, "#{controller_file_name}_controller_test.rb")
|
142
|
+
end
|
143
|
+
|
144
|
+
# Helpers.
|
145
|
+
unless options[:skip_helpers]
|
146
|
+
m.template 'helper_standard.rb',
|
147
|
+
File.join(HELPERS_PATH, controller_class_path, "#{controller_file_name}_helper.rb")
|
148
|
+
# Helper Tests
|
149
|
+
unless options[:skip_tests]
|
150
|
+
m.template 'helper_test_standard.rb',
|
151
|
+
File.join(UNIT_TESTS_PATH, controller_class_path, "#{controller_file_name}_helper_test.rb")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
# Views.
|
156
|
+
unless options[:skip_views]
|
157
|
+
# View template for each action.
|
158
|
+
(actions & VIEW_TEMPLATES.keys).each do |action|
|
159
|
+
m.template "view_#{action}.html.#{view_template_format}", File.join(VIEWS_PATH, controller_file_name, "#{action}.html.#{view_template_format}")
|
160
|
+
# View template for each partial - if not already copied.
|
161
|
+
(VIEW_TEMPLATES[action] || []).each do |partial|
|
162
|
+
m.template "view__#{partial}.html.#{view_template_format}",
|
163
|
+
File.join(VIEWS_PATH, controller_file_name, "_#{partial}.html.#{view_template_format}")
|
164
|
+
end
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Layout.
|
169
|
+
if options[:include_layout]
|
170
|
+
m.template "view_layout.html.#{view_template_format}",
|
171
|
+
File.join(LAYOUTS_PATH, "#{controller_file_name}.html.#{view_template_format}")
|
172
|
+
end
|
173
|
+
|
174
|
+
# Routes.
|
175
|
+
m.route_resources controller_file_name
|
176
|
+
|
177
|
+
# Models - use Rails default generator.
|
178
|
+
m.dependency 'model', [name] + @args_for_model, :collision => :skip
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
protected
|
183
|
+
|
184
|
+
def symbol_array_to_expression(array)
|
185
|
+
":#{array.compact.join(', :')}" if array.present?
|
186
|
+
end
|
187
|
+
|
188
|
+
def assign_names!(name)
|
189
|
+
super
|
190
|
+
@model_singular_name = @singular_name
|
191
|
+
@model_plural_name = @plural_name
|
192
|
+
@collection_name = options[:resourceful] ? RESOURCEFUL_COLLECTION_NAME : @model_plural_name
|
193
|
+
@singular_name = options[:resourceful] ? RESOURCEFUL_SINGULAR_NAME : @model_singular_name
|
194
|
+
@plural_name = options[:resourceful] ? RESOURCEFUL_SINGULAR_NAME.pluralize : @model_plural_name
|
195
|
+
end
|
196
|
+
|
197
|
+
def add_options!(opt)
|
198
|
+
opt.separator ''
|
199
|
+
opt.separator 'Options:'
|
200
|
+
|
201
|
+
opt.on('--skip-pagination',
|
202
|
+
"Skip 'will_paginate' for collections in controllers and views, wich requires gem 'mislav-will_paginate'.") do |v|
|
203
|
+
options[:pagination] = !v
|
204
|
+
end
|
205
|
+
|
206
|
+
opt.on('--skip-resourceful',
|
207
|
+
"Skip 'inherited_resources' style controllers and views, wich requires gem 'josevalim-inherited_resources'.") do |v|
|
208
|
+
options[:resourceful] = !v
|
209
|
+
end
|
210
|
+
|
211
|
+
opt.on('--skip-formtastic',
|
212
|
+
"Skip 'formtastic' style forms, wich requires gem 'justinfrench-formtastic'.") do |v|
|
213
|
+
options[:formtastic] = !v
|
214
|
+
end
|
215
|
+
|
216
|
+
opt.on('--skip-views', "Skip generation of views.") do |v|
|
217
|
+
options[:skip_views] = v
|
218
|
+
end
|
219
|
+
|
220
|
+
opt.on('--skip-helper', "Skip generation of helpers.") do |v|
|
221
|
+
options[:skip_helpers] = v
|
222
|
+
end
|
223
|
+
|
224
|
+
opt.on('--skip-tests', "Skip generation of tests.") do |v|
|
225
|
+
options[:skip_tests] = v
|
226
|
+
end
|
227
|
+
|
228
|
+
opt.on('--include-layout', "Generate layout.") do |v|
|
229
|
+
options[:include_layout] = v
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def model_name
|
234
|
+
class_name.demodulize
|
235
|
+
end
|
236
|
+
|
237
|
+
def banner
|
238
|
+
"Usage: #{$0} dry_scaffold ModelName [field:type field:type ...]" +
|
239
|
+
" [_actions:new,create,...]" +
|
240
|
+
" [_formats:html,json,...]" +
|
241
|
+
" [--skip-pagination]" +
|
242
|
+
" [--skip-resourceful]" +
|
243
|
+
" [--skip-formtastic]" +
|
244
|
+
" [--skip-views]" +
|
245
|
+
" [--skip-helpers]" +
|
246
|
+
" [--skip-tests]" +
|
247
|
+
" [--include-layout]"
|
248
|
+
end
|
249
|
+
|
250
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
class <%= controller_class_name %>Controller < InheritedResources::Base
|
2
|
+
|
3
|
+
<% if actions -%>
|
4
|
+
actions <%= symbol_array_to_expression(actions) %>
|
5
|
+
<% end -%>
|
6
|
+
<% if formats -%>
|
7
|
+
respond_to <%= symbol_array_to_expression(formats) %>
|
8
|
+
|
9
|
+
<% end -%>
|
10
|
+
<% if actions -%>
|
11
|
+
before_filter :load_resource, :only => [<%= symbol_array_to_expression(actions & DryScaffoldGenerator::DEFAULT_MEMBER_AUTOLOAD_ACTIONS) %>]
|
12
|
+
<% end -%>
|
13
|
+
<% if actions -%>
|
14
|
+
before_filter :load_and_paginate_resources, :only => [<%= symbol_array_to_expression(actions & DryScaffoldGenerator::DEFAULT_COLLECTION_AUTOLOAD_ACTIONS) %>]
|
15
|
+
|
16
|
+
<% end -%>
|
17
|
+
<% (actions - DryScaffoldGenerator::DEFAULT_CONTROLLER_ACTIONS).each do |action| -%>
|
18
|
+
# GET /<%= plural_name %>/<%= action.to_s %>
|
19
|
+
def <%= action.to_s %>
|
20
|
+
end
|
21
|
+
|
22
|
+
<% end -%>
|
23
|
+
protected
|
24
|
+
|
25
|
+
def collection
|
26
|
+
<% if options[:pagination] -%>
|
27
|
+
paginate_options ||= {}
|
28
|
+
paginate_options[:page] ||= (params[:page] || 1)
|
29
|
+
paginate_options[:per_page] ||= (params[:per_page] || 20)
|
30
|
+
@<%= plural_name %> = @<%= model_plural_name %> ||= end_of_association_chain.paginate(paginate_options)
|
31
|
+
<% else -%>
|
32
|
+
@<%= plural_name %> = @<%= model_plural_name %> ||= end_of_association_chain.all
|
33
|
+
<% end -%>
|
34
|
+
end
|
35
|
+
|
36
|
+
def resource
|
37
|
+
@<%= singular_name %> = @<%= model_singular_name %> ||= end_of_association_chain.find(params[:id])
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|