engineer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009-2010 Phil Smith
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.rdoc ADDED
@@ -0,0 +1,336 @@
1
+ = Engineer
2
+
3
+ It makes engines, get it?
4
+
5
+ == Explanation
6
+
7
+ Engines are rails apps which can be run from inside other rails apps.
8
+
9
+ Engineer is a small gem that lets a sufficiently normal rails application quickly become an
10
+ engine. It provides necessary bits of engine anatomy, and also gives the engine author a way to
11
+ publish changes (such as migrations) to users as the engine evolves. This has been a sticking
12
+ point in the past, and is one of the few remaining areas of engine support not yet covered by
13
+ rails itself.
14
+
15
+ Engineer targets rails 3 engines hosted inside rails 3 applications. If you are looking for rails
16
+ 2.3 support, try the engines plugin (http://rails-engines.org).
17
+
18
+ == Getting Started
19
+
20
+ Let's say you have a new rails app named +my_engine+ that you wish to package as an engine. Drop
21
+ this in your +Gemfile+ and do the bundler thing:
22
+
23
+ <tt>gem "engineer"</tt>
24
+
25
+ A new generator named <tt>engineer:install</tt> will be available; run it.
26
+ $ rails g engineer:install
27
+ exist lib
28
+ create lib/my_engine/engine.rb
29
+ create lib/my_engine.rb
30
+ create lib/generators/my_engine/install/install_generator.rb
31
+ create lib/generators/my_engine/install/templates/my_engine.rake
32
+ create lib/generators/my_engine/install/USAGE
33
+ create app/controllers/my_engine
34
+ create app/controllers/my_engine/application_controller.rb
35
+ remove app/controllers/application_controller.rb
36
+ append Rakefile
37
+ gsub config/routes.rb
38
+
39
+ The two major take-aways from this are
40
+ 1. <tt>application_controller.rb</tt> has moved under +my_engine+.
41
+ 2. Your +Rakefile+ has grown a bit.
42
+
43
+ The Gory Details below explain more deeply, but for now let's just look at the new +Rakefile+
44
+ content:
45
+ $ cat Rakefile
46
+ # ...
47
+
48
+ Engineer::Tasks.new do |gem|
49
+ gem.name = "my_engine"
50
+ gem.summary = %Q{TODO: one-line summary of your engine}
51
+ gem.description = %Q{TODO: longer description of your engine}
52
+ gem.email = "TODO"
53
+ gem.homepage = "TODO"
54
+ gem.authors = ["TODO"]
55
+ gem.require_path = 'lib'
56
+ gem.files = FileList[
57
+ "[A-Z]*",
58
+ "{app,config,lib,public,spec,test,vendor}/**/*",
59
+ "db/**/*.rb"
60
+ ]
61
+
62
+ # Include Bundler dependencies
63
+ Bundler.definition.dependencies.each do |dependency|
64
+ next if dependency.name == "engineer"
65
+
66
+ if (dependency.groups & [:default, :production, :staging]).any?
67
+ gem.add_dependency dependency.name, *dependency.requirement.as_list
68
+ else
69
+ gem.add_development_dependency dependency.name, *dependency.requirement.as_list
70
+ end
71
+ end
72
+
73
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
74
+ end
75
+
76
+ If you've used jeweler (http://github.com/technicalpickles/jeweler) before, this should look
77
+ eerily familiar. Engineer engines are shipped as gems, so engineer throws a (very light) wrapper
78
+ around jeweler for wrangling the gem-related tasks. Jeweler in turn keeps your gem's metadata in
79
+ the +Rakefile+, so here it is.
80
+
81
+ <b>Unlike jeweler, dependencies should still be declared in +Gemfile+, and not here.</b>
82
+
83
+ As you can see, your bundler dependencies will be included in the generated gemspec.
84
+
85
+ Let's make a gem:
86
+ $ rake build
87
+ (in /Users/phil/Public/code/my_engine)
88
+ Expected VERSION or VERSION.yml to exist. See version:write to create an initial one.
89
+
90
+ Whoops. Our gem needs to know what version it is, and we haven't told it. Start off at 0.0.0 by
91
+ $ rake version:write
92
+ (in /Users/phil/Public/code/my_engine)
93
+ Updated version: 0.0.0
94
+
95
+ $ rake build
96
+ (in /Users/phil/Public/code/my_engine)
97
+ Generated: my_engine.gemspec
98
+ my_engine.gemspec is valid.
99
+ rake aborted!
100
+ "FIXME" or "TODO" is not an author
101
+
102
+ (See full trace by running task with --trace)
103
+
104
+ Doh. Remember all those TODOs (summary, author, etc) in the Rakefile metadata? Go fill those out.
105
+
106
+ Once more:
107
+ $ rake build
108
+ (in /Users/phil/Public/code/my_engine)
109
+ Generated: my_engine.gemspec
110
+ my_engine.gemspec is valid.
111
+ WARNING: no rubyforge_project specified
112
+ Successfully built RubyGem
113
+ Name: my_engine
114
+ Version: 0.0.0
115
+ File: my_engine-0.0.0.gem
116
+
117
+ There we go, there's an engine gem sitting in <tt>pkg/</tt>, go nuts. See jeweler's documentation
118
+ for managing the version, pushing to gemcutter and other goodies.
119
+
120
+ == Installing Engine Gems
121
+
122
+ How about the other side of the fence? To install an engine into a host application, the host
123
+ author follows a similar workflow.
124
+
125
+ First, add a line to the +Gemfile+ and call bundler:
126
+
127
+ <tt>gem "my_engine"</tt>
128
+
129
+ A new generator will be available, named <tt>my_engine:install</tt>; run it.
130
+ $ rails g my_engine:install
131
+ exist lib/tasks
132
+ create lib/tasks/my_engine.rake
133
+ rake my_engine:assets my_engine:db:schema my_engine:db:migrate
134
+ rm -rf /.../host/public/my_engine
135
+ ln -s /.../gems/my_engine-0.0.0/public /.../host/public/my_engine
136
+ mkdir -p db/migrate
137
+ cp /tmp/20100428232715_my_engine_schema_after_create_comments.rb /.../host/db/migrate/20100428232715_my_engine_schema_after_create_comments.rb
138
+
139
+ This includes the engine's static assets in a subdirectory under the host's +public+. Voodoo in
140
+ the engine takes care of looking there for assets (see Gory Details below.) If your OS is
141
+ allergic to symlinks, the files are copied instead.
142
+
143
+ The engine's schema is also added as a new database migration without running it. The host author
144
+ is free to take a peek at it before deciding to <tt>rake db:migrate</tt> for real.
145
+
146
+ Run your pending migrations and fire up <tt>rails s</tt>, you're good to go.
147
+
148
+ == Managing Engine Gems
149
+
150
+ After installation, some new rake tasks are available:
151
+ $ rake -T my_engine
152
+ (in /Users/phil/Public/code/host)
153
+ rake my_engine:assets[copy] # Link (or copy) my_engine's static assets
154
+ rake my_engine:db:migrate # Import my_engine's new db migrations
155
+ rake my_engine:db:schema # Import my_engine's schema as a db migration
156
+ rake my_engine:db:seed # Load my_engine's seed data
157
+ rake my_engine:update # Import my_engine's assets and new db migrations
158
+
159
+ There are catch-all tasks as well:
160
+ $ rake -T engines
161
+ (in /Users/phil/Public/code/host)
162
+ rake engines:assets[copy] # Link (or copy) static assets from all engines
163
+ rake engines:db:migrate # Import new migrations from all engines
164
+ rake engines:db:seed # Load seed data from all engines
165
+ rake engines:update # Import assets and new db migrations from all engines
166
+
167
+ These let the host author manage the newly-installed (or updated!) engine. If the host
168
+ application revs the version of the engine gem, any new engine db migrations can be imported into
169
+ the host app with:
170
+ $ rake my_engine:db:migrate
171
+ (in /Users/phil/Public/code/host)
172
+ mkdir -p db/migrate
173
+ cp /tmp/20100428232715_my_engine_create_tags.rb /.../host/db/migrate/20100428232715_my_engine_create_tags.rb
174
+ cp /tmp/20100428232716_my_engine_create_taggings.rb /.../host/db/migrate/20100428232716_my_engine_create_taggings.rb
175
+
176
+ As before, this doesn't actually run any engine migrations but instead copies new ones (with mild
177
+ munging) to <tt>db/migrate</tt>.
178
+
179
+ The engine's static assets (stylesheets, images and so on) can be updated with:
180
+ $ rake my_engine:assets
181
+ (in /Users/phil/Public/code/host)
182
+ rm -rf /Users/phil/Public/code/host/public/my_engine
183
+ ln -s /.../gems/my_engine-0.0.1/public /.../host/public/my_engine
184
+
185
+ Even when using soft-links, updating the assets is important: you need the symlink pointing into
186
+ the correct gem version.
187
+
188
+ One can do both in a single shot with <tt>rake my_engine:update</tt>. Or with
189
+ <tt>rake engines:update</tt> to hit all engines at once.
190
+
191
+ == Gory Details
192
+
193
+ Rails' engine support has become robust with the release of rails 3. There are still a few pain
194
+ points which engineer tries to address. It does so by making some decisions for you, the engine
195
+ author. In the spirit of openness, the introduced voodoo is not buried inside the engineer gem
196
+ but copied into the engine app on installation. It's your gem, feel free to tailor it to your
197
+ needs.
198
+
199
+ Some intrepid adventurers will ask for an explanation of the generated engine's internals; here is
200
+ an overview.
201
+
202
+ === Database Migrations
203
+
204
+ Engine database migrations are a tricky problem. Luckily for me, some very clever people already
205
+ hammered out a workable idea, which engineer implements.
206
+
207
+ The migrations are packaged in the gem along with the rest of the engine. When the host author
208
+ updates the gem, she runs the <tt><engine_name>:db:migrate</tt> rake task (directly, or indirectly
209
+ through <tt><engine_name>:update</tt>.) This copies the engine migrations into the host
210
+ application, changing a few things on the way.
211
+
212
+ Specifically:
213
+ 1. The migration numbers are reassigned.
214
+ 2. The engine name is inserted into the new migration name.
215
+
216
+ The numbers are reassigned so that the copied migrations preserve their relative order, and yet
217
+ occur after all previous migrations in the host application. This guarantees that the host
218
+ application has a linear schema history, by making that history explicit. Put another way, the
219
+ final host migrations should reflect the evolution of the _host_ schema, not the _engine_ schema
220
+ from which it was derived.
221
+
222
+ The engine name is inserted to avoid name collisions. It allows the engine to see what migrations
223
+ have already been copied into the host app; it will not attempt to copy them again. Changing the
224
+ migration name implies changing the contained migration class name as well: the rake task will
225
+ normally take care of it.
226
+
227
+ Interested readers could also check out
228
+ * The original discussion: https://rails.lighthouseapp.com/projects/8994/tickets/2058
229
+ * James Adam's related blog post: http://interblah.net/plugin-migrations
230
+
231
+ === Schemas
232
+
233
+ It is recommended practice to avoid running a long string of migrations when setting up a new
234
+ database, since the migration process can become slow and brittle. Analogously, engines should be
235
+ able to (and can) create their schemas in the host database without running a long string of
236
+ migrations.
237
+
238
+ It is a very real use case for a host application author to add a new engine after the host schema
239
+ has been created and deployed to production. Deployment tools like cap and vlad know how to run
240
+ pending migrations at the right time, but they don't understand (out of the box) how to run an
241
+ engine-specific <tt>db:schema:load</tt>. It would be really great if they didn't have to.
242
+
243
+ Engineer engines satisfy these two requirements by importing the engine schema into the host
244
+ application as a migration. A naming convention is used to identify engine migrations that are
245
+ implied by the schema: the schema migration will be named something like
246
+ +my_engine_schema_after_create_posts+. This indicates that all engine migrations before (and
247
+ including) +create_posts+ should be considered to be already run. If there are no engine
248
+ migrations to skip (because the engine author removed them) then the name +my_engine_schema+ is
249
+ used instead.
250
+
251
+ No effort is made to make this schema migration reversible.
252
+
253
+ === Seeding
254
+
255
+ Similar to loading a schema, seeding initial database rows is another task that should only be run
256
+ once per database.
257
+
258
+ Engineer engines provide two ways to load the engine's <tt>db/seeds.rb</tt>. The first is a rake
259
+ task, which the host-level <tt>rake db:seed</tt> depends on. This is meant for new databases
260
+ being set up after the engine's schema has been incorporated into the host's. All seed rows will
261
+ be loaded together at the usual time, just as all the tables were created together.
262
+
263
+ The other way seed data can be loaded is in a migration generated on engine installation. This is
264
+ appropriate for installing a new engine into an established application, such as in a production
265
+ deploy.
266
+
267
+ === Static Assets and Asset Helpers
268
+
269
+ Separation of assets is another issue facing engines. While it is certainly useful to harness the
270
+ host's layouts and styling, engines will inescapably need their own stylesheets, scripts, etc.
271
+
272
+ Rails provides conventions about where those assets live and what they are named, and backs those
273
+ conventions up with helpers that "just work." The problem is (for example), the host and engine
274
+ master stylesheets can't both live at <tt>public/stylesheets/application.css</tt>. So we need two
275
+ separate places to keep assets, and a way of hiding this separation when it is convenient.
276
+
277
+ Many http servers serve static assets more efficiently than dynamic ones generated from frameworks
278
+ like rails. Any serious solution to the problem will need to respect this optimization. So,
279
+ assuming the web server wants to be dumb, the separate asset locations will be apparent in the
280
+ URIs. The browser only fetches the URIs the application gives it, so the application must know to
281
+ render engine asset URIs for engine assets, and host asset URIs otherwise. These URIs are created
282
+ by asset tag helpers such as +image_tag+ and +stylesheet_link_tag+.
283
+
284
+ An engine author could visit each tag and stick (for example) <tt>my_engine/</tt> in front of the
285
+ asset names, but that stinks. It also breaks the engine when run as a normal application.
286
+ Instead, the asset helpers called by the engine must create engine asset URIs only when the engine
287
+ is run as such.
288
+
289
+ Enter +asset_path+. This is an +action_controller+ configuration hook provided by rails to
290
+ control how asset URIs are generated. It can be set on a controller class, and it is inherited by
291
+ subclasses. By default, it provides rails' cache-busting ability (the <tt>?123line-noise456</tt>
292
+ on the end of your stylesheet URIs.) Engineer hijacks it to provide asset separation.
293
+
294
+ Recall that engineer's install generator moves <tt>application_controller.rb</tt> into an
295
+ engine-specific namespace: this is why. On startup, a <tt>MyEngine::Engine</tt> initializer sets
296
+ an +asset_path+ on <tt>MyEngine::ApplicationController</tt>. Since this controller is no longer
297
+ the global +ApplicationController+, we're ensured this +asset_path+ will affect not only all the
298
+ engine controllers, but only them. The initializers in <tt>MyEngine::Engine</tt> are only run
299
+ when the application is started as an engine. Thus when +my_engine+ is fired up as a normal
300
+ application, the custom +asset_path+ is not used.
301
+
302
+ Almost there. +asset_path+ can take as value either a string template (such as
303
+ <tt>"/my_engine%s"</tt>) or a lambda. If the engine and host authors were not interested in
304
+ sharing layouts and other views, <tt>"/my_engine%s"</tt> would be enough. There, all asset tags
305
+ rendered by any view from an engine controller will target the engine's assets. This goes bad
306
+ when an engine view wants to render with a host layout that includes a stylesheet: the stylesheet
307
+ URI would point into the engine, not the host.
308
+
309
+ A little more (and arguably too much) leg work can save us. The flaw is that we want the
310
+ customized URI not when requesting an engine _controller_, but when rendering an engine _view_.
311
+ If you have one around, open up <tt>lib/my_engine/engine.rb</tt>. There is another initializer in
312
+ there that duck punches <tt>ActionView::Template#render</tt>. When a template is rendered, its
313
+ +identifier+ is captured. For templates loaded from files, this is a file system path descending
314
+ from a <tt>config.paths.app.views</tt> of either the host or the engine. Thus we can distinguish
315
+ host templates from engine templates. Engine assets are generated for engine views and non-file
316
+ system views.
317
+
318
+ == Running the Tests
319
+
320
+ The (sparse) tests are written with cucumber (http://cukes.info) and can be run with just "rake".
321
+ You may need to install jeweler first.
322
+
323
+ == Thanks
324
+
325
+ This tool would not exist if the road had not already been well-paved by many others.
326
+ Specifically, James Adam's herculean effort in the rails 2.3 engines plugin has illuminated many
327
+ issues and solutions. Thanks also to the rails core team for incorporating many engine-centric
328
+ ideas into the framework, vastly simplifying what this tool needs to do. And also for just making
329
+ an awesome framework.
330
+
331
+ Finally, thanks to SEOmoz (http://seomoz.org) for letting me build this at my desk in the
332
+ afternoons instead of on the couch in the middle of the night ^_^.
333
+
334
+ == Copyright
335
+
336
+ Copyright (c) 2009-2010 Philip Smith. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,62 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "engineer"
8
+ gem.summary = %Q{Turn rails 3 applications into engines}
9
+ gem.description = "Turn your rails 3 app into an embeddable engine gem, with answers for db "\
10
+ "migrations, static assets and more."
11
+ gem.email = "phil.h.smith@gmail.com"
12
+ gem.homepage = "http://github.com/phs/engineer"
13
+ gem.authors = ["Phil Smith"]
14
+ gem.files = FileList["[A-Z]*", "{features,lib,spec}/**/*"]
15
+ gem.add_dependency "jeweler", ">= 1.4.0"
16
+ gem.add_development_dependency "rspec", ">= 1.2.9"
17
+ gem.add_development_dependency "cucumber", ">= 0.6.4"
18
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
19
+ end
20
+ Jeweler::GemcutterTasks.new
21
+ rescue LoadError
22
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
23
+ end
24
+
25
+ require 'spec/rake/spectask'
26
+ Spec::Rake::SpecTask.new(:spec) do |spec|
27
+ spec.libs << 'lib' << 'spec'
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ end
30
+
31
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
32
+ spec.libs << 'lib' << 'spec'
33
+ spec.pattern = 'spec/**/*_spec.rb'
34
+ spec.rcov = true
35
+ end
36
+
37
+ task :spec => :check_dependencies
38
+
39
+ task :default => [:spec, :cucumber]
40
+
41
+ require 'rake/rdoctask'
42
+ Rake::RDocTask.new do |rdoc|
43
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
44
+
45
+ rdoc.rdoc_dir = 'rdoc'
46
+ rdoc.title = "engineer #{version}"
47
+ rdoc.rdoc_files.include('README*')
48
+ rdoc.rdoc_files.include('lib/**/*.rb')
49
+ end
50
+
51
+ require 'cucumber/rake/task'
52
+ Cucumber::Rake::Task.new do |t|
53
+ t.cucumber_opts = %w{--format pretty}
54
+ end
55
+
56
+ desc "Remove build products"
57
+ task :clean do
58
+ rm_rf 'pkg'
59
+ end
60
+
61
+ # Some steps expect the gem to be built, so it can be added to rails projects created in tests.
62
+ task :cucumber => [:clean, :build]
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,55 @@
1
+ Feature: Rake Tasks added to Engines
2
+ As an engine author
3
+ I want to pack my app as a gem the same way I do with jeweler
4
+ Since I probably already know how to use jeweler
5
+
6
+ Background:
7
+ Given I have a new rails app named my_engine, with the engineer gem
8
+ And I rails g engineer:install
9
+
10
+ Scenario: Creating initial VERSION file
11
+ When I try to rake version:bump:patch
12
+ Then I should see output:
13
+ """
14
+ Expected VERSION or VERSION.yml to exist. See version:write to create an initial one.
15
+ """
16
+
17
+ When I rake version:write
18
+ Then VERSION should contain:
19
+ """
20
+ 0.0.0
21
+ """
22
+
23
+ Scenario: Bumping versions
24
+ Given I rake version:write
25
+ When I rake version:bump:patch
26
+ Then I should see output:
27
+ """
28
+ Current version: 0.0.0
29
+ Updated version: 0.0.1
30
+ """
31
+
32
+ When I rake version:bump:major
33
+ Then I should see output:
34
+ """
35
+ Current version: 0.0.1
36
+ Updated version: 1.0.0
37
+ """
38
+
39
+ Scenario: Building a gem
40
+ Given I rake version:write
41
+ And I fill out my Rakefile gemspec
42
+ When I rake build
43
+ Then I should see a my_engine.gemspec file
44
+ And I should see a pkg/my_engine-0.0.0.gem file
45
+
46
+ And my_engine.gemspec should contain:
47
+ """
48
+ s.add_runtime_dependency(%q<rails>, ["${VERSION}"])
49
+ s.add_runtime_dependency(%q<sqlite3-ruby>, ["${VERSION}"])
50
+ """
51
+
52
+ And my_engine.gemspec should not contain:
53
+ """
54
+ s.add_runtime_dependency(%q<engineer>, ["${VERSION}"])
55
+ """
@@ -0,0 +1,47 @@
1
+ Feature: Engineer Installation
2
+ As an engine author
3
+ I want to install engineer painlessly
4
+ So that I can turn my rails app into an embeddable engine
5
+
6
+ Background:
7
+ Given I have a new rails app named my_engine, with the engineer gem
8
+
9
+ Scenario: Install engineer, see my new rake tasks
10
+ When I rails g engineer:install
11
+ Then I should see output:
12
+ """
13
+ exist lib
14
+ create lib/my_engine/engine.rb
15
+ create lib/my_engine.rb
16
+ create lib/generators/my_engine/install/install_generator.rb
17
+ create lib/generators/my_engine/install/templates/my_engine.rake
18
+ create lib/generators/my_engine/install/USAGE
19
+ create app/controllers/my_engine
20
+ create app/controllers/my_engine/application_controller.rb
21
+ remove app/controllers/application_controller.rb
22
+ append Rakefile
23
+ gsub config/routes.rb
24
+ """
25
+
26
+ And config/routes.rb should contain:
27
+ """
28
+ Rails.application.routes.draw
29
+ """
30
+
31
+ And app/controllers/my_engine/application_controller.rb should contain:
32
+ """
33
+ class MyEngine::ApplicationController
34
+ """
35
+
36
+ When I rake -T
37
+ Then I should see output:
38
+ """
39
+ rake build # Build gem
40
+ """
41
+ And I should see output:
42
+ """
43
+ rake version:bump:major # Bump the gemspec by a major version.
44
+ rake version:bump:minor # Bump the gemspec by a minor version.
45
+ rake version:bump:patch # Bump the gemspec by a patch version.
46
+ rake version:write # Writes out an explicit version.
47
+ """
@@ -0,0 +1,54 @@
1
+ Feature: Engine Installation into a Host App
2
+ As an host application author
3
+ I want to install an engine painlessly
4
+ So that I can use it in my app
5
+
6
+ Background:
7
+ Given I have a finished engine application named my_engine
8
+ And I have a new rails app named host, with the my_engine gem
9
+
10
+ Scenario: Install my_engine
11
+ When I rails g --help
12
+ Then I should see output:
13
+ """
14
+ my_engine:install
15
+ """
16
+
17
+ When I rails g my_engine:install
18
+ Then I should see output:
19
+ """
20
+ exist lib/tasks
21
+ create lib/tasks/my_engine.rake
22
+ rake my_engine:install
23
+ """
24
+
25
+ And I should see output:
26
+ """
27
+ rm -rf /${HOST_PATH}/host/public/my_engine
28
+ ln -s /${GEM_REPO}/gems/my_engine-0.0.0/public /${HOST_PATH}/host/public/my_engine
29
+ """
30
+
31
+ When I rake -T
32
+ Then I should see output:
33
+ """
34
+ rake my_engine:assets[copy] # Link (or copy) my_engine's static assets
35
+ rake my_engine:db:migrate # Import my_engine's new db migrations
36
+ rake my_engine:db:schema # Import my_engine's schema as a db migration
37
+ rake my_engine:db:seed # Load my_engine's seed data
38
+ rake my_engine:update # Import my_engine's assets and new db migrations
39
+ """
40
+
41
+ And I should see output:
42
+ """
43
+ rake engines:assets[copy] # Link (or copy) static assets from all engines
44
+ rake engines:db:migrate # Import new migrations from all engines
45
+ rake engines:db:seed # Load seed data from all engines
46
+ rake engines:update # Import assets and new db migrations from all engines
47
+ """
48
+
49
+ When I rake my_engine:assets[true]
50
+ Then I should see output:
51
+ """
52
+ rm -rf /${HOST_PATH}/host/public/my_engine
53
+ cp -r /${GEM_REPO}/gems/my_engine-0.0.0/public /${HOST_PATH}/host/public/my_engine
54
+ """
@@ -0,0 +1,153 @@
1
+ # Inspiration shamelessly stolen from
2
+ # http://gravityblast.com/2009/08/11/testing-rails-generators-with-cucumber/
3
+
4
+ require "rubygems"
5
+ gem "railties", ">= 3.0.0.beta2"
6
+
7
+ require "fileutils"
8
+ require "tempfile"
9
+
10
+ class String
11
+ def strip_ansi
12
+ gsub /\e\[\d+m/, ''
13
+ end
14
+ end
15
+
16
+ PROJECT_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..', '..'))
17
+ ENGINEER_GEM_FILE = Dir["#{PROJECT_ROOT}/pkg/engineer*gem"].first
18
+
19
+ if ENV['VERBOSE'] == 'true'
20
+ include FileUtils::Verbose
21
+ else
22
+ include FileUtils
23
+ end
24
+
25
+ module Helpers
26
+ def generate_rails_app(name = 'rails_app', gem_files = [])
27
+ @current_app = name
28
+
29
+ unless File.exists?(in_workspace "apps", name)
30
+ in_workspace "apps" do
31
+ run "rails #{name}"
32
+ end
33
+
34
+ in_workspace "apps", @current_app do
35
+ gem_files.each do |gem_file|
36
+ gem_name, version = File.basename(gem_file).match(/(.*)-(\d+\.\d+\.\d+)\.gem/)[1..2]
37
+ install_gem gem_file
38
+ File.open('Gemfile', 'a') do |gemfile|
39
+ gemfile << "gem '#{gem_name}', '#{version}'\n"
40
+ end
41
+ end
42
+
43
+ run "#{gem_home} bundle install"
44
+ end
45
+ end
46
+
47
+ cp_r in_workspace("apps", @current_app), in_current_app
48
+ end
49
+
50
+ def generate(generator, options = {})
51
+ in_current_app do
52
+ run "#{gem_home} bundle exec rails g #{generator}", options
53
+ end
54
+ end
55
+
56
+ def rake(rake_task, options = {})
57
+ in_current_app do
58
+ run "#{gem_home} bundle exec rake #{rake_task}", options
59
+ end
60
+ end
61
+
62
+ def fill_out_the_rakefile_gemspec
63
+ in_current_app do
64
+ rakefile = File.read('Rakefile')
65
+ rakefile.gsub!(
66
+ '%Q{TODO: one-line summary of your engine}', '%Q{My awesome engine}')
67
+ rakefile.gsub!(
68
+ '%Q{TODO: longer description of your engine}', '%Q{My awesome, beautiful engine}')
69
+ rakefile.gsub!('gem.email = "TODO"', 'gem.email = "me@example.com"')
70
+ rakefile.gsub!('gem.homepage = "TODO"', 'gem.homepage = "http://example.com/"')
71
+ rakefile.gsub!('gem.authors = ["TODO"]', 'gem.authors = ["Me"]')
72
+ File.open('Rakefile', 'w') { |f| f << rakefile }
73
+ end
74
+ end
75
+
76
+ def strip_wildcards(content)
77
+ Regexp.quote(content).gsub /\\\$\\\{[A-Z_]+\\\}/, '.*' # escaped ${WHAT_EVER} becomes .*
78
+ end
79
+
80
+ def latest_output
81
+ @latest_output
82
+ end
83
+
84
+ private
85
+
86
+ def install_gem(gem_file)
87
+ @installed_gems ||= {}
88
+ @installed_gems[gem_file] ||= begin
89
+ run "#{gem_home} gem install --no-rdoc --no-ri #{gem_file}"
90
+ true
91
+ end
92
+ end
93
+
94
+ def run(cmd, options = {})
95
+ log cmd
96
+ (`#{cmd} 2>&1`).tap do |output|
97
+ @latest_output = output
98
+ log output
99
+ fail unless $?.to_i == 0 or options[:may_fail]
100
+ end
101
+ end
102
+
103
+ def in_workspace(*path)
104
+ absolute_path = File.join(WORKSPACE, *path)
105
+
106
+ if block_given?
107
+ Dir.chdir(absolute_path) do
108
+ log "cd #{absolute_path}"
109
+ yield
110
+ end
111
+ else
112
+ absolute_path
113
+ end
114
+ end
115
+
116
+ def in_current_scenario(*path, &block)
117
+ in_workspace "current_scenario", *path, &block
118
+ end
119
+
120
+ def in_current_app(*path, &block)
121
+ in_current_scenario @current_app, *path, &block
122
+ end
123
+
124
+ def gem_home
125
+ @gem_home ||= begin
126
+ repo_path = in_workspace 'gemrepo'
127
+ mkdir_p repo_path
128
+ "GEM_HOME='#{repo_path}'"
129
+ end
130
+ end
131
+
132
+ def log(message)
133
+ puts message if ENV['VERBOSE'] == 'true'
134
+ end
135
+
136
+ end
137
+
138
+ # TODO join from Dir::tmpdir after https://rails.lighthouseapp.com/projects/8994/tickets/4442
139
+ WORKSPACE = File.join("/tmp", "engineer-cucumber-#{$$}").tap do |tmpdir|
140
+ mkdir_p tmpdir
141
+ mkdir_p File.join(tmpdir, 'apps')
142
+ at_exit { rm_rf tmpdir }
143
+ end
144
+
145
+ Before do
146
+ mkdir_p in_current_scenario
147
+ end
148
+
149
+ After do
150
+ rm_rf in_current_scenario
151
+ end
152
+
153
+ World(Helpers)
@@ -0,0 +1,58 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/env')
2
+
3
+ Given %r{I have a new rails app named (.*), with the (.*) gems?} do |name, gems|
4
+ gem_files = gems.split(',').collect do |gem_name|
5
+ gem_name.strip!
6
+ if gem_name == 'engineer'
7
+ ENGINEER_GEM_FILE
8
+ else
9
+ Dir["#{in_current_scenario(gem_name)}/pkg/#{gem_name}-*.gem"].first
10
+ end
11
+ end
12
+
13
+ generate_rails_app name, gem_files
14
+ end
15
+
16
+ When "I rails g $generator" do |generator|
17
+ generate generator
18
+ end
19
+
20
+ When "I try to rails g $generator" do |generator|
21
+ generate generator, :may_fail => true
22
+ end
23
+
24
+ When "I rake $rake_task" do |rake_task|
25
+ rake rake_task
26
+ end
27
+
28
+ When "I try to rake $rake_task" do |rake_task|
29
+ rake rake_task, :may_fail => true
30
+ end
31
+
32
+ When "I fill out my Rakefile gemspec" do
33
+ fill_out_the_rakefile_gemspec
34
+ end
35
+
36
+ Then "I should see output:" do |command_output|
37
+ latest_output.strip_ansi.strip.should match strip_wildcards(command_output.strip)
38
+ end
39
+
40
+ Then "$file should contain:" do |file, content|
41
+ File.read(in_current_app file).should match strip_wildcards(content)
42
+ end
43
+
44
+ Then "$file should not contain:" do |file, content|
45
+ File.read(in_current_app file).should_not match strip_wildcards(content)
46
+ end
47
+
48
+ Then "I should see a $file file" do |file|
49
+ File.exists?(in_current_app file).should be_true
50
+ end
51
+
52
+ Given "I have a finished engine application named $engine" do |engine|
53
+ Given "I have a new rails app named #{engine}, with the engineer gem"
54
+ And "I rails g engineer:install"
55
+ And "I rake version:write"
56
+ And "I fill out my Rakefile gemspec"
57
+ And "I rake build"
58
+ end
@@ -0,0 +1,4 @@
1
+ class Engineer
2
+ class Tasks < ::Jeweler::Tasks
3
+ end
4
+ end
data/lib/engineer.rb ADDED
@@ -0,0 +1,5 @@
1
+ require 'jeweler'
2
+
3
+ class Engineer < Jeweler
4
+ autoload :Tasks, 'engineer/tasks'
5
+ end
@@ -0,0 +1,10 @@
1
+ Description:
2
+ Transform the application into a gem-based engine.
3
+
4
+ The major steps are:
5
+ * creating an Engine subclass under lib/
6
+ * moving ApplicationController into a submodule
7
+ * creating an installation generator for host apps
8
+ * adding jeweler-style gem tasks to the Rakefile
9
+
10
+ Please see http://github.com/phs/engineer for details
@@ -0,0 +1,101 @@
1
+ class Engineer
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ def self.source_root
6
+ @source_root ||= File.expand_path('../templates', __FILE__)
7
+ end
8
+
9
+ def create_engine_files
10
+ directory 'lib', 'lib'
11
+ end
12
+
13
+ def namespace_application_controller
14
+ unnested_path = Rails.application.paths.app.controllers.to_a.detect do |p|
15
+ File.exists? File.join(p, 'application_controller.rb')
16
+ end
17
+
18
+ return unless unnested_path
19
+
20
+ nested_path = File.join(unnested_path, app_name)
21
+ empty_directory nested_path
22
+
23
+ create_file File.join(nested_path, 'application_controller.rb') do
24
+ content = File.read File.join(unnested_path, 'application_controller.rb')
25
+ content.gsub! "class ApplicationController", "class #{app_module}::ApplicationController"
26
+ content
27
+ end
28
+
29
+ remove_file File.join(unnested_path, 'application_controller.rb')
30
+
31
+ controller_files = Rails.application.paths.app.controllers.to_a.collect do |p|
32
+ Dir[File.join(p, "**", "*_controller.rb")]
33
+ end.flatten
34
+
35
+ controller_files.reject { |f| f =~ /application_controller\.rb$/ }.each do |file|
36
+ gsub_file file, "< ApplicationController", "< #{app_module}::ApplicationController"
37
+ end
38
+ end
39
+
40
+ def append_gemspec_to_Rakefile
41
+ in_root do
42
+ unless IO.read('Rakefile') =~ /Engineer::Tasks.new/
43
+ append_file 'Rakefile' do
44
+ <<-RAKE
45
+
46
+ Engineer::Tasks.new do |gem|
47
+ gem.name = "#{app_name}"
48
+ gem.summary = %Q{TODO: one-line summary of your engine}
49
+ gem.description = %Q{TODO: longer description of your engine}
50
+ gem.email = "TODO"
51
+ gem.homepage = "TODO"
52
+ gem.authors = ["TODO"]
53
+ gem.require_path = 'lib'
54
+ gem.files = FileList[
55
+ "[A-Z]*",
56
+ "{app,config,lib,public,spec,test,vendor}/**/*",
57
+ "db/**/*.rb"
58
+ ]
59
+
60
+ # Include Bundler dependencies
61
+ Bundler.definition.dependencies.each do |dependency|
62
+ next if dependency.name == "engineer"
63
+
64
+ if (dependency.groups & [:default, :production, :staging]).any?
65
+ gem.add_dependency dependency.name, *dependency.requirement.as_list
66
+ else
67
+ gem.add_development_dependency dependency.name, *dependency.requirement.as_list
68
+ end
69
+ end
70
+
71
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
72
+ end
73
+ RAKE
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # TODO Remove after https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4400
80
+ def tweak_route_declaration
81
+ gsub_file(File.join("config", "routes.rb"),
82
+ /#{Rails.application.class}.routes.draw/, "Rails.application.routes.draw")
83
+ end
84
+
85
+ protected
86
+
87
+ def app_name
88
+ app_module.underscore
89
+ end
90
+
91
+ def app_module
92
+ Rails.application.class.name.split('::').first
93
+ end
94
+
95
+ def engineer_version
96
+ Gem.loaded_specs["engineer"].version
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,41 @@
1
+ require "<%= app_name %><%#%>"
2
+ require "rails"
3
+
4
+ module <%= app_module %><%#%>
5
+ class Engine < Rails::Engine
6
+ ASSET_PREFIX = "<%= app_name %><%#%>"
7
+ ENGINEER_VERSION = "<%= engineer_version %><%#%>"
8
+
9
+ initializer "<%= app_name %><%#%>.action_view.identifier_collection" do
10
+ require 'action_view/template'
11
+ class ActionView::Template
12
+ def render_with_identifier_collection(view, locals, &block)
13
+ (Thread.current[:view_identifiers] ||= []).push identifier
14
+ render_without_identifier_collection(view, locals, &block)
15
+ ensure
16
+ Thread.current[:view_identifiers].pop
17
+ end
18
+
19
+ unless instance_methods.include? "render_without_identifier_collection"
20
+ alias_method_chain :render, :identifier_collection
21
+ end
22
+ end
23
+ end
24
+
25
+ def engine_view?(identifier)
26
+ @engine_views ||= Hash.new do |h, identifier|
27
+ h[identifier] = !Rails.application.paths.app.views.any? do |path|
28
+ identifier =~ /^#{Regexp.escape(path)}/
29
+ end
30
+ end
31
+ @engine_views[identifier]
32
+ end
33
+
34
+ initializer "<%= app_name %><%#%>.asset_path" do
35
+ <%= app_module %><%#%>::ApplicationController.config.asset_path = lambda do |source|
36
+ view_identifier = (Thread.current[:view_identifiers] ||= []).last
37
+ engine_view?(view_identifier) ? "/#{ASSET_PREFIX}#{source}" : source
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,3 @@
1
+ module <%= app_module %>
2
+ require "<%= app_name %>/engine" if defined?(Rails)
3
+ end
@@ -0,0 +1,5 @@
1
+ Description:
2
+ Install <%= app_name %>
3
+
4
+ Example:
5
+ rails generate <%= app_name %>:install
@@ -0,0 +1,19 @@
1
+ module <%= app_module %><%#%>
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ def self.source_root
6
+ @source_root ||= File.expand_path('../templates', __FILE__)
7
+ end
8
+
9
+ def install_rake_tasks
10
+ directory '.', File.join("lib", "tasks")
11
+ end
12
+
13
+ def rake_engine_install
14
+ rake "<%= app_name %><%#%>:install"
15
+ end
16
+
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,167 @@
1
+ namespace "<%= app_name %><%#%>" do
2
+
3
+ # Migrate after schema in case there is no engine schema: at least we'll get the migrations.
4
+ task :install => %w{assets db:schema db:migrate db:migrate:seed}
5
+
6
+ desc "Import <%= app_name %><%#%>'s assets and new db migrations"
7
+ task :update => %w{assets db:migrate}
8
+
9
+ namespace :db do #<%# TODO: not everyone uses active record. %>
10
+
11
+ def host_migration(new_migration_name, &block)
12
+ require 'rails/generators/active_record'
13
+ db_migrate = File.join Rails.root, *%w{db migrate}
14
+ mkdir_p db_migrate unless File.exists? db_migrate
15
+
16
+ # TODO Kill after https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/4412
17
+ sleep 1 # wait for timestamp to change.
18
+
19
+ migration_number = ActiveRecord::Generators::Base.next_migration_number(db_migrate)
20
+ new_migration = File.join db_migrate, "#{migration_number}_#{new_migration_name}.rb"
21
+ tmp_migration = File.join Dir::tmpdir, "#{migration_number}_#{new_migration_name}.rb"
22
+
23
+ File.open(tmp_migration, "w") do |f|
24
+ f << block.call
25
+ end
26
+
27
+ cp tmp_migration, new_migration
28
+ end
29
+
30
+ def import_migration(original_file, new_migration_name, &block)
31
+ host_migration new_migration_name do
32
+ block.call File.read original_file
33
+ end
34
+ end
35
+
36
+ desc "Import <%= app_name %><%#%>'s new db migrations"
37
+ task :migrate do
38
+ require 'rails/generators/active_record'
39
+ db_migrate = File.join Rails.root, *%w{db migrate}
40
+
41
+ # Consider the set of engine migrations, in chronological order.
42
+ migrations = Dir[File.join <%= app_module %><%#%>::Engine.root, *%w{db migrate [0-9]*_*.rb}]
43
+ migrations.sort!
44
+
45
+ # See if the host already has a schema migration.
46
+ last_migration_in_schema = migrations.reverse.detect do |migration|
47
+ name = File.basename(migration).match(/\d+_(.*)\.rb/)[1]
48
+ Dir[File.join(db_migrate, "[0-9]*_<%= app_name %><%#%>_schema_after_#{name}.rb")].any?
49
+ end
50
+
51
+ # If so, do not import any migrations implied by the schema
52
+ # (recall slice! *removes* the indicated range, leaving the rest.)
53
+ migrations.slice! 0..migrations.index(last_migration_in_schema) if last_migration_in_schema
54
+
55
+ # Of the remainder
56
+ migrations.each do |old_migration|
57
+ old_name = File.basename(old_migration).match(/\d+_(.*)\.rb/)[1]
58
+ new_name = "<%= app_name %><%#%>_#{old_name}"
59
+
60
+ # Skip this single migration if we already have it
61
+ next if Dir[File.join(db_migrate, "[0-9]*_#{new_name}.rb")].any?
62
+
63
+ import_migration old_migration, new_name do |migration_source|
64
+ migration_source.gsub "class #{old_name.camelize}", "class #{new_name.camelize}"
65
+ end
66
+ end
67
+ end
68
+
69
+ desc "Import <%= app_name %><%#%>'s schema as a db migration"
70
+ task :schema do
71
+ schema = File.join <%= app_module %><%#%>::Engine.root, *%w{db schema.rb}
72
+ if File.exist? schema
73
+ migrations = Dir[File.join <%= app_module %><%#%>::Engine.root, *%w{db migrate [0-9]*_*.rb}]
74
+ latest_migration = migrations.sort.last
75
+
76
+ migration_name = if latest_migration
77
+ latest_migration_name = File.basename(latest_migration).match(/^\d+_(.+)\.rb$/)[1]
78
+ "<%= app_name %><%#%>_schema_after_#{latest_migration_name}"
79
+ else
80
+ "<%= app_name %><%#%>_schema"
81
+ end
82
+
83
+ import_migration schema, migration_name do |schema_source|
84
+ # Strip schema declaration
85
+ schema_source.gsub! /\A.*^ActiveRecord::Schema.define\(:version => \d+\) do/m, ''
86
+ schema_source.strip!.gsub!(/^end\Z/m, '').strip!
87
+
88
+ # Indent everything 2 and strip trailing white space
89
+ schema_source.gsub!(/^/, ' ').gsub!(/[\t ]+$/, '')
90
+
91
+ # Wrap with migration class declaration
92
+ <<-EOF
93
+ class #{migration_name.camelize} < ActiveRecord::Migration
94
+ def self.up
95
+ #{schema_source}
96
+ end
97
+ end
98
+ EOF
99
+ end
100
+ end
101
+ end
102
+
103
+ task "migrate:seed" do
104
+ migration_name = "<%= app_name %><%#%>_seed"
105
+ if File.exist? File.join(<%= app_module %><%#%>::Engine.root, 'db', 'seeds.rb')
106
+ host_migration migration_name do
107
+ <<-EOF
108
+ class #{migration_name.camelize} < ActiveRecord::Migration
109
+ def self.up
110
+ load File.join(<%= app_module %><%#%>::Engine.root, 'db', 'seeds.rb')
111
+ end
112
+ end
113
+ EOF
114
+ end
115
+ end
116
+ end
117
+
118
+ desc "Load <%= app_name %><%#%>'s seed data"
119
+ task :seed => :environment do
120
+ seed_file = File.join(<%= app_module %><%#%>::Engine.root, 'db', 'seeds.rb')
121
+ load(seed_file) if File.exist?(seed_file)
122
+ end
123
+
124
+ end
125
+
126
+ desc "Link (or copy) <%= app_name %><%#%>'s static assets"
127
+ task :assets, [:copy] => :environment do |t, args|
128
+ engine_asset_path = File.join(
129
+ Rails.application.paths.public.to_a.first,
130
+ <%= app_module %><%#%>::Engine::ASSET_PREFIX)
131
+
132
+ rm_rf engine_asset_path
133
+ host_asset_path = <%= app_module %><%#%>::Engine.paths.public.to_a.first
134
+
135
+ link = lambda do
136
+ begin
137
+ ln_s host_asset_path, engine_asset_path
138
+ true
139
+ rescue NotImplementedError
140
+ false
141
+ end
142
+ end
143
+
144
+ copy = lambda { cp_r host_asset_path, engine_asset_path }
145
+
146
+ not args[:copy] and link.call or copy.call
147
+ end
148
+
149
+ end
150
+
151
+ namespace :engines do
152
+
153
+ desc "Load seed data from all engines"
154
+ task "db:seed" => "<%= app_name %><%#%>:db:seed"
155
+
156
+ desc "Import new migrations from all engines"
157
+ task "db:migrate" => "<%= app_name %><%#%>:db:migrate"
158
+
159
+ desc "Link (or copy) static assets from all engines"
160
+ task :assets, [:copy] => "<%= app_name %><%#%>:assets"
161
+
162
+ desc "Import assets and new db migrations from all engines"
163
+ task :update => "<%= app_name %><%#%>:update"
164
+
165
+ end
166
+
167
+ task "db:seed" => "engines:db:seed"
data/spec/spec.opts ADDED
@@ -0,0 +1 @@
1
+ --color
@@ -0,0 +1,13 @@
1
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
2
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
3
+
4
+ require 'rubygems'
5
+ gem 'jeweler', '>= 1.4.0'
6
+
7
+ require 'engineer'
8
+ require 'spec'
9
+ require 'spec/autorun'
10
+
11
+ Spec::Runner.configure do |config|
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,123 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: engineer
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 1
8
+ - 0
9
+ version: 0.1.0
10
+ platform: ruby
11
+ authors:
12
+ - Phil Smith
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-04-28 00:00:00 -07:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: jeweler
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ segments:
28
+ - 1
29
+ - 4
30
+ - 0
31
+ version: 1.4.0
32
+ type: :runtime
33
+ version_requirements: *id001
34
+ - !ruby/object:Gem::Dependency
35
+ name: rspec
36
+ prerelease: false
37
+ requirement: &id002 !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ segments:
42
+ - 1
43
+ - 2
44
+ - 9
45
+ version: 1.2.9
46
+ type: :development
47
+ version_requirements: *id002
48
+ - !ruby/object:Gem::Dependency
49
+ name: cucumber
50
+ prerelease: false
51
+ requirement: &id003 !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ segments:
56
+ - 0
57
+ - 6
58
+ - 4
59
+ version: 0.6.4
60
+ type: :development
61
+ version_requirements: *id003
62
+ description: Turn your rails 3 app into an embeddable engine gem, with answers for db migrations, static assets and more.
63
+ email: phil.h.smith@gmail.com
64
+ executables: []
65
+
66
+ extensions: []
67
+
68
+ extra_rdoc_files:
69
+ - LICENSE
70
+ - README.rdoc
71
+ files:
72
+ - LICENSE
73
+ - README.rdoc
74
+ - Rakefile
75
+ - VERSION
76
+ - features/engine_rake_tasks.feature
77
+ - features/engineer_install_generator.feature
78
+ - features/host_install_generator.feature
79
+ - features/support/env.rb
80
+ - features/support/steps.rb
81
+ - lib/engineer.rb
82
+ - lib/engineer/tasks.rb
83
+ - lib/generators/engineer/install/USAGE
84
+ - lib/generators/engineer/install/install_generator.rb
85
+ - lib/generators/engineer/install/templates/lib/%app_name%.rb.tt
86
+ - lib/generators/engineer/install/templates/lib/%app_name%/engine.rb.tt
87
+ - lib/generators/engineer/install/templates/lib/generators/%app_name%/install/USAGE.tt
88
+ - lib/generators/engineer/install/templates/lib/generators/%app_name%/install/install_generator.rb.tt
89
+ - lib/generators/engineer/install/templates/lib/generators/%app_name%/install/templates/%app_name%.rake.tt
90
+ - spec/spec.opts
91
+ - spec/spec_helper.rb
92
+ has_rdoc: true
93
+ homepage: http://github.com/phs/engineer
94
+ licenses: []
95
+
96
+ post_install_message:
97
+ rdoc_options:
98
+ - --charset=UTF-8
99
+ require_paths:
100
+ - lib
101
+ required_ruby_version: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - ">="
104
+ - !ruby/object:Gem::Version
105
+ segments:
106
+ - 0
107
+ version: "0"
108
+ required_rubygems_version: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - ">="
111
+ - !ruby/object:Gem::Version
112
+ segments:
113
+ - 0
114
+ version: "0"
115
+ requirements: []
116
+
117
+ rubyforge_project:
118
+ rubygems_version: 1.3.6
119
+ signing_key:
120
+ specification_version: 3
121
+ summary: Turn rails 3 applications into engines
122
+ test_files:
123
+ - spec/spec_helper.rb