On updating vs. editing.

Posted almost 7 years back at The Hobo Blog

We recently had some great posts on the forum from larryk. I was just replying to Larry, and, what with the cup of rather fine Darjeeling, and an indulgently large pile of mini gingerbread-man biscuits, I got into the swing of it until I thought - this is a blog post!

Quick background – Larry has a model with 60(!) fields, and he has a page with <editor> tags for all of them. He’s discovered an O(n^2) problem because each call to <edit> calls updatable_by? which has to check all 60 fields to see what has changed.

Not pretty.

First of all, some background on the rationale behind the design of editable_by and updatable_by.

The Hobo permission system serves two totally different roles:

  1. Policing POST and PUT requests from the client
  2. Automatically adjusting the view rendering so people see only what is appropriate for them.

For 1, we have to cope with the fact that a single request can change many fields, so we have the moethod updatable_by? which is responsible for allowing/denying the overall change to the object. We know what the old state is, we know what the new state is, so we ask “is this change ok?”

For 2, we want to know something entirely different – is a user allowed to edit a specific field? For that (as spotted by Matt in that same thread) you can write a method editable_by? which takes the user and the name of the field they want to edit. There’s no “new” value in this case, because how do we know what the user will do?

Hang on though – the hobo_model generator doesn’t even create a stub for editable_by?. Why not?

Well although editable_by? and updatable_by? are quite different questions, the underlying application logic is always going to be the same. updatable_by? has to cover every eventuality, so in theory it should be possible to somehow derive editable_by? from updatable_by?.

In theory.

Well it turns out there’s a clever trick that does something along those lines.

Hmmm. Clever trick. Clever tricks can be good and they can be veeeeeery bad. Right now I’m still leaning towards liking this particular trick, but I’m not totally sure yet.

It goes like this. Hobo defined an extremely volatile type Hobo::Undefined. If you so much as look at one of these it goes BANG! (raises a Hobo::UndefinedAccessError). When Hobo needs to know if field ‘foo’ is editable by user Fred, it looks for the editable_by? method. If that’s not there the clever trick comes into play. Hobo creates a “hypothetical” new object, exactly the same as the current one, but field ‘foo’ has the value Hobo::Undefined. Hobo can now ask if the object is updatable_by? the current user, passing this tricky little thing as the new state of the object.

Now if your updatable_by? method depends on the value of ‘foo’ in any way - BANG! Hobo catches the exception and says nope – not editable.

It’s not fool-proof, but it gives the correct results in many situations, and if it lets you down, well you’ll just have to write your editable_by? method. Make the common things easy, keep the uncommon things possible.

Back to Larry’s problem, editable_by? will generally be quicker than updatable_by? as it doesn’t have to check for all the many things that might have been done to an object. So defining editable_by? should improve things a lot.

We’ve been thinking about the whole morph-the-view-to-the-user thing though, and we’ve come to a conclusion that should make things even faster. The behaviour of <editor> where it automatically degrades to a view is pretty nifty, and very useful in some situations, but in our apps at least 95% of the calls to <editor> are always going to result in an editor. The overhead of going to the permission system is really undesirable.

So we’ll probably make the auto-degrade feature optional, and the default will be off. If you want an editor that will degrade to a view when the user has no edit permission, you’ll need to do something like:

<editor or_view/>

I actually prefer that as it says more clearly what you’re getting – an editor, or possibly a view.

We can do something similar for the few tags that render nothing when the user is not allowed to perform the action in question, e.g.

<delete_button if_allowed/>

Quiz

Posted almost 7 years back at The Hobo Blog

This neat hack just popped into my head – something I’ve wanted for ages. But what is it for?

    class Object
      def _?()
        self
      end
    end

    class NilClass
      def _?()
        SafeNil.new
      end
    end

    class SafeNil
      def method_missing(*args, &b)
        nil.send(*args, &b) rescue nil
      end
    end

Quiz

Posted almost 7 years back at The Hobo Blog

This neat hack just popped into my head – something I’ve wanted for ages. But what is it for?

    class Object
      def _?()
        self
      end
    end

    class NilClass
      def _?()
        SafeNil.new
      end
    end

    class SafeNil
      def method_missing(*args, &b)
        nil.send(*args, &b) rescue nil
      end
    end

Hobo 0.6.1 released

Posted almost 7 years back at The Hobo Blog

Not too bad – only one day later than advertised :-) It’s time to

gem update hobo

or

svn up vendor/plugins/hobo

Not content to fix a few of the problems in 0.6, we’ve added some major new features in 0.6.1

There’s a major overhaul to the Ajax part update mechanism. It’s now more secure and can cope with parts that need access to local variables.

Hobo now supports multiple user models, so you could for example have a separate model for regular users and administrators. Each has its own login and signup pages.

As always, see the changelog for the nitty gritty.

Hobo 0.6.1 released

Posted almost 7 years back at The Hobo Blog

Not too bad – only one day later than advertised :-) It’s time to

gem update hobo

or

svn up vendor/plugins/hobo

Not content to fix a few of the problems in 0.6, we’ve added some major new features in 0.6.1

There’s a major overhaul to the Ajax part update mechanism. It’s now more secure and can cope with parts that need access to local variables.

Hobo now supports multiple user models, so you could for example have a separate model for regular users and administrators. Each has its own login and signup pages.

As always, see the changelog for the nitty gritty.

Weblog update

Posted almost 7 years back at Revolution On Rails

Hey all I wanted to give you an update:

Three of our active contributors are moving on: Aaron, Eddie, and Val. They plan to stay active in the Rails community, as well as new areas like Facebook. You can follow their work at blog.hungrymachine.com. It has been a great pleasure to work with them and we wish them the best.

Weblog update

Posted almost 7 years back at Revolution On Rails

Hey all I wanted to give you an update:

Three of our active contributors are moving on: Aaron, Eddie, and Val. They plan to stay active in the Rails community, as well as new areas like Facebook. You can follow their work at blog.hungrymachine.com. It has been a great pleasure to work with them and we wish them the best.

eRubyCon - Bruce Tate - Ruby on Rails Podcast

Posted almost 7 years back at Ruby on Rails Podcast

Robert Stevenson interviews Bruce Tate about Changing the Present and Ruby in the enterprise.
From eRubyCon in Columbus, OH.

Methodphitamine - I'm hooked without even using it

Posted almost 7 years back at The Hobo Blog

Now that is going straight into Hobo’s core extensions. Thanks for sharing Jay!

Meaning I can finally get rid of all that omap, oselect nonsense (if you didn’t notice those methods, don’t even ask, it was a bad idea. If you did and you use them - stop!)

Hmmm. Maybe it’s time to make this stuff available in a separate gem. HoboSupport? It’s starting to bug me when I’m in a non-Hobo script and all this stuff is missing.

p.s. Hobo 0.6.1 coming today!

Methodphitamine - I'm hooked without even using it

Posted almost 7 years back at The Hobo Blog

Now that is going straight into Hobo’s core extensions. Thanks for sharing Jay!

Meaning I can finally get rid of all that omap, oselect nonsense (if you didn’t notice those methods, don’t even ask, it was a bad idea. If you did and you use them - stop!)

Hmmm. Maybe it’s time to make this stuff available in a separate gem. HoboSupport? It’s starting to bug me when I’m in a non-Hobo script and all this stuff is missing.

p.s. Hobo 0.6.1 coming today!

Ruby Hoedown Videos

Posted about 7 years back at Alloy Code - Home

The Ruby Hoedown conference sessions are now available online from the Confreaks website.

Personally, my top picks for “must view” are:

  1. Using C to tune your Ruby (or Rails) Application
  2. Charity testing workshop
  3. Exploring Merb

Interestingly, while I was listening to Ezra’s presentation on Merb the first time, I kind of missed the point. The real power and purpose of Merb didn’t start sinking in for me until the second day, when I realized that there was a much bigger audience for a thread-safe, ActionPack-decoupled, performance tuned web framework. The validity of Ezra’s talk was reinforced by others, particularly Bruce Tate, who helped push the point home that Rails was just one stop on the Ruby journey. And, of course, it’s especially timely, considering the current dust-up over Pete’s Swanky Framework

Ruby Hoedown Videos

Posted about 7 years back at Alloy Code - Home

The Ruby Hoedown conference sessions are now available online from the Confreaks website.

Personally, my top picks for “must view” are:

  1. Using C to tune your Ruby (or Rails) Application
  2. Charity testing workshop
  3. Exploring Merb

Interestingly, while I was listening to Ezra’s presentation on Merb the first time, I kind of missed the point. The real power and purpose of Merb didn’t start sinking in for me until the second day, when I realized that there was a much bigger audience for a thread-safe, ActionPack-decoupled, performance tuned web framework. The validity of Ezra’s talk was reinforced by others, particularly Bruce Tate, who helped push the point home that Rails was just one stop on the Ruby journey. And, of course, it’s especially timely, considering the current dust-up over Pete’s Swanky Framework

Go get it

Posted about 7 years back at The Hobo Blog

And finally…

Also note we’re now on rubyforge so you could always just gem update hobo (subject to rubyforge delays – I only threw it up there one minute ago)

The demos on the site have not been updated, nor has the manual I’m afraid to say. But work on updating the manual is already underway so hopefully it won’t be all that long.

Have fun!

Go get it

Posted about 7 years back at The Hobo Blog

And finally…

Also note we’re now on rubyforge so you could always just gem update hobo (subject to rubyforge delays – I only threw it up there one minute ago)

The demos on the site have not been updated, nor has the manual I’m afraid to say. But work on updating the manual is already underway so hopefully it won’t be all that long.

Have fun!

Automated testing of websites via “real” users.

Posted about 7 years back at work.rowanhick.com

Of all the testing we can do, there's nothing quite like firing up your browser and navigating to the website in question, logging in and doing stuff. All of the integration tests, port tests, contrived "I'm alive" tests etc can give you an *indication* that your website code is all well and good. However what you really want to test is "can I jump to it, do x y z action, and complete those actions without it bombing out". We have a two options, we hire an army of minions who will do that for us every 15minutes on the dot OR we could do it automatically. How do we do this ? Well there are a number of ways. One option I'm going to walk through is using the magic genius of Safari Watir, RSpec tests, ruby, a few gems, cron and any Mac you can lay your hands on. We set this up a while ago monitoring some of our sites and it's great for finding out when something in your webstack has fallen over. For a < $1k investment it's a no brainer for any web site. Caveat - this ain't going to test your flash/flex content - this is just for HTML based sites. Read on... 1. Machine setup First, take one mac mini. We need to install ruby, ruby gems, rails, rspec, safariwatir, and rb-applescript bridge on it. Follow the Hivelogic narrative here for instructions on installing a full rails stack. Then just simply run these commands to finish off the last remaining gems. sudo gem install rspec sudo gem install safariwatir --include-dependencies 2. First website check So we've got our infrastructure setup. Now we need to write the scripts that actually run the test the site. These will fire up safari and tell it to navigate to various URL's, then check the content on the pages returned. So in true behaviour driven development style we write a spec that in plain english walks through the steps. It fires up a browser instance courtesy of Watir::Safari.new and uses it to navigate around pages. First let's create a file called check_google.rb like so: require 'rubygems' require 'safariwatir' context "With www.google.com" do before(:all) do @browser = Watir::Safari.new end it "can navigate to home page" do @browser.goto("http://www.google.com/") end it "homepage should not have a 404" do @browser.contains_text("404").should == nil end #it "should throw an contrived error, when seeing if yahoo is on main page" do # @browser.contains_text("www.yahoo.com").should == true #end after(:all) do @browser.close end end Now if we run spec check_google.rb we get output as follows: RowanH$ spec check_google.rb .. Finished in 2.818356 seconds 2 examples, 0 failures Fantastic, it's what we expect google is alive and tickety-boo. Now to check the failure scenario, uncomment the yahoo test: RowanH$ spec check_google.rb ..F 1) 'With www.google.com should throw an contrived error, when seeing if yahoo is on g main page' FAILED expected: true, got: nil (using ==) ./test_mysite.rb:18: Finished in 2.837444 seconds Cool, we know what a failure looks like. 3. Turning your console output into a email message We're not going to be logging in and doing this all the time, so we want to be emailed of success/failures. We are going to use a custom formatter for RSpec, which can make our output look a lot more interesting and pop it into an email. As I was running these scripts on a machine with a full rails stack I used ActiveMailer for sending emails.. (you'll see why in a minute) so let's create a custom formatter that will format our output for mailing. Lets call it mail_formatter.rb and pop it into the same place as our existing scripts - as follows. require 'rubygems' require 'action_mailer' #actual email thingy ActionMailer::Base.server_settings[:address] = 'domain@myhost.com' class Emailer < ActionMailer::Base def test_email(text, status) subject "TEST Autotest Status - #{status}" from "mytestbox@myhost.com" recipients "me@myhost.com" body text end end class MailFormat < Spec::Runner::Formatter::BaseTextFormatter def start(spec_count) @mail_output = String.new @mail_output << "Test started \n" @mail_status = "Tests OK" end def add_behaviour(name) @mail_output << "\n #{name}\n" end def example_failed(example, counter, failure) @mail_output << " - #{example.description} FAILED \n" @mail_status = "#{counter} tests FAILED" end def example_passed(example) @mail_output << " - #{example.description} PASSED \n" end def dump_summary(duration, spec_count, failure_count, something_else) @mail_output << "\n" @mail_output << "Finished in #{duration} seconds " @mail_output << "#{spec_count} test#{'s' unless spec_count == 1}, #{failure_count} failure#{'s' unless failure_count == 1}" Emailer.deliver_test_email(@mail_output, @mail_status) end end Right so this time if we a new command like so, to require the mail_formatter, and output in mail format, we should by rights receive an email: RowanH$ spec check_google.rb -r mail_formatter.rb -f MailFormat 1) 'With www.google.com should throw an contrived error, when seeing if yahoo is on main page' FAILED expected: true, got: nil (using ==) ./test_mysite.rb:18: (we still get our console output if we get a failure) 5. Automating it Okay so wrap that lot up in a shell script, and call it via cron (hey I'm not going to write *everything* out here), and you've now got a simple script that's calling up your website and checking it. 6. Extending it If you browse around for some of the safari-watir examples out there then you'll see more of how to access specific fields and named form elements - this will let you actually login and do stuff on your site(s) your testing. 7. Extra Credit - Screenshots!! Okay, we've got our system emailing us every test. But what would be really cool is if it actually sent us a screenshot everytime something turned to custard and failed so we could see what actually went wrong. Well... it's suprisingly easy. We're going to use OSX's inbuilt screencapture utility and fire off a shell command to capture a png file everytime something goes wrong. Then pop those in as attachments to our status email. Modify the code like so: In the Emailer.test_mail method, add a parameter images, and pop this code at the bottom of the method: if ( images ) for image_name in images attachment :content_type => "image/png", :filename => image_name , :body => File.read("/Users/rowanh/Desktop/failed_images/#{image_name}") end end So this will allow our email method to read in a list of images and attach them to mail message. Next we want to update our formatter. For the start method add the following: @failed_image_count = 0 @failed_images = Array.new Add a new method like so : def take_screenshot() @failed_image_count += 1 screenshot_name = "failure_#{@failed_image_count}.png" system "screencapture /Users/rowanh/Desktop/failed_images/#{screenshot_name}" @failed_images << screenshot_name end Next update example_failed so it takes_screenshot. Finally in the deliver_test_email method, pop in the @failed_images array. Conclusion Bingo! you now have a hugely useful tester, that runs away, tests your website, tells you and will actually show you what went wrong. For not a whole lot of investment. Off to setup a continuous integration machine that actually does this for each build........ Credit: http://blog.aslakhellesoy.com/2006/12/2/getting-screenshots-from-watir http://redsquirrel.com/cgi-bin/dave/projects/watir