has_many_through_generator 0.4.0

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 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