has_many_through_generator 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2006 Dr Nic Williams
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 ADDED
@@ -0,0 +1,18 @@
1
+ *******************************************************************************************
2
+ ** For all documentation see the project website: http://hasmanythrough.rubyforge.org **
3
+ *******************************************************************************************
4
+
5
+ Description:
6
+
7
+ Example:
8
+ ./script/generate has_many_throuh User Group
9
+ ./script/generate has_many_throuh User Group Membership
10
+
11
+ Both will generate User and Group models and unit tests (if they don't
12
+ already exist). It will both also generate a third model representing
13
+ the many-to-many relationship between the two, called UserGroup (first
14
+ example and Membership (second example).
15
+
16
+ It will also generate unit tests for the many-to-many class, and all
17
+ unit tests will test the relationships between the other two classes.
18
+
@@ -0,0 +1,330 @@
1
+ class Hash
2
+ # lets through the keys in the argument
3
+ # >> {:one => 1, :two => 2, :three => 3}.pass(:one)
4
+ # => {:one=>1}
5
+ def pass(*keys)
6
+ tmp = self.clone
7
+ keys = keys[0] if keys[0].is_a? Array
8
+ tmp.delete_if {|k,v| ! keys.include?(k) }
9
+ tmp
10
+ end
11
+
12
+ def merge_with_prefix(prefix, hash)
13
+ merged = clone
14
+ hash.each {|key, value| merged.merge!("#{prefix}_#{key}" => value)}
15
+ merged
16
+ end
17
+
18
+ def merge_with_prefix!(prefix, hash)
19
+ hash.each {|key, value| merge!("#{prefix}_#{key}" => value)}
20
+ end
21
+ end
22
+
23
+ class Rails::Generator::Manifest
24
+ attr_reader :actions
25
+
26
+ def copy_actions(manifest)
27
+ @actions.concat(manifest.actions)
28
+ end
29
+ end
30
+
31
+ module Rails::Generator::Commands
32
+ # Create is the premier generator command. It copies files, creates
33
+ # directories, renders templates, and more.
34
+ class Create < Base
35
+ # When creating a migration, it knows to find the first available file in db/migrate and use the migration.rb template.
36
+ def migration_template(relative_source, relative_destination, template_options = {})
37
+ migration_directory relative_destination
38
+ migration_file_name = template_options[:migration_file_name] || file_name
39
+ if not migration_exists?(migration_file_name)
40
+ template(relative_source, "#{relative_destination}/#{next_migration_string}_#{migration_file_name}.rb", template_options)
41
+ else
42
+ puts "Skipping migration #{migration_file_name}, as already exists: #{existing_migrations(migration_file_name).first}" if migration_exists?(migration_file_name)
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ module Rails
49
+ module Generator
50
+ class MultiNamedBase < Rails::Generator::Base
51
+ attr_reader :all_attrs,
52
+ :name, :class_name, :singular_name, :plural_name, :table_name,
53
+ :class_path, :file_path, :class_nesting, :class_nesting_depth
54
+ alias_method :file_name, :singular_name
55
+ attr_reader :controller_name,
56
+ :controller_class_path,
57
+ :controller_file_path,
58
+ :controller_class_nesting,
59
+ :controller_class_nesting_depth,
60
+ :controller_class_name,
61
+ :controller_singular_name,
62
+ :controller_plural_name
63
+ alias_method :controller_file_name, :controller_singular_name
64
+ alias_method :controller_table_name, :controller_plural_name
65
+ #alias_method :base_controller_file_path, :controller_file_path
66
+
67
+ MODEL_ATTRS = %w(name class_name singular_name plural_name
68
+ table_name class_path file_path class_nesting
69
+ class_nesting_depth file_name singular_name)
70
+ CONTROLLER_ATTRS = %w(controller_name controller_class_path
71
+ controller_file_path controller_class_nesting
72
+ controller_class_nesting_depth controller_class_name
73
+ controller_singular_name controller_plural_name)
74
+
75
+ def initialize(runtime_args, runtime_options = {})
76
+ super
77
+
78
+ # Name argument is required.
79
+ usage if runtime_args.empty? or runtime_args.length < 2
80
+
81
+ @all_attrs = {}
82
+
83
+ runtime_args.each do |base_name|
84
+ load_attrs(base_name)
85
+ assign_names!(base_name)
86
+ store_attrs(base_name)
87
+ end
88
+ end
89
+
90
+ protected
91
+ def load_attrs(key)
92
+ all_attrs[key] = {} if not all_attrs[key]
93
+ MODEL_ATTRS.concat(CONTROLLER_ATTRS).each do |attr|
94
+ instance_variable_set "@#{attr.to_s}", all_attrs[key][attr]
95
+ end
96
+ all_attrs[key]
97
+ end
98
+
99
+ def store_attrs(key)
100
+ all_attrs[key] = instance_values.pass(MODEL_ATTRS.concat(CONTROLLER_ATTRS))
101
+ end
102
+
103
+ def assign_names!(name)
104
+ @name = name
105
+ base_name, @class_path, @file_path, @class_nesting, @class_nesting_depth = extract_modules(@name)
106
+ @class_name_without_nesting, @singular_name, @plural_name = inflect_names(base_name)
107
+ @table_name = ActiveRecord::Base.pluralize_table_names ? plural_name : singular_name
108
+ if @class_nesting.empty?
109
+ @class_name = @class_name_without_nesting
110
+ else
111
+ @class_name = "#{@class_nesting}::#{@class_name_without_nesting}"
112
+ end
113
+
114
+ @controller_name ||= ActiveRecord::Base.pluralize_table_names ? @name.pluralize : @name
115
+
116
+ base_name, @controller_class_path, @controller_file_path, @controller_class_nesting, @controller_class_nesting_depth = extract_modules(@controller_name)
117
+ @controller_class_name_without_nesting, @controller_singular_name, @controller_plural_name = inflect_names(base_name)
118
+
119
+ if @controller_class_nesting.empty?
120
+ @controller_class_name = @controller_class_name_without_nesting
121
+ else
122
+ @controller_class_name = "#{@controller_class_nesting}::#{@controller_class_name_without_nesting}"
123
+ end
124
+ end
125
+
126
+ # Extract modules from filesystem-style or ruby-style path:
127
+ # good/fun/stuff
128
+ # Good::Fun::Stuff
129
+ # produce the same results.
130
+ def extract_modules(name)
131
+ modules = name.include?('/') ? name.split('/') : name.split('::')
132
+ name = modules.pop
133
+ path = modules.map { |m| m.underscore }
134
+ file_path = (path + [name.underscore]).join('/')
135
+ nesting = modules.map { |m| m.camelize }.join('::')
136
+ [name, path, file_path, nesting, modules.size]
137
+ end
138
+
139
+ def inflect_names(name)
140
+ camel = name.camelize
141
+ under = camel.underscore
142
+ plural = under.pluralize
143
+ [camel, under, plural]
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ class HasManyThroughGenerator < Rails::Generator::MultiNamedBase
150
+ default_options :skip_migration => false
151
+
152
+ attr_reader :models, :manytomany
153
+
154
+ def initialize(runtime_args, runtime_options = {})
155
+ super
156
+
157
+ @models = runtime_args[0..1].sort
158
+
159
+ if not (@manytomany = runtime_args[2])
160
+ @manytomany = "#{models[0]}#{models[1]}"
161
+ load_attrs(@manytomany)
162
+ assign_names!(@manytomany)
163
+ store_attrs(@manytomany)
164
+ end
165
+
166
+ first_attrs_orig = all_attrs[@models.first].clone
167
+ second_attrs_orig = all_attrs[@models.last].clone
168
+ manytomany_attrs_orig = all_attrs[@manytomany].clone
169
+ all_attrs[@manytomany].merge_with_prefix!("first", first_attrs_orig)
170
+ all_attrs[@manytomany].merge!("first_id_column" => "#{first_attrs_orig['singular_name']}_id")
171
+ all_attrs[@manytomany].merge_with_prefix!("second", second_attrs_orig)
172
+ all_attrs[@manytomany].merge!("second_id_column" => "#{second_attrs_orig['singular_name']}_id")
173
+
174
+ all_attrs[@models.first].merge_with_prefix!("other", second_attrs_orig)
175
+ all_attrs[@models.last].merge_with_prefix!("other", first_attrs_orig)
176
+ @models.each {|model| all_attrs[model].merge_with_prefix!("manytomany", manytomany_attrs_orig)}
177
+
178
+ end
179
+
180
+ def manifest
181
+ record do |m|
182
+ load_attrs(@models.first)
183
+
184
+
185
+ @models.each do |model_name|
186
+ attrs = load_attrs(model_name)
187
+
188
+ # Check for class naming collisions.
189
+ m.class_collisions class_path, class_name, "#{class_name}Test"
190
+
191
+ # Model, test, and fixture directories.
192
+ m.directory File.join('app/models', class_path)
193
+ m.directory File.join('test/unit', class_path)
194
+ m.directory File.join('test/fixtures', class_path)
195
+ m.directory File.join('app/controllers', controller_class_path)
196
+ m.directory File.join('app/helpers', controller_class_path)
197
+ m.directory File.join('app/views/layouts', controller_class_path)
198
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
199
+ m.directory File.join('test/functional', controller_class_path)
200
+ m.directory File.join('public/stylesheets')
201
+
202
+ # Model class, unit test, and fixtures.
203
+ m.template 'model.rb', File.join('app/models', class_path, "#{file_name}.rb"), :assigns => attrs
204
+ m.template 'unit_test.rb', File.join('test/unit', class_path, "#{file_name}_test.rb"), :assigns => attrs
205
+ m.template 'fixtures.yml', File.join('test/fixtures', class_path, "#{table_name}.yml"), :assigns => attrs
206
+
207
+ unless options[:skip_migration]
208
+ m.migration_template 'migration.rb', 'db/migrate', :assigns => attrs.merge({
209
+ :migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"
210
+ }), :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
211
+ end
212
+
213
+ # Controller class, functional test, helper, and views.
214
+ m.template 'controller.rb',
215
+ File.join('app/controllers',
216
+ controller_class_path,
217
+ "#{controller_file_name}_controller.rb"),
218
+ :assigns => attrs
219
+
220
+ m.template 'functional_test.rb',
221
+ File.join('test/functional',
222
+ controller_class_path,
223
+ "#{controller_file_name}_controller_test.rb"),
224
+ :assigns => attrs
225
+
226
+ # Scaffolded partials.
227
+ scaffold_partials.each do |name|
228
+ m.template "partial_#{name}.rhtml",
229
+ File.join('app/views',
230
+ controller_class_path,
231
+ controller_file_name,
232
+ "_#{name}.rhtml"),
233
+ :assigns => attrs
234
+ end
235
+ end
236
+
237
+ attrs = load_attrs(@manytomany)
238
+
239
+ # Check for class naming collisions.
240
+ m.class_collisions class_path, class_name, "#{class_name}Test"
241
+
242
+ # Model, test, and fixture directories.
243
+ m.directory File.join('app/models', class_path)
244
+ m.directory File.join('test/unit', class_path)
245
+ m.directory File.join('test/fixtures', class_path)
246
+ m.directory File.join('app/controllers', controller_class_path)
247
+ m.directory File.join('app/helpers', controller_class_path)
248
+ m.directory File.join('app/views/layouts', controller_class_path)
249
+ m.directory File.join('app/views', controller_class_path, controller_file_name)
250
+ m.directory File.join('test/functional', controller_class_path)
251
+
252
+ # Model class, unit test, and fixtures.
253
+ m.template 'model_manytomany.rb', File.join('app/models', class_path, "#{file_name}.rb"), :assigns => attrs
254
+ m.template 'unit_test_manytomany.rb', File.join('test/unit', class_path, "#{file_name}_test.rb"), :assigns => attrs
255
+ m.template 'fixtures_manytomany.yml', File.join('test/fixtures', class_path, "#{table_name}.yml"), :assigns => attrs
256
+
257
+ unless options[:skip_migration]
258
+ m.migration_template 'migration_manytomany.rb', 'db/migrate',
259
+ :assigns => attrs.merge({:migration_name => "Create#{class_name.pluralize.gsub(/::/, '')}"}),
260
+ :migration_file_name => "create_#{file_path.gsub(/\//, '_').pluralize}"
261
+ end
262
+
263
+ # Controller class, functional test, helper, and views.
264
+ m.template 'controller_manytomany.rb',
265
+ File.join('app/controllers',
266
+ controller_class_path,
267
+ "#{controller_file_name}_controller.rb"),
268
+ :assigns => attrs
269
+ m.template 'functional_test_manytomany.rb',
270
+ File.join('test/functional',
271
+ controller_class_path,
272
+ "#{controller_file_name}_controller_test.rb"),
273
+ :assigns => attrs
274
+
275
+ # Common templates for models and many-to-many model
276
+ @models.concat([@manytomany]).each do |model_name|
277
+ attrs = load_attrs(model_name)
278
+
279
+ m.template 'helper.rb',
280
+ File.join('app/helpers',
281
+ controller_class_path,
282
+ "#{controller_file_name}_helper.rb"),
283
+ :assigns => attrs
284
+
285
+ # Scaffolded views.
286
+ scaffold_views.each do |action|
287
+ m.template "view_#{action}.rhtml",
288
+ File.join('app/views',
289
+ controller_class_path,
290
+ controller_file_name,
291
+ "#{action}.rhtml"),
292
+ :assigns => attrs.merge({ :action => action })
293
+ end
294
+ # Layout and stylesheet.
295
+ m.template 'layout.rhtml',
296
+ File.join('app/views/layouts',
297
+ controller_class_path,
298
+ "#{controller_file_name}.rhtml"),
299
+ :assigns => attrs
300
+
301
+ m.template 'stylesheet.css',
302
+ File.join('public/stylesheets',
303
+ controller_class_path,
304
+ "#{controller_file_name}.css"),
305
+ :assigns => attrs
306
+ end
307
+ end
308
+ end
309
+
310
+ protected
311
+ def banner
312
+ "Usage: #{$0} has_many_through ModelName1 ModelName2 [ManyToManyModelName]"
313
+ end
314
+
315
+ def scaffold_views
316
+ %w( index )
317
+ end
318
+
319
+ def scaffold_partials
320
+ %w( form )
321
+ end
322
+
323
+ def add_options!(opt)
324
+ opt.separator ''
325
+ opt.separator 'Options:'
326
+ opt.on("--skip-migration",
327
+ "Don't generate a migration file for this model") { |v| options[:skip_migration] = v }
328
+ end
329
+
330
+ end
@@ -0,0 +1,116 @@
1
+ class <%= controller_class_name %>Controller < ApplicationController
2
+
3
+ def index
4
+ end
5
+
6
+ def new
7
+ @<%= singular_name %> = <%= class_name %>.new
8
+ @successful = true
9
+
10
+ respond_to do |type|
11
+ type.html do
12
+ if @successful
13
+ @options = { :action => "create" }
14
+ render :partial => "form", :layout => true
15
+ else
16
+ redirect_to_main
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ def create
23
+ begin
24
+ @<%= singular_name %> = <%= class_name %>.new(params[:<%= singular_name %>])
25
+ @successful = @<%= singular_name %>.save
26
+ rescue
27
+ flash[:error], @successful = $!.to_s, false
28
+ end
29
+
30
+ flash[:info] = "<%= class_name %> created" if @successful
31
+
32
+ respond_to do |type|
33
+ type.html do
34
+ if not @successful
35
+ @options = { :action => "create" }
36
+ render :partial => "form", :layout => true
37
+ else
38
+ redirect_to_main
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def edit
45
+ begin
46
+ @<%= singular_name %> = <%= class_name %>.find(params[:id])
47
+ @successful = !@<%= singular_name %>.nil?
48
+ rescue
49
+ flash[:error], @successful = $!.to_s, false
50
+ end
51
+
52
+ respond_to do |type|
53
+ type.html do
54
+ if @successful
55
+ @options = { :action => "update", :id => params[:id] }
56
+ render :partial => "form", :layout => true
57
+ else
58
+ redirect_to_main
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ def update
65
+ begin
66
+ @<%= singular_name %> = <%= class_name %>.find(params[:id])
67
+ @successful = @<%= singular_name %>.update_attributes(params[:<%= singular_name %>])
68
+ rescue
69
+ flash[:error], @successful = $!.to_s, false
70
+ end
71
+
72
+ flash[:info] = "<%= class_name %> updated" if @successful
73
+
74
+ respond_to do |type|
75
+ type.html do
76
+ if not @successful
77
+ @options = { :action => "update", :id => params[:id] }
78
+ render :partial => "form", :layout => true
79
+ else
80
+ redirect_to_main
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ def destroy
87
+ begin
88
+ @successful = <%= class_name %>.find(params[:id]).destroy
89
+ rescue
90
+ flash[:error], @successful = $!.to_s, false
91
+ end
92
+
93
+ flash[:info] = "<%= class_name %> deleted" if @successful
94
+
95
+ respond_to do |type|
96
+ type.html {return redirect_to_main}
97
+ end
98
+ end
99
+
100
+ def cancel
101
+ @successful = true
102
+
103
+ respond_to do |type|
104
+ type.html {return redirect_to_main}
105
+ end
106
+ end
107
+
108
+ protected
109
+ def redirect_to_main
110
+ redirect_to common_redirection
111
+ end
112
+
113
+ def common_redirection
114
+ { :action => 'index' }
115
+ end
116
+ end