kealy_cheese 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ == Overview
2
+
3
+ One of the things I was most looking forward to in rails 3 was the plugin / engine architecture. Recently, I sat down to figure out how to create my first engine and package it up as a gem and it took me awhile of time just to get the structure of the engine setup. It's missing a lot of the "rails" you get in a normal rails app.
4
+
5
+ In it's simplest form engines are quite easy, however for a full-featured engine (such as creating a forum) there are a lot of extras that you're going to want. These are the things I spent time figuring out that I've packaged up into an easy starting point:
6
+
7
+ * Namespacing models & controllers so they don't collide with those in the main app
8
+ * Creating a global layout within your engine that gets nested within your application layout
9
+ * Generating migrations from your engine
10
+ * Creating an "acts_as_[plugin]" declaration for use inside your main app's models
11
+ * Easy plugin configuration file editable from main app directory
12
+ * Rake tasks within engine
13
+ * Writing tests for models complete with fixtures
14
+ * Serving static assets within engine
15
+ * Packaging and distributing as a gem
16
+ * Code is here - I've created an engine stub that has all the things setup for you already. I want to see a lot more rails 3 engines get created, I hope this helps! I'd love to hear feedback from you if you try it out.
17
+
18
+ Here’s how you get ready to create your first gem by using this starting point:
19
+
20
+ * git clone http://github.com/krschacht/rails_3_engine_demo.git
21
+ * cd rails_3_engine_demo
22
+ * [edit test/database.yml]
23
+ * rake test (this will initialize your test database and run the basic test suite)
24
+
25
+ Now, create a plain rails app and set it up to use your engine. FYI: even though the engine's directory is 'rails_3_engine_demo', internally the engine is named 'cheese'
26
+
27
+ * cd .. [ this is to take you outside the 'rails_3_engine_demo' directory that was created above' ]
28
+ * rails new demo_app_to_use_gem -d mysql
29
+ * cd demo_app_to_use_gem
30
+ * [edit config/database.yml]
31
+ * [edit Gemfile, add line: ] gem ‘cheese’, :path => "../rails_3_engine_demo"
32
+ * rails generate cheese
33
+ * [examine config/initializers/cheese.rb to see basic config parameters]
34
+ * rake db:create
35
+ * rake db:migrate (one of the migrations that you'll see run came from the engine)
36
+
37
+ You have now setup a empty rails app that utilizes your engine. To test out the functionality, startup the demo app’s webserver:
38
+
39
+ * rails server
40
+ * Then visit: http://localhost:3000/cheese (this is a controller included within the engine)
41
+ * Watch the server logs as you're viewing this page and you'll see some output which is coming from an application before_filter which is executing from inside the engine (in lib/application_controller.rb)
42
+ * rake cheese:report (this is a a rake task that is included inside the engine)
43
+
44
+ Lastly, let's package up your engine as a real gem. You’ll need Jeweler installed for this:
45
+
46
+ * cd rails_3_engine_demo
47
+ * sudo gem install jeweler
48
+ * rake gemspec
49
+ * rake build
50
+ * rake install (you have now installed your engine as a gem locally)
51
+ * [ At this point if you wanted your demo app to use your installed gem, edit the Gemfile in your demo app and remove the 'path' option from your gem line. It should look like this: ] gem ‘cheese’
52
+ * rake gemcutter:release (this pushes your gem up to Gemcutter, a public gem repository)
53
+
54
+ Now you’re ready to start customizing this engine for your own purposes. Do a global find in your engine directory and replace every instance of "cheese" and "Cheese" with your engine name (be sure to preserve capitalization). Likewise, rename every directory and file that’s named "cheese" with your engine name. I should really automate this but I haven’t figured out how to create a rake task that I can run from within the engine directory itself.
55
+
56
+ P.S. Special thanks to Chrispy for helping figure out the application_controller includes!
@@ -0,0 +1,15 @@
1
+ module Cheese
2
+ class WidgetsController < ApplicationController
3
+
4
+ unloadable
5
+
6
+ layout 'cheese' # this allows you to have a gem-wide layout
7
+
8
+ def index
9
+ end
10
+
11
+ def show
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,2 @@
1
+ ## Look in lib/application_helper.rb
2
+ ## I can't figure out how to get it included unless I put it that directory
@@ -0,0 +1,9 @@
1
+ module Cheese
2
+ module WidgetsHelper
3
+
4
+ def helper_for_widgets_view
5
+ "this output is coming from a helper method just for the widgets controller"
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ module Cheese
2
+ class Widget < ActiveRecord::Base
3
+ set_table_name "cheese_widgets"
4
+
5
+ def make
6
+ puts "widget made"
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ widget#index<br>
2
+ <br>
3
+ The red border is from a layout within the engine which is nested within your application layout<br>
4
+ <br>
5
+ <%= helper_for_widgets_view %><br>
6
+ <br>
7
+ <%= app_wide_helper_method %><br><br>
8
+ This image is being served from the public directory contained within the engine:
9
+ <%= image_tag "cheese.jpg" %>
@@ -0,0 +1 @@
1
+ widgets#show
@@ -0,0 +1,12 @@
1
+
2
+ <% content_for :content do %>
3
+
4
+ <%= stylesheet_link_tag("cheese") %>
5
+
6
+ <div class="red_border">
7
+ <%= yield %>
8
+ </div>
9
+
10
+ <% end -%>
11
+
12
+ <%= render :file => 'layouts/application' %>
@@ -0,0 +1,12 @@
1
+ Rails.application.routes.draw do |map|
2
+
3
+ mount_at = Cheese::Engine.config.mount_at
4
+
5
+ match mount_at => 'cheese/widgets#index'
6
+
7
+ map.resources :widgets, :only => [ :index, :show ],
8
+ :controller => "cheese/widgets",
9
+ :path_prefix => mount_at,
10
+ :name_prefix => "cheese_"
11
+
12
+ end
@@ -0,0 +1,36 @@
1
+ module Cheese
2
+ module ActsAsWidget
3
+
4
+ ## Define ModelMethods
5
+ module Base
6
+ def self.included(klass)
7
+ klass.class_eval do
8
+ extend Config
9
+ end
10
+ end
11
+
12
+ module Config
13
+ def acts_as_widget
14
+
15
+ # This is where arbitrary code goes that you want to
16
+ # add to the class that declared "acts_as_widget"
17
+
18
+ has_many :widgets, :class_name => 'Cheese::Widget'
19
+
20
+ include Cheese::ActsAsWidget::Base::InstanceMethods
21
+ end
22
+ end
23
+
24
+ module InstanceMethods
25
+
26
+ def factory_name
27
+ "this is an example instance method"
28
+ end
29
+
30
+ end # InstanceMethods
31
+ end
32
+
33
+ end
34
+ end
35
+
36
+ ::ActiveRecord::Base.send :include, Cheese::ActsAsWidget::Base
@@ -0,0 +1,7 @@
1
+ module ApplicationHelper
2
+
3
+ def app_wide_helper_method
4
+ "this output is from an app-wide helper method that is declared within the gem"
5
+ end
6
+
7
+ end
@@ -0,0 +1,5 @@
1
+ module Cheese
2
+ require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
3
+ require 'acts_as_widget/base'
4
+ require 'application_controller'
5
+ end
@@ -0,0 +1,30 @@
1
+ require 'cheese'
2
+ require 'rails'
3
+ require 'action_controller'
4
+ require 'application_helper'
5
+
6
+ module Cheese
7
+ class Engine < Rails::Engine
8
+
9
+ # Config defaults
10
+ config.widget_factory_name = "default factory name"
11
+ config.mount_at = '/'
12
+
13
+ # Load rake tasks
14
+ rake_tasks do
15
+ load File.join(File.dirname(__FILE__), 'rails/railties/tasks.rake')
16
+ end
17
+
18
+ # Check the gem config
19
+ initializer "check config" do |app|
20
+
21
+ # make sure mount_at ends with trailing slash
22
+ config.mount_at += '/' unless config.mount_at.last == '/'
23
+ end
24
+
25
+ initializer "static assets" do |app|
26
+ app.middleware.use ::ActionDispatch::Static, "#{root}/public"
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,75 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ class CheeseGenerator < Rails::Generators::Base
5
+ include Rails::Generators::Migration
6
+
7
+ def self.source_root
8
+ File.join(File.dirname(__FILE__), 'templates')
9
+ end
10
+
11
+ def self.next_migration_number(dirname) #:nodoc:
12
+ if ActiveRecord::Base.timestamped_migrations
13
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
14
+ else
15
+ "%.3d" % (current_migration_number(dirname) + 1)
16
+ end
17
+ end
18
+
19
+
20
+ # Every method that is declared below will be automatically executed when the generator is run
21
+
22
+ def create_migration_file
23
+ f = File.open File.join(File.dirname(__FILE__), 'templates', 'schema.rb')
24
+ schema = f.read; f.close
25
+
26
+ schema.gsub!(/ActiveRecord::Schema.*\n/, '')
27
+ schema.gsub!(/^end\n*$/, '')
28
+
29
+ f = File.open File.join(File.dirname(__FILE__), 'templates', 'migration.rb')
30
+ migration = f.read; f.close
31
+ migration.gsub!(/SCHEMA_AUTO_INSERTED_HERE/, schema)
32
+
33
+ tmp = File.open "tmp/~migration_ready.rb", "w"
34
+ tmp.write migration
35
+ tmp.close
36
+
37
+ migration_template '../../../tmp/~migration_ready.rb',
38
+ 'db/migrate/create_cheese_tables.rb'
39
+ remove_file 'tmp/~migration_ready.rb'
40
+ end
41
+
42
+ def copy_initializer_file
43
+ copy_file 'initializer.rb', 'config/initializers/cheese.rb'
44
+ end
45
+
46
+ def update_application_template
47
+ f = File.open "app/views/layouts/application.html.erb"
48
+ layout = f.read; f.close
49
+
50
+ if layout =~ /<%=[ ]+yield[ ]+%>/
51
+ print " \e[1m\e[34mquestion\e[0m Your layouts/application.html.erb layout currently has the line <%= yield %>. This gem needs to change this line to <%= content_for?(:content) ? yield(:content) : yield %> to support its nested layouts. This change should not affect any of your existing layouts or views. Is this okay? [y/n] "
52
+ begin
53
+ answer = gets.chomp
54
+ end while not answer =~ /[yn]/i
55
+
56
+ if answer =~ /y/i
57
+
58
+ layout.gsub!(/<%=[ ]+yield[ ]+%>/, '<%= content_for?(:content) ? yield(:content) : yield %>')
59
+
60
+ tmp = File.open "tmp/~application.html.erb", "w"
61
+ tmp.write layout; tmp.close
62
+
63
+ remove_file 'app/views/layouts/application.html.erb'
64
+ copy_file '../../../tmp/~application.html.erb',
65
+ 'app/views/layouts/application.html.erb'
66
+ remove_file 'tmp/~application.html.erb'
67
+ end
68
+ elsif layout =~ /<%=[ ]+content_for\?\(:content\) \? yield\(:content\) : yield[ ]+%>/
69
+ puts " \e[1m\e[33mskipping\e[0m layouts/application.html.erb modification is already done."
70
+ else
71
+ puts " \e[1m\e[31mconflict\e[0m The gem is confused by your layouts/application.html.erb. It does not contain the default line <%= yield %>, you may need to make manual changes to get this gem's nested layouts working. Visit ###### for details."
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,8 @@
1
+ module Cheese
2
+ class Engine < Rails::Engine
3
+
4
+ config.mount_at = '/cheese'
5
+ config.widget_factory_name = 'Factory Name'
6
+
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ class CreateCheeseTables < ActiveRecord::Migration
2
+ def self.up
3
+ SCHEMA_AUTO_INSERTED_HERE
4
+ end
5
+
6
+ def self.down
7
+ drop_table :cheese_widgets
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ ActiveRecord::Schema.define(:version => 0) do
2
+
3
+ create_table :cheese_widgets, :force => true do |t|
4
+ t.string :title
5
+ t.datetime :created_at
6
+ t.datetime :updated_at
7
+ end
8
+
9
+ add_index :cheese_widgets, [:title]
10
+
11
+ end
@@ -0,0 +1,8 @@
1
+ namespace :cheese do
2
+
3
+ desc "example gem rake task"
4
+ task :report => :environment do
5
+ puts "you just ran the example gem rake task"
6
+ end
7
+
8
+ end
Binary file
@@ -0,0 +1,9 @@
1
+ .cheese {
2
+ color: red;
3
+ }
4
+
5
+ .red_border {
6
+ border: 2px solid red;
7
+ margin: 5px;
8
+ padding: 5px;
9
+ }
@@ -0,0 +1,56 @@
1
+ ENV["RAILS_ENV"] = "test"
2
+
3
+ require 'test/unit'
4
+ require 'rubygems'
5
+ require 'yaml'
6
+ require 'active_record'
7
+ require 'mysql'
8
+
9
+ require 'app/models/cheese/widget.rb'
10
+
11
+ def cheese_widget( fixture_name )
12
+ id = @@fixtures['cheese_widget'][ fixture_name.to_s ][ 'id' ]
13
+ Cheese::Widget.find( id )
14
+ end
15
+
16
+ def load_schema
17
+ config = YAML::load( IO.read( File.dirname(__FILE__) + '/database.yml') )
18
+
19
+ # Manually initialize the database
20
+ conn = Mysql.real_connect( config['mysql']['host'], config['mysql']['username'], config['mysql']['password'] )
21
+ conn.query( "CREATE DATABASE IF NOT EXISTS #{config['mysql']['database']}" )
22
+
23
+ ActiveRecord::Base.establish_connection( config['mysql'] )
24
+ ActiveRecord::Base.connection()
25
+
26
+ load(File.dirname(__FILE__) + "/../" +
27
+ "lib/rails/generators/cheese/templates/schema.rb")
28
+
29
+ @@fixtures = {}
30
+
31
+ load_fixture( 'cheese_widget' )
32
+ end
33
+
34
+ def load_fixture( table )
35
+ @@fixtures[ table ] = {}
36
+ fixture = YAML::load( IO.read( File.dirname(__FILE__) + "/fixtures/#{table}.yml") )
37
+ @@fixtures[ table ] = fixture
38
+
39
+ klass = class_eval table.titleize.gsub(/ /, '::')
40
+
41
+ fixture.each do |record_name, record|
42
+ record.each do |column, value|
43
+ if ( match = column.match(/(.*)_id/) )
44
+ fixture_reference = "cheese_" + match[1].pluralize
45
+ if value.is_a? Symbol
46
+ r = class_eval "#{fixture_reference}( '#{value}' )"
47
+ record[ column ] = r.id
48
+ end
49
+ end
50
+ end
51
+
52
+ r = klass.create( record )
53
+ @@fixtures[ table ][ record_name ][ 'id' ] = r.id
54
+ end
55
+
56
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ class WidgetTest < Test::Unit::TestCase
4
+ load_schema
5
+
6
+ def test_fixtures_are_working
7
+ assert_equal cheese_widget(:first).title, "This is the title"
8
+ end
9
+
10
+
11
+ end
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: kealy_cheese
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.3.0
6
+ platform: ruby
7
+ authors:
8
+ - John Kealy
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2010-09-13 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description:
17
+ email: you@email.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README.rdoc
24
+ files:
25
+ - app/controllers/cheese/widgets_controller.rb
26
+ - app/helpers/application_helper.rb
27
+ - app/helpers/cheese/widgets_helper.rb
28
+ - app/models/cheese/widget.rb
29
+ - app/views/cheese/widgets/index.html.erb
30
+ - app/views/cheese/widgets/show.html.erb
31
+ - app/views/layouts/cheese.html.erb
32
+ - config/routes.rb
33
+ - lib/acts_as_widget/base.rb
34
+ - lib/application_helper.rb
35
+ - lib/cheese.rb
36
+ - lib/engine.rb
37
+ - lib/rails/generators/cheese/cheese_generator.rb
38
+ - lib/rails/generators/cheese/templates/initializer.rb
39
+ - lib/rails/generators/cheese/templates/migration.rb
40
+ - lib/rails/generators/cheese/templates/schema.rb
41
+ - lib/rails/railties/tasks.rake
42
+ - public/images/cheese.jpg
43
+ - public/stylesheets/cheese.css
44
+ - README.rdoc
45
+ - test/test_helper.rb
46
+ - test/unit/widget_test.rb
47
+ homepage:
48
+ licenses: []
49
+
50
+ post_install_message:
51
+ rdoc_options:
52
+ - --charset=UTF-8
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: "0"
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ none: false
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Description of your gem
74
+ test_files:
75
+ - test/test_helper.rb
76
+ - test/unit/widget_test.rb