Pagination, Sets, Checkboxes

Posted by Chuck Vose Mon, 25 Aug 2008 20:29:00 GMT

When I was at RailsConf2008 I learned a lot about life and Rails but there were some nagging problems that lingered after me for a long time.

At one point in the talks there was a discussion of how to make listing pages have checkboxes and how to handle this data. The problem is complicated by pagination and filtering so it’s actually a really good problem to talk about.

The proposed solution was to send back a structure that looked something like this:

[{:id => 1, :activated => "+", :contact => "-"}, ...]

At this point I was probably playing pogs in the audience with Ryan Schenk so the details are lost to time but the concept remains.

It seems needlessly complex though. A very interesting idea, no doubt about it, and it certainly has its merit but it seems very complex to me when we’re talking about booleans. Also, I feel like it’s worth mentioning that this method is much faster than the below method but somehow that doesn’t turn me off that badly.

Instead of the above I took the already well accepted practice of using the check_box_tag in the view with the addition of a hidden field which would tell me which objects to care about:

<% @users.each do |user| %>
  <%= hidden_field_tag('seen[]', user.id) -%>
  <%= check_box_tag 'activated[]', user.id -%>
<% end %>

In the controller it’s as easy as loading up params[:seen] and loading each object:

def index
  if request.post?
    activated_ids = params[:activated].collect {|id| id.to_i} if params[:activated]
    seen_ids = params[:seen].collect {|id| id.to_i} if params[:seen]

    if activated_ids
      seen_ids.each do |id|
        r = User.find_by_id(id)
        r.activated = activated_ids.include?(id)
        r.save
      end
    end
  end
end

So what I don’t get is whether this is wrong on some moral stance or if I really didn’t understand what the presenter was getting at. Yes, he loads and saves fewer objects, which could be important. But he also introduced what seems to me to be a lot of complexity into a fairly simple process.

At any rate, this code is here in case someone needs to find it until I find what the presenter was proposing. I seem to recall being impressed at the time so there must be something I’ve forgotten.

Posted in , ,  | Tags , , , , , , , , , , ,  | no comments

find_or_create by params (extension to dynamic attribute based finders)

Posted by Chuck Vose Thu, 13 Sep 2007 03:40:00 GMT

Rails has a dynamic method where you can do find_or_create_by_attr_name(attr) but it the method names get incredibly long very quickly. So SJS wrote this article in which he tries to remedy the situation. His solution was pretty elegant but it still only worked for fields that were already defined in the database; if you often redefine setter methods it doesn’t work at all.

Here’s my attempt to remedy the situation.

module ActiveRecordExtensions
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def find_or_create(params)
      begin
        return self.find(params[:id])
      rescue ActiveRecord::RecordNotFound => e
        attrs = {}

        # search for valid attributes in params
        self.column_names.map(&:to_sym).each do |attrib|
          # skip unknown columns, and the id field
          next if params[attrib].nil? || attrib == :id

          attrs[attrib] = params[attrib]
        end

        # call the appropriate ActiveRecord finder method
        found = self.send("find_by_#{attrs.keys.join('_and_')}", *attrs.values) if !attrs.empty?

        if found && !found.nil?
          return found
        else
          return self.create(params)
        end
      end
    end
    alias create_or_find find_or_create
  end
end

ActiveRecord::Base.send(:include, ActiveRecordExtensions)

Newb instructions

Create a file called active_record_extensions.rb in your the lib directory. Then add `require ‘active_record_extensions’` to your environment.rb at the bottom (without the ``). Restart your server and see what happens!

Posted in ,  | Tags , , , , , , , ,

Long Bets

Posted by Chuck Vose Wed, 12 Sep 2007 04:42:00 GMT

Sam Ruby occasionally does an article about his long bets and I really respect his ability to predict the future. This time I feel that he was focusing too close and forgot the long part of the ‘long bet’. REST is already important, and there are further developments in edge rails that make it even more important (active resource). MySQL is already a pain for scaling and the plugins are starting to come out but haven’t really hit the fan yet.

Bet One – MySQL becomes the exception

First, I think Sam Ruby is dead on about databases. Right now there are only a few things that are a consistent pain for deployment and scaling and databases are the top of the heap. I know there are things like MaxDB for mysql and there are ways to set up ring replication but it’s hard; something that Ruby’s community is really good at solving.

My first bet is that MySQL dies out for the rails community and something new pops in. It wouldn’t surprise me if it was Mnesia from Erlang/OTP or something involving Hadoop and HBase. It would surprise me if CouchDB popped onto the scene in a big way but it’s the sort of thinking that could lead us somewhere interesting. The other option is that someone comes up with a really concrete stack of abstractions that makes it easy to balance mysql requests and writes.

Bet Two – Rails drops REST completely

Secondly, I think REST is the wrong way to move forward. REST maps very well onto the CRUD principles, but I feel like we very rarely actually use just CRUD. More often than not I want to run custom little things and create crazy associations. And I realize that this is all possible in the REST model, but it makes the controllers obscene sometimes.

What we really want is Query Language for the Internet and what better language to build that in than Ruby. I’ve seen DSL’s for direct database access and it seems like the routes would be just around the corner if this is where we decide to take it.

The question is whether DHH sees the writing on the wall or desperately wants to hang on to REST. Since he’s put so much effort into the REST idea it seems like he would be loath to drop it, but at the same time he’s an incredibly mature developer and would hopefully handle a change like this if it ever happened.

Conclusion

All respect to Sam Ruby, I really do respect his predictions over my own. But I think his predictions are too close to us right now. I would like to know what happens after REST and what happens in the database arena.

Posted in , ,  | Tags , , , , , , , , , , ,

Inheritance without STI: method_missing routing

Posted by Chuck Vose Mon, 13 Aug 2007 22:05:00 GMT

Every once in a while I find myself in the situation where I need to override a model or create an inheritance without using single table inheritance (STI).

The method_missing call can help us to create an intelligent router that will route calls to the parent class or the child while maintaining an extremely small code footprint.

Here’s an example of what I’m talking about. We needed a landing page that would be dynamic for each user that wanted one. But we needed a template landing page which just needed the values substituted in at the right spot. Here’s a quick Yaml of what the table might look like.

# Example Page
landing_page_template:
  title: Welcome to [COMPANY-NAME]
  meta_keywords: [COMPANY-NAME], [COMPANY-META-KEYWORDS]
  ...

# Example Landing Page
landing_page:
  company_name: CV, Inc. 
  company_logo: www.chuckvose.com/fake_logo.gif
  company_meta_keywords: happy, rails, method_missing, inheritance
  ...

So here’s the hope, when we load up the LandingPage object and call LandingPage#title it should display “Welcome to CV, Inc.”

Pseudo-code and implementation

def method_missing(method_name, args*)
  if method_name is in the extended database
    call method_name
  if method_name isn't in the extended database
    try calling parent.method_name
      return results directly or mangle them somehow

We chose to mangle the results but if you just wanted to add a couple columns you could. You could run into performance issues doing this instead of STI. However this is an absolutely ideal place to mangle the code too if you want. Probably as good as a controller after_filter.

Two steps needed to happen for this, we need to load up the template on initialization, then use method_missing to switch between calls to the LandingPage class (such as :company_name) and the Page class (such as title).

Step 1: Load the template

The first part is a little weird because of rails performance. If you haven’t used after_find/after_initialize rails-core had to put in a little kludge for performance reasons. The solution is just to define a blank function named after_find or after_initialize.

class LandingPage < ActiveRecord::Base
  after_initialize :find_template

  # Required to activate the after_initialize filter
  def after_initialize; end 

  private

  def find_template
    @template = Page.find_by_url("landing_page_template")
  end
end

Step 2: Create the method_missing switcher

The method_missing switcher had to incorporate two things and one trick. First, it needed to look in the LandingPage table for its own attributes. Then it needed to look in the Page table for anything else before passing it through a regex engine and returning. The last little trick was that the regex engine needed to only be run on Strings, trying to regex a boolean doesn’t work very well. This allows the plethora of query functions such as Page.valid?.

class LandingPage < ActiveRecord::Base

  private

  def method_missing(meth, *args)
    # See if this attribute is in this object already. 
    # If so just call out to super and let 
    # ActiveRecord::Base deal with trying to find the 
    # attribute in the database. 
    if @attributes.include?(meth.to_s) || @attributes.include?(meth.to_s.gsub(/(=|_before_type_cast)/, ""))
      super
    else
      # Call Page#meth and store the response. Usually something like @page.body or @page.title
      resp = @template.send(meth)

      # If the attribute returned from above is a String, toss it into the regex grinder
      # and return the results. 
      if resp.is_a?(String)
        return resp.html_sub(self)

      # If the response is a boolean, symbol, or something else, just return it since we
      # can't (and probably don't need to) regex it.
      else
        return resp
      end
    end
  end
end

Step 3: The regex grinder

Security Warning: Before I begin this I want people to know that it should never be used by people outside your direct trust. It calls ruby code directly off of text found in the database. It can be rewritten to be more secure but this wasn’t necessary for us. If you want slightly more security drop some constraints on the LandingPage.send() method. For even more security do one regex per tag to replace (such as html = html.gsub(/\[MERCHANT-NAME\]/, template.merchant_name || ””). Security Warning

The last step is totally up to you. We needed it to find things like [MERCHANT-NAME] and replace them with LandingPage#merchant_name. This can be done fairly simply monkey-patching String with a blocked gsub.

class String
  # Look for snippets like [MERCHANT-NAME] and replace 
  # them with LandingPage#merchant_name
  def html_sub(landing_page)
    html = self # self cannot and should not be modified directly.

    # Look for [WHATEVER] tags
    html = html.gsub(/\[[^\]]+?\]/) do |match|
      # For each match, call the LandingPage instance method 
      # with the same name and return those results or the empty
      # string.
      landing_page.send(match.gsub(/[\[\]]/, "").gsub(/-/, "_").downcase) || "" 
    end
  end
end

Conclusion

The method_missing call seems to be a rough spot for a lot of discussions. Why_ wrote an article (The Best of Method Missing) that shows this pretty well: he starts by explaining how he has hated every piece of code he’s written with it then proceeds to point out some method_missing code that he loves. Lots of people feel this way about it and about many aspects of Rails in general.

But sometimes it’s a wonderful thing. This example should have taken several hundred lines of code and lots of kludges and bugs, but method missing trimmed it to ~20 LOC and we haven’t been able to find a bug yet.

Hope this helps you with some project, I’ve enjoyed playing with it and writing it up.

Posted in  | Tags , , , , , , ,

Nginx http_perl_module

Posted by Chuck Vose Tue, 07 Aug 2007 17:24:00 GMT

Errors you might get at this step:
Can't locate nginx.pm in @INC
Can't locate loadable object for module nginx in @INC
This is how I installed the http_perl_module for Nginx. I'm not sure why these things are the way they are, I'm following a mailing list that was written in russian. :) Installing from ~/src/nginx-0.6.5 Installing to /opt/local/nginx (if this isn't owned by you use append sudo to the last step)
cd ~/src/nginx-0.6.5
./configure --with-http_perl_module --with-http_ssl_module --prefix=/opt/local/nginx/ --with-perl_modules_path=/opt/local/nginx/modules/perl
make
make install
sudo /opt/local/nginx/sbin/nginx
Now we hope.

Tags: , ,

Posted in  | Tags , , ,

Rendering partials with a 302 breaks ie6

Posted by Chuck Vose Wed, 02 May 2007 21:50:00 GMT

If you can spot what’s wrong with this code you get a cookie. Apparently rendering with a 302 breaks ie6 entirely. Just deleting the 302 makes it okay.

render :partial => 'admin/restricted', :layout => 'admin', :status => "302" and return false

Whether it makes sense or not is not the question. I’m pretty confident that it doesn’t make sense after thinking about it for a while. But why would it make ie6 connections die while firefox et al are fine with it?

Posted in  | Tags , , ,

Testing rails without fixtures

Posted by Chuck Vose Wed, 25 Apr 2007 21:21:00 GMT

At On & On Creative we have a lot of sites on a common rails backend. It’s incredibly flexible so we can offer a lot of support to customers but because there are so many customers with different needs testing has become a huge pain. Not only does it let us run tests custom to each client, it provides a way to import the data to the test db really easily and it’s way faster than loading the fixtures each time you start a test. Yay!

After googling around I happened upon this caboo.se article about how to run without fixtures at all. It’s a very indepth article with lots of good things in it; far be it for me to try to compete.

Instead, here’s the low down on how we prepare each of our sites for testing.

Add the following to the end of your rails_root Rakefile:

class Rake::Task def detract(prerequisite) @prerequisites.delete(prerequisite) end end %w(units functionals integration recent uncommitted).each do |task| Rake::Task["test:#{task}"].detract('db:test:prepare') Rake::Task["test:#{task}"].enhance(['environment']) end

Add the following to lib/tasks/fixtures.rake. Make sure to look at the two commented lines:

# This code courtesy of the "Rails Recipe's book":http://www.pragmaticprogrammer.com/titles/fr_rr/ I believe. namespace :db do namespace :fixtures do desc 'Create YAML test fixtures from data in an existing database. Defaults to development database. Set RAILS_ENV to override.' task :dump => :environment do # Remove the limit if you want to. It's a timesaver mostly sql = "SELECT * FROM %s LIMIT 0,1000" # Add tables you don't want dumped. skip_tables = ["schema_info", "plugin_schema_info", "engine_schema_info", "sessions"] ActiveRecord::Base.establish_connection(:development) (ActiveRecord::Base.connection.tables - skip_tables).each do |table_name| i = "000" File.open("#{RAILS_ROOT}/test/fixtures/#{table_name}.yml", 'w') do |file| data = ActiveRecord::Base.connection.select_all(sql % table_name) file.write data.inject({}) { |hash, record| hash["#{table_name}_#{i.succ!}"] = record hash }.to_yaml end end end end end

Then the fun easy stuff happens:

rake db:fixtures:dump RAILS_ENV=production rake db:fixtures:load RAILS_ENV=test rake test:plugins PLUGIN=your_plugin

If you want to commit the fixtures to the repo you can but it seems a waste to me since you might be regenerating them often.

Also, check the two comments in the fixtures.rake, it could be doing things you don’t expect.

Posted in ,  | Tags , , , ,

RailsMachine / Slingshot image loader slowdown

Posted by Chuck Vose Mon, 23 Apr 2007 00:56:00 GMT

Background / Analysis

Recently a fellow asked me to help him diagnose a slowdown in his rails stack at the Ruby Brigade meeting in Seattle.

After going through the normal procedures of checking mysql indexes, talking about caching, and checking the code for bizarreness I still couldn’t find anything really out of the ordinary.

Finally we turned to Firebug and found that the images were taking an eternity to download. This had happened to On & On Creative earlier on this year so the solution was quick at hand.

Solution

RailsMachine and the mongrel docs themselves recommend the following code for apache:

# Redirect all non-static requests to cluster RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ balancer://NEF_mongrel_cluster%{REQUEST_URI} [P,QSA,L]

But there’s a hidden gotcha that I still haven’t fully figured out. Sometimes, usually when images aren’t in the top level of /images, apache will not match the RewriteCond and pass the image to mongrel instead of loading it itself.

Mongrel is terrible at serving images and static content from the filesystem. In the words of Zed Shaw, “This means mongrel will serve images, javascript, files, and everything else. It’s quite fast at this, but Apache can do it better.”

1 http://mongrel.rubyforge.org/docs/apache.html

So if you see a slowdown on images, you run mongrel through apache’s mod_proxy_balancer, and you used the config from the mongrel website or from RailsMachine/Slingshot’s capistrano script, try this code and see if it helps:

# Redirect all non-static requests to cluster RewriteCond %{REQUEST_FILENAME} !.*(jpg|gif|png|js|css)$ RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f RewriteRule ^/(.*)$ balancer://NEF_mongrel_cluster%{REQUEST_URI} [P,QSA,L]

Did It Work?

Checking the rewrite logs

My computer is exceedingly slow so I can’t rely on my browser to tell me how fast an image is really downloading. If you’re like me it might be worth watching the rewrite logs to see if the images are being passed through rather than being rendered.

Drop this into your virtual host configuration right after the RewriteEngine On:

RewriteLog logs/myapp_rewrite_log RewriteLogLevel 9

Then check the apache logs folder (often /var/log/httpd or /usr/local/apache2/logs) for the myapp_rewrite_log. After loading a page it should look something like this:

init rewrite engine with requested uri /images/drivers_panel.jpg applying pattern '^/(.*)$' to uri '/images/drivers_panel.jpg' RewriteCond: input='/var/www/apps/NEF/current/public//images/drivers_panel.jpg' pattern='!.*(jpg|gif|png|js|css)' => not-matched pass through /images/drivers_panel.jpg

Rather than this contrived example:

init rewrite engine with requested uri /images/extra/dirs/my_image.gif applying pattern '^/(.*)$' to uri '/images/extra/dirs/my_image.gif' RewriteCond: input='/var/www/apps/myapp/current/public/images/extra/dirs/my_image.gif' pattern='!-f' => matched rewrite '/images/extra/dirs/my_image.gif' -> 'balancer://myapp_mongrel_cluster/' forcing proxy-throughput with balancer://myapp_mongrel_cluster/ go-ahead with proxy request proxy:balancer://myapp_mongrel_cluster/ [OK]

Checking the rails logs

The rewrite logs are cool and handy so I put them first but you should also be seeing entries in your development.log or production.log. Try the following snippet, you may be surprised to see the results:

cat development.log production.log | egrep '(jpg|png|gif|js|css)' | less

Posted in  | Tags , , , , , , , , ,

Rmagick OS X Verdana

Posted by Chuck Vose Sat, 17 Mar 2007 22:18:00 GMT

If you’re getting errors about Verdana not being found, specifically:

“unable to read font `/Library/Fonts/Verdana’ (Magick::ImageMagickError)”

try:

sudo apt-get install applesystemfonts
cp /sw/lib/X11/fonts/applettf/Verdana.ttf /Library/Fonts/
sudo gem install rmagick

Posted in

Recursive children

Posted by Chuck Vose Wed, 24 May 2006 21:13:00 GMT

One of the things sorely missing from Rails (and with good reason) is a function to find all the children in a tree. Below is my attempt at it, you can drop it in any model that acts_as_tree and call all_children to get a flattened list of all children, grandchildren, etc.

  def all_children
    all_children = []
    all_children << list_children(self)
    return all_children.flatten
  end

  private

  def list_children(branch)
    all_children = []
    for child in branch.children
      all_children << list_children(child)
    end

    if branch.children.empty?
      return branch
    else
      all_children << branch
      return all_children
    end
  end

There’s probably a lot of reasons for this not existing but sometimes you need the limited functionality that this provides and can work within the constraints of making sure that no children have two parents.

The other approach is to use acts_as_nested_set but you sometimes lose valuable data such as the self_and_siblings function from acts_as_tree.

Posted in

Older posts: 1 2