PullMonkey Blog


06 Jan

POST OFC Graph as Image


I was asked recently (well sort of) to give an example of saving an image to the server. If you look at teethgrinder's example for this, you will see that he has made available an external interface to do just that - POST your graph as png raw data to your server for storage. This has many benefits such as saving the image for use in a PDF report or for printing, since we know at times it is a bit troublesome to print the embedded flash object.

I think the main problem people are having with this is the receiving of the image data post - see the upload_image method below. Also, teethgrinder's example never really says where to make the post_image() call. So I touch on both in the code below.

Here is an example of the png that is saved when I did this for the chart in the previous example:

OFC Saved Image


Well, let's just get right in to the code.

The controller contains the same code as my last post with only a few minor changes to the index method and the addition of the upload_image method.
In the controller, I have this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class TestItController < ApplicationController
  def index
    # note the user of open_flash_chart_object_from_hash instead of just open_flash_chart_object
    # this allows you to pass in the id of the div you want the the chart to be in
    # this is useful for when we need to findSWF by this id
    @graph = open_flash_chart_object_from_hash("/test_it/chart", :div_name => "my_chart")
  end

  # added to recieve the post data for the OFC png image of the OFC graph
  def upload_image
    name = "tmp_image.png" || params[:name]
    # the save_image method that is provided by the OFC swf file sends raw post data, so get to it like this
    data = request.raw_post
    File.open("#{RAILS_ROOT}/tmp/#{name}", "wb") { |f| f.write(data) } if data
    render :nothing => true
  end

  def chart
    # same code from here - http://pullmonkey.com/2010/01/05/open-flash-chart-ii-x-axis-date-and-time/ 
    ...
  end
end




So just note the use of open_flash_chart_object_from_hash() in the index method, this way we can pass in the id of the div.

In the view, I have this:

1
2
3
4
5
<%= javascript_include_tag 'swfobject.js' %>
<%= @graph %>
<%= save_as_image("http://localhost:3000/test_it/upload_image?name=tmp.png", :id => "my_chart") %>
<br/>
<%= button_to_function "Save Image", "post_image()" %>



Really the only difference from what we would normally have in our view is that I am using the save image setup method that was added to the open flash chart ruby on rails plugin in the last couple hours (as of this post). The save_image method takes some arguments, mainly the url to post the image data to and the id of the chart we setup in the controller.



04 Jan

Open Flash Chart II for Ruby on Rails - Lug Wyrm Charmer


A long time overdue, but I've managed to get everything updated to the new version of Teethgrinder's open flash chart.

I've also started tagging everything, so if you notice any problems trying to do anything from Teethgrinder's examples, then first check that you are using the latest (as of now, that is Lug Wyrm Charmer) - http://github.com/pullmonkey/open_flash_chart/tree/LugWyrmCharmer.

Make sure you are using the latest swf either from the plugin assets directory or from Teethgrinder's downloads.


11 Feb

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!