Blog plugin tutorial for Ruby on Rails
Update: Bloggity does not require the Engines plugin to run if you are using Rails 2.3 or above (where the Engines plugin is baked in). -- Noted below by Bill.
Update: Added the plugin to github - simple_blog. It is not production ready or really all that usable quite yet.
Ok, so this a rant and I am sorry for that - but as simple as it is, I have been looking for a blog plugin lately. The problem with the plugins I find is that I don't want to have to deal with the engines plugin or have the controllers, models, views, etc ... all extracted into my applications code. I want it all external (hence a plugin) but let it be minimally configurable.
So in my recent search for a blog plugin for rails, I came across two that look very useful, but each with their flaws:
1) bloget - Everything is extracted to my code space. Why? Yes, I realize that it is most likely because I will want to override things, but get out of my space and keep to yourself!
Provide me a way to override things that I would need to (there really shouldn't be too many), after all it is ruby.
2) bloggity - Uses the engines plugin! I have nothing against the engines plugin (I think it is well written and documented) but for a freaking blog plugin?!? Why?
Is there a third option?
Glad you asked - yes, there is a third option - I hate to say it, but do it right! There's your third option.
Ok, but really, if there is a third option (a third plugin), I would love to hear about it.
Ok, so all that to lead up to a little plugin tutorial? Well, it got your attention didn't it?
Starting from scratch
Ok, I guess I will start from scratch. So let's get started.
Creating a plugin
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pullmonkey$ ./script/generate plugin simple_blog create vendor/plugins/simple_blog/lib create vendor/plugins/simple_blog/tasks create vendor/plugins/simple_blog/test create vendor/plugins/simple_blog/README create vendor/plugins/simple_blog/MIT-LICENSE create vendor/plugins/simple_blog/Rakefile create vendor/plugins/simple_blog/init.rb create vendor/plugins/simple_blog/install.rb create vendor/plugins/simple_blog/uninstall.rb create vendor/plugins/simple_blog/lib/simple_blog.rb create vendor/plugins/simple_blog/tasks/simple_blog_tasks.rake create vendor/plugins/simple_blog/test/simple_blog_test.rb create vendor/plugins/simple_blog/test/test_helper.rb |
Create the app directories for your plugin
1 2 3 4 5 6 7 8 9 10 |
pullmonkey$ cd vendor/plugins/simple_blog/ # pretty important pullmonkey$ ls init.rb install.rb lib MIT-LICENSE Rakefile README tasks test uninstall.rb pullmonkey$ mkdir app pullmonkey$ mkdir -p app/models pullmonkey$ mkdir -p app/controllers pullmonkey$ mkdir -p app/views pullmonkey$ mkdir -p app/helpers pullmonkey$ ls app/ controllers helpers models views |
Well that was easy, so let's move on.
Models, Views, Controllers and Helpers - Living as one in my plugin
Models
Ok, so we have a clear path for where our models, controllers, views, and helpers should live, right?
For simplicity, let's just have a post and comment model - you have all seen this a billion times.
Models: vendor/plugins/simple_blog/app/models/post.rb
1 2 3 |
class Post < ActiveRecord::Base has_many :comments end |
Models: vendor/plugins/simple_blog/app/models/comment.rb
1 2 3 |
class Comment < ActiveRecord::Base belongs_to :post end |
And there you have it.
So what do you do to tell your rails application about your models?
Simple - inside vendor/plugins/simple_blog/init.rb - add these lines
1 2 3 |
model_path = File.join(directory, 'app', 'models') $LOAD_PATH << model_path ActiveSupport::Dependencies.load_paths << model_path |
Ok, so let's test it out.
Step 1 - we will need some default migrations for the model to use.
Post migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
pullmonkey$ ./script/generate migration post # This is what mine looks like class Post < ActiveRecord::Migration def self.up create_table :posts do |t| t.string :subject t.text :body t.timestamps end end def self.down drop_table :posts end end |
And then the comment migration:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
pullmonkey$ ./script/generate migration comment # This is what mine looks like def self.up create_table :comments do |t| t.string :username t.text :body t.references :post t.timestamps end end def self.down drop_table :comments end |
Run the migrations:
|
|
pullmonkey$ rake db:migrate |
That was all just setup - now for the actual testing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
pullmonkey$ ./script/console Loading development environment (Rails 2.2.2) >> Comment.new => #<Comment id: nil, username: nil, body: nil, post_id: nil, created_at: nil, updated_at: nil> >> Post.new => #<Post id: nil, subject: nil, body: nil, created_at: nil, updated_at: nil> >> p = Post.create(:subject => "Test 1", :body => "My Body") => #<Post id: 1, subject: "Test 1", body: "My Body", created_at: "2009-02-11 19:09:25", updated_at: "2009-02-11 19:09:25"> >> p.body => "My Body" >> p.subject => "Test 1" >> p.new_record? => false >> p.comments => [] >> c = Comment.create(:username => 'pullmonkey', :body => "this is simple") => #<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: nil, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:01"> >> p.comments << c => [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">] >> p.comments => [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">] >> Post.first.comments => [#<Comment id: 1, username: "pullmonkey", body: "this is simple", post_id: 1, created_at: "2009-02-11 19:10:01", updated_at: "2009-02-11 19:10:06">] |
That's probably good enough. We have a working model and relationships. The best part is that all the code is still in the plugin.
What does my code space contain?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
pullmonkey$ ls -l app/** app/controllers: total 4 -rw-rw-r-- 1 pullmonkey pullmonkey 720 Feb 11 11:00 application.rb app/helpers: total 4 -rw-rw-r-- 1 pullmonkey pullmonkey 115 Feb 11 11:00 application_helper.rb app/models: total 0 app/views: total 4 drwxrwxr-x 2 pullmonkey pullmonkey 4096 Feb 11 11:00 layouts |
Just the defaults - neat
Controllers
In much the same way as models, we can easily use controllers from our plugin. No extracting, no engines plugin.
Controllers: vendor/plugins/simple_blog/app/controllers/posts_controller.rb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
class PostsController < ApplicationController def index @posts = Post.all end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create if @post = Post.create(params[:post]) flash[:notice] = "Post Created" redirect_to :action => 'index' else flash[:error] = "Post Not Created" render :action => 'new' end end #.... more code end |
Controllers: vendor/plugins/simple_blog/app/controllers/comments_controller.rb
1 2 3 4 5 6 |
class CommentsController < ApplicationController def index @comments = Comment.find_all_by_post_id(params[:post_id]) end #.... more code end |
Now, to register the controllers, add the following to vendor/plugins/simple_blog/init.rb:
1 2 3 4 |
controller_path = File.join(directory, 'app', 'controllers') $LOAD_PATH << controller_path ActiveSupport::Dependencies.load_paths << controller_path config.controller_paths << controller_path |
Ok, before we can really test this we will need to do the views, so keep going.
Views
Create your view directories:
1 2 |
pullmonkey$ mkdir -p app/views/posts pullmonkey$ mkdir -p app/views/comments |
Create your views:
For this example, I will just create one, then we will test it.
Views: vendor/plugins/simple_blog/app/views/posts/index.html.erb
1 2 3 4 5 6 7 8 9 10 11 |
<h1>Posts</h1> <% @posts.each do |post| -%> <h2><%= h post.subject %></h2> <%= post.body %> <h3>Comments</h3> <% post.comments.each do |comment| -%> <b>by <%= comment.username %></b><br/> <%= comment.body %><br/> <br/> <% end -%> <% end -%> |
Append your view paths:
If you don't do this next step, you will very likely see an error message like this:
Missing template posts/index.erb in view path /home/pullmonkey/rails_projects/simple_blog/app/views:
So let's add it.
There are at least two ways to do this. 1) Added to your controllers individually or 2) Add to application controller globally.
I prefer the less obtrusive, so let's go with number 1.
For this test, we will just work with the posts controller, so open it up again and add this line:
|
|
self.append_view_path(File.join(File.dirname(__FILE__), '..', 'views')) |
So your file should look like this now:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
class PostsController < ApplicationController self.append_view_path(File.join(File.dirname(__FILE__), '..', 'views')) def index @posts = Post.all end def show @post = Post.find(params[:id]) end def new @post = Post.new end def create if @post = Post.create(params[:post]) flash[:notice] = "Post Created" redirect_to :action => 'index' else flash[:error] = "Post Not Created" render :action => 'new' end end end |
Time to test
Start your web server - ./script/server
Browse to http://localhost:3000/posts
You should see the post we created up above via Post.create(...) and its associated comment that we also created above.
Note:Feel free to overwrite any of the views. This can be done simply for the posts index view by creating the same file under RAILS_ROOT/app/views/posts/index.html.erb and doing what you'd like.
That's it for part 1
Ok, so that's part 1. The goal was to keep everything external and I think we succeeded (aside from migrations).
No offense to those that use engines or extract files into one's application's space, we all have our ways - the above is what I prefer.
Part 2 will consist mainly of filling this out a bit more and further discussion on adding helpers, routes and migrations to your plugin without interfering in the application's code space.
As always, have fun and good luck!
