Recently in rails Category

Transparent PNGs in IE w/ Rails

| | Comments () | TrackBacks (0)
I've been working on Kebima for several months now, using Firefox and Linux/OSX.  Chalk it up to not doing enough research, but I just figured transparent PNGs worked in IE.  Oh well.  They don't.  At least not in 6 and earlier.  So began my mission to get them to work.

I was already using a lightbox package that uses the technique mentioned in the MS support article, but I didn't want to have to apply a div-specific solution for every png on my page... and since i'm using the silk icon set, this would mean a lot a lot of specificity.

Googling gets you a lot of results.  The first hit is actually pretty good, in that it states that the script isn't maintained, but points you to 24 Ways, which has a pretty good solution.  This likely works out of the box for normal web development (I did run into one bug... for some reason the section of the script that sets root on line 17 failed... I took out the bit that allowed you to limit the div the script was applied to).  However rails likes to throw timestamps on the end of images, so instead of '/images/icon.png' you get '/images/icon.png?1209327623'

Because the 24 Ways script is looking for an img tag whose src attribute ends with '.png', this makes things not work.  My solution was to add a regex to match rails style image srcs

    var png_pattern        = /\.png(\?\d*)?$/i;

And then match against that in fnLoadPngs

            // background pngs
            if (obj.currentStyle.backgroundImage.match(png_pattern) !== null) {
                bg_fnFixPng(obj);
            }
            // image elements
            if (obj.tagName=='IMG' && obj.src.match(png_pattern) !== null){
                el_fnFixPng(obj);
            }

And it worked.  Plus I got a pretty decent workout running up and down the stairs between Mac and PC.
Recently there have been a slew of posts complaining that rails doesn't scale and is hard to deploy.  I think the root of this is that people come to rails after experience with Java, PHP or similar frameworks.   Rails is neither of these.  It is heavier than PHP, and it seems that this makes its fit with the typical PHP deployment methods slow at best.  At the same time it has an entirely different deployment model than Java web applications.  The Java application containers are heavier, and designed to support a much more highly available application within  that JVM instance.

But rails still has its sweet spot.  So why would I use rails instead of Java? It's not as light as PHP, but from a development perspective it's usually close enough.  Bouncing webrick or mongrel takes a second or two, and for most changes I don't even need to do this.  To accomplish the same code/test cycle in Java takes  at least 10 times as long, often much worse.  At the same time rails offers.  Plus I can do a lot of things I can't do with Java, thanks to dynamic typing, better metaprogramming facilities, and things like the rails console.  Plus, from a hosting perspective you can get much better deals on hosting a rails application than a full Java servlet environment, which tells you something about the relative difficulty of hosting rails and J2EE.

Why would I use it instead of PHP?  The framework does enough magic that getting an application up and running is faster than raw PHP (disclaimer:  I haven't used any of the newer PHP frameworks like CakePHP . Maybe they're more on par with rails here.).  MVC is nicely broken out.  Ruby itself is a wonderful language to develop in.  Yes, you're going to have to do more work around deployment.  I can't imagine going live using FastCGI, SCGI, or that sort of thing.  But learn how to set up a mongrel cluster.  That seems to work.

I think Java and PHP still have their places as well.  Java provides much better concurrency in a single process, better built-in capabilities for high availability, security, and a larger library/tool ecosystem.  Enterprises know how to deploy and maintain Java applications, and in general feel more comfortable putting those in production than a rails system.  PHP is lighter weight, will likely scale better out of the box, and is better understood by most hosting companies.

At the end of the day the decision comes down to what you're building and where you're deploying.  I think rails works in wide range of situations, but there is an equally wide range where PHP or Java may be a better choice. 
I'm in the process of building a REST service using rails and ActiveResource. On the server side everything was fairly straightforward, but I hit a snag when I moved to the web tier. For various reasons I wanted to have two separate servers, one providing the REST service and the other serving HTML content. The rub is that ActiveResource assumes that credentials are a class level attribute. I'd like the calls to the ActiveResource client to use the credentials of the currently logged on user, but there doesn't appear to be a straightforward way to do this out of the box. Metaprogramming to the rescue!

Using the REST example from my last post, I extended the client in the following way.

require 'activeresource'

module Sample
  module Client
    class API
      #
      # Creates a module that serves as an ActiveResource
      # client for the specified user
      #
      def self.create_api(login = nil, password = nil)
        # specify the site.  Default to no credentials
        @url_base = "http://localhost:3000"
        @url_base = "http://#{login}:#{password}@localhost:3000" if login
        # build a module name.  This assumes that logins are unique.
        # it also assumes they're valid ruby module names when capitalized
        @module = login ? login.capitalize : "Default"
        class_eval <<-"end_eval",__FILE__, __LINE__
        module #{@module}
          class Post < ActiveResource::Base
            self.site = "#{@url_base}"
          end
          class Comment < ActiveResource::Base
            self.site = "#{@url_base}/posts/:post_id"
          end
          # return the module, not the last site String
          self
        end
        end_eval
      end
    end
  end
end

This new version allows you to create an api for a specific user. You can squirrel this away and use it for specific users, allowing you to use multiple connections to your rest service. A quick irb session follows

>> require 'ares_sample_client'
=> ["Sample"]
>> api = Sample::Client::API.create_api
=> Sample::Client::API::Default
>> p = api::Post.find(1)
=> #<Sample::Client::API::Default::Post:0xb715af74 @attributes={"updated_at"=>Wed Jan 09 02:36:34 UTC 2008, "id"=>1, "content"=>"The first post", "user_id"=>1, "created_at"=>Wed Jan 09 02:36:34 UTC 2008}, @prefix_options={}>
>> p = api::Post.create(:content => "should fail")
ActiveResource::UnauthorizedAccess: Failed with 401 Unauthorized
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:125:in `handle_response'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:112:in `request'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:101:in `post'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:803:in `create'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:636:in `save_without_validation'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/validations.rb:262:in `save'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:339:in `create'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/object/misc.rb:28:in `returning'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:339:in `create'
        from (irb):4
>> auth_api = Sample::Client::API.create_api('test1','test1')
=> Sample::Client::API::Test1
>> p = auth_api::Post.find(1)
=> #<Sample::Client::API::Default::Post:0xb713dde8 @attributes={"updated_at"=>Wed Jan 09 02:36:34 UTC 2008, "id"=>1, "content"=>"The first post", "user_id"=>1, "created_at"=>Wed Jan 09 02:36:34 UTC 2008}, @prefix_options={}>
>> p = auth_api::Post.create(:content => "should succeed!")
=> #<Sample::Client::API::Test1::Post:0xb713312c @attributes={"updated_at"=>Thu Jan 10 04:01:53 UTC 2008, "id"=>7, "content"=>"should succeed!", "user_id"=>nil, "created_at"=>Thu Jan 10 04:01:53 UTC 2008}, @prefix_options={}>
>>

   
Posted by McWong 53 days ago
This walkthrough shows the creation of a simple REST API using ActiveResource. It includes a nested model, authorization scheme, and a client library. The intent is not to exhaustively explore ActiveResource, but to show how to quickly get up and running with a reasonable REST API.

Step 1: create an application

rails ares-demo

I'm using rails 2.0.2 here, but I don't think anything is version specific.

Step 2: install two great rest plugins
Resource This is a plugin that adds default rest-based actions to a controller. It has so-so support for nested resources... you won't be able to use it out of the box for HABTM relationships for instance, but for strict hierarchies it works well, and really beats hand coding the controllers. You can install it with

script/plugin install http://jnewland.com/svn/public/ruby/rails/plugins/resource_this/

Restful Authentication is derived from acts_as_authenticated, and adds a rest-ish session controller, as well as the usual controller helpers (login_required, etc.) You can install it with

script/plugin install http://svn.techno-weenie.net/projects/plugins/restful_authentication/

Step 3: setup restful authentication
This bit is easy. Just run the generator to get your migrations for users created, along with a couple other things. I honestly didn't dig into this too much, it just works.

./script/generate authenticated user sessions

Step 4: create application model
Next up is generating your model. We'll do a simple one. Posts contain many Comments. Both Posts and Comments belong to a user.

./script/generate scaffold post
./script/generate scaffold comment

Next set up your migrations. db/migrate/002_create_posts.rb should contain

class CreatePosts < ActiveRecord::Migration
  def self.up
    create_table :posts do |t|
      t.column :content, :text
      t.column :user_id, :integer
      t.timestamps
    end
  end

  def self.down
    drop_table :posts
  end
end

and db/migrate/003_create_comments.rb should contain

class CreateComments < ActiveRecord::Migration
  def self.up
    create_table :comments do |t|
      t.column :content, :text
      t.column :user_id, :integer
      t.column :post_id, :integer
      t.timestamps
    end
  end

  def self.down
    drop_table :comments
  end
end

Next you'll need to add the relationships to your model classes. Add the following bit to the front of app/models/user.rb

class User < ActiveRecord::Base
  # custom relationships
  has_many :posts
  has_many :comments

app/models/post.rb should look like

class Post < ActiveRecord::Base
  belongs_to :user
  has_many :comments
end

and app/models/comment.rb should look like

class Comment < ActiveRecord::Base
  belongs_to :user
  belongs_to :post
end

Now run migrate to get your database all configured

rake db:migrate

Step 5: populate dummy data
Next we'll want to get some default data in there. Execute script/console and run through the following

>> u1 = User.new(:login => "test1", :email => "test1@foo.com", :password => "test1", :password_confirmation => "test1")
=> #<User id: nil, login: "test1", email: "test1@foo.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil, remember_token: nil, remember_token_expires_at: nil>
>> u1.save
=> true
>> u2 = User.new(:login => "test2", :email => "test2@foo.com", :password => "test2", :password_confirmation => "test2")
=> #<User id: nil, login: "test2", email: "test2@foo.com", crypted_password: nil, salt: nil, created_at: nil, updated_at: nil, remember_token: nil, remember_token_expires_at: nil>
>> u2.save
=> true
>> p = Post.new(:user => u1, :content => "The first post")
=> #<Post id: nil, content: "The first post", user_id: 1, created_at: nil, updated_at: nil>
>> p.save
=> true
>> c = Comment.new(:user => u2, :post => p, :content => "first!")
=> #<Comment id: nil, content: "first!", user_id: 2, post_id: 1, created_at: nil, updated_at: nil>
>> c.save
=> true
>>

Step 6: resource_this!
Now the boring part us over. So far we have a basic rails app. Now we get to the REST part! You're three edits away... Change app/controllers/posts_controller.rb to the following

class PostsController < ApplicationController
  resource_this
end

Change app/controllers/comments_controller.rb to the following

class CommentsController < ApplicationController
  resource_this :nested => [:post]
end

And finally update config/routes.rb

ActionController::Routing::Routes.draw do |map|
  map.resources :posts do |post|
    post.resources :comments
  end

  map.resources :users

  map.resource :session
end

That's it! You're ready to look through your API

Step 7: browse
Start up your server with script/server. You'll need to clean up the erb views if you want to use the HTML interface, but your xml interface should be working just fine. Try the following urls for verification

    * http://localhost:3000/posts/1.xml
    * http://localhost:3000/posts/1/comments.xml

Step 8: lock it down
So far our service is open to anybody. We'll add very simple authentication at this stage... we'll require a valid user for anything other than show and index requests. All we have to do for this is include AuthenticatedSystem in our application controller, and add a before_filter. Make app/controllers/application.rb look like the following

class ApplicationController < ActionController::Base
  include AuthenticatedSystem
  before_filter :login_required, :only => [:create,:edit,:new,:update,:destroy]
  helper :all # include all helpers, all the time

  # See ActionController::RequestForgeryProtection for details
  # Uncomment the :secret if you're not using the cookie session store
  protect_from_forgery # :secret => 'b845cf981afbfef06600e5d7f23e0fed'
end

Step 9: create client lib
We could test this with curl or a browser, but the real fun of ActiveResource is getting to use ruby objects. So... we'll create a simple client api. Create a new file in lib called 'ares_sample_client.rb' and add the following code

require 'activeresource'

module Sample
  module Client
    class API
      class Post < ActiveResource::Base
        self.site = "http://localhost:3000"
      end
      
      class Comment < ActiveResource::Base
        self.site = "http://localhost:3000"
      end
    end
  end
end

Now we can play with our creation using the console. Make sure your server is running on port 3000, and startup script/console.

         
>> require 'ares_sample_client.rb'
=> ["Sample"]
>> posts = Sample::Client::API::Post.find(:all)
=> [#<Sample::Client::API::Post:0xb710bec4 @attributes={"updated_at"=>Wed Jan 09 02:36:34 UTC 2008, "id"=>1, "content"=>"The first post", "user_id"=>1, "created_at"=>Wed Jan 09 02:36:34 UTC 2008}, @prefix_options={}>]
>>

>> p = Sample::Client::API::Post.create
ActiveResource::UnauthorizedAccess: Failed with 401 Unauthorized
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:125:in `handle_response'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:112:in `request'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/connection.rb:101:in `post'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:803:in `create'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:636:in `save_without_validation'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/validations.rb:262:in `save'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:339:in `create'
        from /usr/lib/ruby/gems/1.8/gems/activesupport-2.0.2/lib/active_support/core_ext/object/misc.rb:28:in `returning'
        from /usr/lib/ruby/gems/1.8/gems/activeresource-2.0.2/lib/active_resource/base.rb:339:in `create'
        from (irb):3
>>

Our API works, and the authentication is as it should be. We didn't specify credentials, so we can't actually create a post. Let's fix up our client lib to add credentials

module Sample
  module Client
    class API
      class Post < ActiveResource::Base
        self.site = "http://test1:test1@localhost:3000"
      end
     
      class Comment < ActiveResource::Base
        self.site = "http://test1:test1@localhost:3000"
      end
    end
  end
end

Note that credentials apply to the whole class... this makes the API fairly easy to use, but makes using multiple users difficult. Anyway, with our authentication in place, let's try again

>> p = Sample::Client::API::Post.create(:content => "Should succeed", :user_id => 1)
=> #<Sample::Client::API::Post:0xb71b81d8 @attributes={"updated_at"=>Wed Jan 09 02:55:22 UTC 2008, "id"=>3, "content"=>"Should succeed", "user_id"=>1, "created_at"=>Wed Jan 09 02:55:22 UTC 2008}, @prefix_options={}>
>> p.save
=> true
>>

Sweet! Now we can not only browse posts and comments, but create them as well!

Contact

Send mail to mark dot mcbride at gmail dot com

April 2008: Monthly Archives

Pages

Powered by Movable Type 4.1

About this Archive

This page is a archive of recent entries in the rails category.

organization is the previous category.

ruby is the next category.

Find recent content on the main index or look in the archives to find all content.