jump to navigation

selectedIndex is your friend on IE6 November 5, 2008

Posted by reidmix in Code, Example, Javascript.
Tags: , , , , , , ,
3 comments

While working on getting ChainedSelects to work on IE6, I realized there are multiple ways to set an option as selected.  Since ChainedSelects creates all the options for a pre-existing select, I first used the defaultSelected argument passed in the Option constructor:

option = new Option("name", "value", true);

Here, the third argument specifies whether the option is defaultSelected, when the select is first created or the form is reset. This worked fine in Firefox but IE6 blythely ignored it.  So I tried passing a fourth argument that specifies whether the option is currently selected.

option = new Option("name", "value", true, true);

Again, Firefox does the right thing but IE6 gets wacky and incosistently selects the correct option or sometimes does nothing at all.  It seems as if IE6 misses the “selected” message on the event queue, because it works only when i put some alert()s in there.

So I tried another way, by using the selected attribute after creating the option:

option = new Option("name", "value", true, true);
option.selected = true

Again, inconsistent behavior on IE6.  I went to the great oracle and asked google and tried setting the selected attribute to ‘selected’, ‘true’, and ‘yes’ all with no luck.  I wonder if this occurs because the select is pre-existing and is not newly created.  This led me to try out the selectedIndex attribute on the select element.

select = $('myselect')
select.selectedIndex = 3

Bingo!  This works in both Firefox and IE.  The index is 0-based as you would guess (0 is the first option is select.options).

Subselector, Moneypenny November 1, 2008

Posted by reidmix in ActiveRecord, Code, Database, Example, Monkey Patch, Plugins & Gems, Rails.
Tags: , , , , ,
2 comments

Building on Josh and Damon‘s idea of Hacking a Subselect in ActiveRecord, I wondered if you could bake this kind of functionality into ActiveRecord.  So Doug and I went digging into the rails code, and came up with a plugin that adds subselects to ActiveRecord which we call Subselector.

So far, it only works on the Hash version of conditions.

On a column you wish to perform a subselect, pass a hash with :in, :not_in, :equals, or :not_equals as the only key.  The value is any of the options you normally would pass to ActiveRecord find.  Notice that we make sure to select a single column with the :select option:

Critic.find(:all, :conditions => { :id => {:in => {:select => :id, :conditions => {:active => true} } } })

Although the example may be contrived, here, we are looking for a critics that are in a set of active critics. The SQL:

select * from critics where id in (select id from critics where active = false)

You can see by default it runs the subselect on the table of outer select.  It gets more interesting you want to run a query on another ActiveRecord model:

Critic.find(:all, :conditions => { :id => {:in => {:model => :rankings, :select => :critic_id, :conditions =>
  {:week => 39} } } })

Here we set :model to :rankings.  Rankings is the ActiveRecord model to perform the find, notice we select the :critic_id column, the SQL is:

select * from critics where id in (select critic_id from rankings where week = 39)

And of course you can always just pass a string as a value to the subselect:

Critic.find(:all, :conditions => { :id => {:not_in => 'select id from critics where active = true' } })

Here’s how subselector can be used with the original example:

Post.find(:all, :conditions => :id => {:in => { :select => :post_id, :conditions => {:blog_id => self.id}, :order => "published_at DESC", :limit => options[:limit] || 10, :offset => options[:offset])} }, :order => "published_at DESC")

UPDATE: Subselector now likes Condition Arrays and Named Bind Variables.

Just pass the hash as a bind variable and specify the type (in/equals) of subselect in the string, make sure to enclose your ‘?’ inside parentheses:

Critic.find(:all, :conditions => ['id in (?)', {:select => :id, :conditions => 'active = true' }])
Critic.find(:all, :conditions => ['id not in (?)', {:select => :id, :conditions => {:active => false} }])
Critic.find(:all, :conditions => ['id in (?)', {:model => :rankings, :select => :critic_id, :conditions => {:week => 39} }])

As you can see, you can format the subselect hash just as above and can specify another model to run the subselect on. If you prefer to use named bind variable hashes, they still work (yay) as you would expect. And you can assign the subselect using them:

Critic.find(:all, :conditions => ['id in (:subselect)', {:subselect => {:select => :id, :conditions => {:active => false} } }])

UPDATE 2: Now with no ActiveRecord breakage

We’ve run the rails ActiveRecord tests without any problems. Let me know if you find any problems.

These Chains of Selects October 29, 2008

Posted by reidmix in Code, Example, Javascript.
Tags: , , , , , ,
3 comments

I was looking for javascript library that would manage select (HTML) elements that are dependent on one another.  These are the drop-downs you often see on online car sites where you select a make and it populates the next drop-down with the models for that car make.

I was hoping that there would be a libary that was publically available, object oriented, small, backed by JSON, and would even like it to be based on the Prototype library.  All I could find was a library that may satisfy one of my requirements but not all.  Libraries I found were either procedural, terribly bulky and complicated, and never backed by JSON.

In my buy it or build it moment, I decided to build it: here is a chained_selects javascript library.  It’s fairly lightweight, it runs in about 85 lines (comments and whitespace included), you can initialize it with javascript objects or JSON, it’s object oriented and relies on just a little bit of Prototype.

You need the heirarchy of data to back a ChainedSelect object, the form, and selects that are assoicated with each “level” in the heirarchy.  It can have as many levels you want in the hierarchy from 2 up.  So, in my car example, imagine you have a two car makes (Acura and Volkswagen) and the Acura make has two models (the RL and the TL, sweet!), and the Volkswagen make has three models (Beetle, Jetta, and the Passat).  Each of the models have a database id.  My JSON for this heirarchy would look like:

var data = {"Acura":{"RL":1, "TL":2},"Volkswagen":{"Beetle":3,"Jetta":4,"Passat":5}}

You can see the first level is the outer-most hash with the makes, and the inner mosts hashes have the models and their db ids.  For rails programmers, this could be a rails hash that you run to_json with.

I set up my HTML form, with a select for each level in the heirarchy, like so:

  <form id="cars" method="post" action="/choose_car">
    <select name="make"></select>
    <select name="model"></select>
  </form>

Then I can setup the ChainedSelect object to manage these selects, using the data above:

new ChainedSelect(data, 'cars', ['make','model']);

This says, created a chained select with my data for the form with id ‘cars’, with the first level select with the name ‘make’ and second level select with the name ‘model’.

The first select will load with three elements:

  • Choose make…
  • Acura
  • Volkswagen

When Volkswagen is selected, the second select will load:

  • Choose model…
  • Beetle
  • Jetta
  • Passat

If Acura is then chose, the second select will refresh with the correct data. If the ‘model’ is chosen let’s say Jetta, then the id for that model is sent in the form (4).  For rails developers, the name of the select needs to be different from the label (“Choose model…”), you can specify a label and a select name separately, with an embedded array, like so:

new ChainedSelect(data, 'cars', ['make',['car[model]','model']]);

Oftentimes, we have an edit page with the values already prefilled on our forms, we can add another array parameter that specifies the path to the selection:

new ChainedSelect(data, 'cars', ['make','model'], ['Volkswagen','Jetta']);

Lastly, when the final select is chosen, sometimes we want to take an action or an AJAX call, we can supply an onComplete function to run which is yielded a the value chosen:

new ChainedSelect(data, 'cars', ['make','model'], null, function(choice) { alert("You chose:"+choice); } );

You can use all of these elements together to get the behavior you want, I also have a little rails form helper to create my javascript for me:

  def chained_selects(tree, form, selects, active, oncomplete=nil)
    %(new ChainedSelects(#{tree}, '#{form}', #{selects.inspect}, #{active.inspect}, #{oncomplete || 'null'});)
  end

I know that the script can be improved in many ways (Doug McInnes added the onComplete feature).  Please feel free to send patches or any bugs you may find.
Enjoy!

Include Health October 29, 2008

Posted by reidmix in Example, Plugins & Gems, Rails.
Tags: , , , , ,
1 comment so far

I created a little plugin to make it easier to offer health check URLs in all our applications called Health.  These URLs are a necessity for our SysAdmins to hook into Big Brother or Nagios.  These monitoring systems systematically poll an application at this URL to check the status of the application.

To get started, install the health plugin and include it in your Application controller:

class ApplicationController < ActionController::Base
  include Health
end
&#91;/sourcecode&#93;

That's it.  You should be able to navigate to <a href="http://localhost:3000/check_heath">http://localhost:3000/check_heath</a>. Health sets up the named route '<span class="s1">check_health' and</span> by default it will check the connection of the database by running a "<strong>select 1</strong>" SQL statement.  If all goes well you should see "SERVERUP".  If there's a problem with the query you will see "DBDOWN" or an 500 error depending on the level of problem.  You can turn off the DB checking (our SysAdmins wanted it):


ApplicationController < ActionController::Base
  include Health
  health_check :with_db => false
end

One more trick, it does is handle any additional / custom checks that you may require. Pass a block, Proc, or a symbol that represents the name of the method to the health_check directive:

ApplicationController < ActionController::Base include Health health_check do |controller| controller.has_donut? || "no donut" end end [/sourcecode] In this case, assuming the controller has a method called has_donut? which does not return false or nil, you will see the “SERVERUP” message, otherwise you will see what was last evaluated in a message like “PROCDOWN no donut”

And you can mix and match all three types of blocks, Procs, and symbols — more examples are in the README.

Plug This: css_browser_selector plugin May 22, 2008

Posted by reidmix in Code, Plugins & Gems, Rails.
Tags: , , ,
3 comments

I’m happy to announce my first plugin based on the css_browser_selector javascript which adds the name of the browser and platform as css class selectors that your client is using on the html element in your page. For example:

<html class="gecko mac js">

I’ve released my first version of the plugin (at 0.9.1) and you can download it from on LATimes RubyForge project or from GitHub. You can read how to use the plugin in its README, it offers a way to include the javascript inline into your views and layouts:

<%= javascript_tag css_browser_selector %>

But wait there’s more! What the plugin also does is offer a body (and html) tag helper that will render css selectors into your views from the backend. The real benefit to this is that these selectors will be available within the page without needing javascript enabled:

<% body do %>
  <%= yield %>
<% end %>

And this will add the selectors as you would expect to your body tag:

<body class="gecko mac">

The body helper is currently aware of page caching and will revert to the javascript version if the page is being cached through the caches_page method.

This is my first plugin and, yes, I still have some work to do (complete my tests, better caching awareness, sensitivity to pre-existing body and html tag helpers), but I wanted to share back to the community. Following Giles Bowkett‘s mantra to release software (often and early).

There’s plenty of more options as explained by the README. I welcome feedback, suggestions, improvements, bugs, and diffs! I’d also like to thank Rafeal Lima who implemented and maintains the javascript and his help and support with the plugin

IE6 Accept Header is Faulty and Makes format.any Suck May 14, 2008

Posted by reidmix in AbstractRequest, ActionController, acts_as_authenticated, Changeset, Code, Example, MimeResponds, Monkey Patch, Plugins & Gems, Rails.
Tags: , , , , , , , , , ,
11 comments

I’ve been using the awesome acts_as_authenticated plugin as the basis for user login and authorization. Its access_denied method takes advantage of the ActionController::MimeResponds.any method which will be invoked on a login_required before filter:

    def access_denied
      respond_to do |format|
        format.html do
          store_location
          redirect_to new_session_path
        end
        format.any do
          request_http_basic_authentication 'Web Password'
        end
      end
    end

This code essentially says, if it’s an html request, store the original request and redirect to the login page, any other requests get the 401 Unauthorized header which the user agent can then use to initiate an HTTP Auth. This bit of code only works correctly with Edge Rail’s 8987 Changeset that allows any to act as a catch-all for any request mime-types not already specified.

Awesome!
Except when Internet Explorer 6 comes into the picture and happily sends this
strange and incomplete ‘Accept:’ header:

image/gif, image/x-xbitmap, image/jpeg, image/pjpeg, application/x-shockwave-flash, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/msword, */*

As you can see, it doesn’t specify application/xhtml+xml or even text/html and what’s worse is the subsequent requests to the same page sends the catch-all */*.

What does this all mean?
When IE6 navigates to a page that requires login, instead of redirecting the user to the login page, the user is presented with an HTTP Auth Dialog. What’s worse is that in my application, cookies and other application state for an web-based end-user is set outside of the login_from_basic_auth method — you know — in my login action.

HTTP Auth Prompt by IE6

My guess is when the format.any fix goes out in the next rails release, IE6 users on acts_as_authenticated sites are going to be sad-faced. I’m not entirely sure the correct way to fix this problem but this is how I solved it in my application:

class ActionController::AbstractRequest
  def accepts_with_faulty_header
    @env['HTTP_ACCEPT']='*/*' if @env['HTTP_USER_AGENT'] =~ /msie/i and @env['HTTP_USER_AGENT'] !~ /opera|webtv/i
    accepts_without_faulty_header
  end
  alias_method_chain :accepts, :faulty_header
end

Originally, I thought to patch the Mime::Type.parse function, but decided that I wanted to make sure to look at the user agent for IE6. As you can see, I switch the HTTP_ACCEPT header to the catch-all */* string if the original HTTP_ACCEPT header is a match to the exact (bizarre) IE6 string and the HTTP_USER_AGENT is IE (matches MSIE but not Opera or WebTV). Then I call the original ActionController::AbstractRequest.accepts method.

I’d love to hear your ideas and suggestions — and if others have run into this problem as I didn’t find many users have this exact problem and how they may have solved it.

Update: Based on the conversation in the comments, I’ve re-written the patch to always change the HTTP_ACCEPT header to the catch-all */* if it is an IE Browser.

Use ActiveResource to Consume Simple Objects Over REST January 16, 2008

Posted by reidmix in ActiveResource, Code, Example, Rails.
Tags:
add a comment

I’m sure that many organizations have services available via REST that are not produced by Rails and do not follow Rails conventions. That doesn’t mean you have to write your own client. For simple objects, use ActiveResource.

To take advantage of ActiveResource in this way use the ActiveResource::Base#find method. You need to find(:all), find(:first), or find(:one) but you need to pass a string of the path to the :from option along with any :params you need. If you pass a symbol as the :from option, this will not work and will employ all of the the ActiveResource::Base mechanics. A string will bypass this, for example:

class RestClient < ActiveResource::Base
  self.site = "http://java-based-service.company.com"

  def self.invoke(params = {})
    find(:one, :from => "/nonconventional/path/to/rest/service/", :params => params)
  end
end

object = RestClient.invoke(:irregular_id => 10) #=> service returns <data><name>reid</name><role>blogger</role></data>
object.name #=> reid
object.role #=> blogger

Passing Hashes to Partials September 13, 2007

Posted by reidmix in Code, Example, Hash, Partial, Rails, Ruby, SimpleDelegator.
add a comment

Sometimes you need to pass a Hash or collection of Hashes to a partial. In either case, when render tries to determine if you are using the old style method (render_partial) or the new style. When it comes across your hash, rails believes it’s dealing with all your supplied parameters to render. You can see this logic in action_view/partials.rb:

module ActionView
	module Partials
		def render_partial(partial_path, local_assigns = nil, deprecated_local_assigns = nil) #:nodoc:
			path, partial_name = partial_pieces(partial_path)
			object = extracting_object(partial_name, local_assigns, deprecated_local_assigns)
			local_assigns = extract_local_assigns(local_assigns, deprecated_local_assigns)
			local_assigns = local_assigns ? local_assigns.clone : {}
			...
		end
	end
end

You can see the last line shown in the method, where if it can’t determine a local_assigns, it just uses an empty Hash. And so that’s how it surfaces, you pass your Hash to the partial and nothing gets rendered. What we need to do is disguise that we are passing a Hash but have all the functionality of one.

Ruby gives us a few options but the easiest is SimpleDelegator. All we need to do is pass our object upon initialization and SimpleDelegator will delegate all methods to that object:

myhash = { :a => "test1", :b => "test2" }
d = SimpleDelegator.new(myhash)
puts d[:a] # => test1

If sometimes have collections of Hashes (or Hashes mixed in with my collection), I employ the following pattern:

collection = [collection] unless collection.is_a? Array
collection = collection.collect { |i| (i.is_a? Hash) ? SimpleDelegator.new(i) : i }
render :partial => "mypartial", :object => collection.compat

Patching Capistrano to use CVS over SSH September 11, 2007

Posted by reidmix in Capistrano, Code, Command Line, CVS, Monkey Patch, Ruby, SSH.
4 comments

At work we still use CVS as our versioning repository and most of our systems rely on CVS to build and deploy into production. Currently there is no effort to transition to SVN, so we need to develop a patch to Capistrano to allow our projects to deploy to our development lab. We needed to extend Capistrano handle SSH logins to CVS and handle CVS repository directories.

Overall, both changes needed to be applied to the checkout method in Capistrano’s Cvs class.

We use SSH as the underlying communication mechanism for CVS but there is a difference in the password prompt: SSH’s is capitalized. We just need to make the prompt check case insensitive with the i switch:

if out =~ %r{password:}i

When setting up Capistrano (v1.4) for use in our development lab, the :application configuration is not sufficient to identify our rails project the CVS tree. We are not cool enough to setup or use modules to map into our repository directories, so our projects are usually in some form of webdev/project/appname. To handle this, i create an additional configuration parameter called :project in the deploy.rb:

set :project, "cvs/dir/to/myproj"

And we no longer use the application name for the CVS path, we use the project variable:

project = configuration[:project] || actor.application
command = &lt;&lt;-CMD
  cd #{configuration.releases_path};
  CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q #{op} -D "#{configuration.revision}" #{branch_option} -d #{File.basename(actor.release_path)} #{project};
CMD

The hardest part is patching Capistrano. Capistrano’s code gets loaded well after any library or environment gets loaded. The trick is to use the method_added hook to wait for the checkout method to get loaded and then alias the method to the patched one, we need to keep a variable around to check if we’ve patched the method so we don’t end up in an infinite loop:

@@checkout_patched = false
def Cvs.method_added(id)
  if id.id2name == "checkout"
    unless @@checkout_patched
      @@checkout_patched = true
      alias_method :checkout, :checkout_patch
    end
  end
end

I’ve not checked to see how these changes work or differ in Capistrano 2. But with all these elements together using Capistrano v1.4, I can drop this file in my lib directory or create a plugin with it. Together, here is the full patch which I have in a file called capistrano_cvs_ext.rb:

module Capistrano
  module SCM
    class Cvs &lt; Base
      @@checkout_patched = false
      def Cvs.method_added(id)
        if id.id2name == "checkout"
          unless @@checkout_patched
            @@checkout_patched = true
            alias_method :checkout, :checkout_patch
          end
        end
      end

      def checkout_patch(actor)
        cvs = configuration[:cvs] || "cvs"
        cvs_rsh = configuration[:cvs_rsh] || ENV['CVS_RSH'] || "ssh"

        if "HEAD" == configuration.branch then
            branch_option = ""
        else
            branch_option = "-r #{configuration.branch}"
        end

        # cvs has a root and repository, repository is not the same as application name as it can be a path
        op = configuration[:checkout] || "co"
        project = configuration[:project] || actor.application

        command = &lt;&lt;-CMD
          cd #{configuration.releases_path};
          CVS_RSH="#{cvs_rsh}" #{cvs} -d #{configuration.repository} -Q #{op} -D "#{configuration.revision}" #{branch_option} -d #{File.basename(actor.release_path)} #{project};
        CMD

        run_checkout(actor, command) do |ch, stream, out|
          prefix = "#{stream} :: #{ch[:host]}"
          actor.logger.info out, prefix
          if out =~ %r{password:}i  # SSH asks for "Password" with a capital P
            actor.logger.info "CVS is asking for a password", prefix
            ch.send_data "#{actor.password}n"
          elsif out =~ %r{^Enter passphrase}
            message = "CVS needs your key's passphrase and cannot proceed"
            actor.logger.info message, prefix
            raise message
          end
        end
      end
    end
  end
end