find and replace hash values


Hash.class_eval do
  # TODO: unit test this

  def find_and_replace( x, y )
    self.dup.find_and_replace! x, y
  end

  def find_and_replace!( x, y )
    self.each_pair do |k,v|
      if v.is_a?(Hash)
        v.find_and_replace! x, y
      else
        if x.is_a?(Regexp) && v.is_a?(String)
          v.gsub!( x, y )
        else
          v = y if v == x
        end
        self[k] = v
      end
    end
  end

end

I recently had a problem with rake gems:install with a fresh rails app. Rakefile bootstraps the full rails environment, including plugins, controllers, etc. Some of the code getting executed depended on a gem, but I was running “rake gem:install” to install those gems. A circular “chicken or the egg” problem!

Here was my solution, which I put in environment.rb


unless $0 =~ /rake/
 # ... require code that relies on certain gems
end

Just because you’re using Ruby on Rails doesn’t make you agile. That’s why we created ScrumNinja, a lightweight project management tool that helps you manage your project like a ninja. ScrumNinja is built on Ruby on Rails by a team of Scrum practitioners. Its simple yet powerful features makes managing your project easier. Also, check out the resources like our Scrum tutorial to get you started.

Scrum has some great things to teach us about how to work with each other and how to be more effective. Here are six of them:

1. Always work from a prioritized list

Have you ever thought about how much of your day is wasted doing things that don’t mean very much? Or, do you engage too much in nerd pleasure i.e. working on things that are technically satisfying, but not necessarily working on what is important to your customer or client? This one simple act of working from a prioritized list will change the way you function and make you more effective. Keep the list up-to-date daily with the most important things on the top. Then start knocking them off.

2. Make a commitment publicly

If you have a task that you want to get done, commit to it and tell someone. In Scrum you would make the commitment to your team members, but your dog might work just as well (just kidding about that, you should probably just stick to humans). Something magic happens when that commitment is made and it is more likely to get done than if you didn’t make it.

3. Get feedback regularly

In Scrum this is called a ‘Retrospective’ and it should be done on a regular basis (regular being key). Ask someone you are working with or your boss at regular intervals (e.g. every 2 weeks, every month) to have lunch with you and give you some feedback. What’s it like working with me? What do you like about working with me? Am I doing anything that’s not working for you? What can I do to improve myself? You can also do it in a more ad-hoc way as long as it works out being regular. For example, if you just finished pair programming or just had a meeting, ask the other person ‘what went well?’ and ‘what could be improved?’

4. Time-box your activities

Setting time limits on meetings and tasks will help you keep focused. With Scrum, the concept of a ‘sprint’ assures that you commit to a set of functionality that is time-boxed. Here’s a golden nugget for you: tasks always tend to shrink or expand to fit the amount of time allotted (cf. Parkinson’s Law). So give yourself a shorter, but realistic allotment of time, then reassess when you hit the limit. If working with a client/product owner, this is a good time to communicate the status of the project and make those trade-offs – should you simplify the feature, remove it, or keep going on the current path. Of course, if you keep working, other features will be pushed out because something’s gotta give.

5. Work sustainably

Startup companies never seem to remember this one. Agile has zero to do with working yourself until you’re burned out and has everything to do with working sustainably. The number of hours we have in a week is fixed – if something is added to the current iteration, something else has to be pushed out. If this is the week of a critical release then there may be an exception, but having critical releases on a weekly basis no longer qualifies as critical.

6. Make your work visible

Use a push, not a pull system of project management. Managers who constantly poll their developers for their project status only interrupt developer work flow. The reason that they do this is because they can’t keep their minds wrapped around all the moving parts of the project. If developers take it upon themselves to push their current status to a place that everyone has visibility into, then the need for micro-management is gone. So, whether you use a product like ScrumNinja or index cards and a cork board, your manager and other stakeholders will be up-to-date on progress.

So in a flash of inspiration on the airplane last week, I jotted down a new version of my shoulda params pattern. Put this pastie in test/shoulda_macros/request_params.rb

The general idea is this:


a_get_to(:index) do
  with_params( {:foo => 'bar'}, "valid params" ) do
    should_respond_with :success

    with_params( :more => 'nested stuff' ) do
      should "do something extra special" do
         assert true
      end
    end

  end
end


Here’s what it looks like in action, taken from the ScrumNinja.com code:

require File.dirname(__FILE__) + '/../test_helper'

class MainControllerTest < ActionController::TestCase
  @@message = "you guys rock!" 

  context "When not logged in" do
    setup do
      login_as( nil )
    end
    %w[ index tour contact api ].each do |action|
      a_get_to( action, params=false ) do
        should_respond_with :success
      end
    end

    a_get_to( :feedback, params=false ) do
      should_redirect_to "'/login'" 
    end    

    a_post_to( :contact, :message => @@message ) do
      should_send_email :to => "Mailer::GO_EMAIL", :body => %r{#@@message}
      should_redirect_to "'/'" 
    end
  end

  context "When logged in" do
    setup do
      login_as( @user = create_user )
    end
    a_post_to( :feedback, :message => @@message ) do
      should_send_email :to => "Mailer::GO_EMAIL", :body => %r{#@@message}
      should_respond_with :redirect
    end
  end

end


ScrumNinja in public beta

We are officially in public beta! Here’s the message that went out to all our users. We’ve made lots of improvements to the system. Please check it out!

Dear User,

Happy New Year! We’re officially in public beta. Check out the new improvements we’ve made to make ScrumNinja a more enjoyable experience:

  • UI look and feel improvements – We know it’s gotta look and feel good to you.
  • More responsive prioritization
  • Ability to drag stories in sprint history
  • Story and Task IDs more prominent and visible
  • Edit permissions based on account type
  • User Directory
  • Visitor read-only account type
  • Chores need not be accepted after delivery
  • Ability to add a stories from the card wall
  • Installed UserVoice feedback widget so you can add and vote on features and bugs

We’ve also added application monitoring services from Scout (scoutapp.com) so that we can keep an eye on our slice and make sure that it is up as much as possible for those of you who depend on us.

We’re excited about 2009 and hope to make ScrumNinja even better. Thank you for all of your feedback. Keep it coming and keep on Scruming!

Best Regards, Rodney & David The ScrumNinja team!

Scrum is Everywhere

I see so many philosophical discussions about Scrum. Is it a Panacea? Does it actually work? Is it better than XP? In truth, I believe that many of these discussions are a waste of time. The reason being is that Scrum is useless unless you put it in action. Scrum is just a framework or a lightweight process by which one develops software. The essence of Scrum is not native to software development. In fact, the more exposure I have to other areas the more I see Scrum as a successful pattern that can be applied to many areas of life and work.

I have two examples that I have seen in the last year that are processes similar to Scrum. One example is C.J. Hayden’s book called Get Clients Now. It uses a process that is similar to Scrum only it is applied to sales and getting more clients. Another example I have seen recently is Dan Olsen’s process of designing a killer web app. This method uses a continuous integration process that looks very much like Scrum to figure out how to drive users to certain goals in your app.

I’m sure you can find many more examples of Scrum being used in different areas. Please post comments of others that you know of.

P.S. We are looking for Alpha testers for ScrumNinja. If you are interested, please sign up.

irb command history searching

Just another little thing I love about ruby and the way it follows the Principle of Least Surprise: command history searching in irb with ctrl-R.


[aurora:~/work/scrumninja/trunk] dfl% irb
(reverse-i-search)`find': Project.find(1).stories.each(&:destroy)

I <3 Ruby! :)

ScrumNinja Alpha Released

We are proud to announce the alpha of our very first product!

ScrumNinja: a hosted project management tool built from scratch with Scrum in mind.

Here is the message we sent to our announcement list:

Dear Scrum Enthusiast,

We are excited to announce the Alpha version of ScrumNinja. The best way to see the value of this tool is to commit to using it for a real project, even just a small one. ScrumNinja will always be free for one project and up to three users. For now, there are no limits to the number of users or projects. Please check out the quick start guide to get started:

http://scrumninja.com/guide

If you’d like to become an official tester and be considered for a $50 credit, please let us know. Official testers actively use the product to manage a real project, give us feedback and occasionally answer simple questionnaires.

Regards, Rodney and David

Factories-and-workers is now updated to not only be a plugin, but also a gem. Thanks to Jonathan Barket and Nick Hoffman for their help!

It is also now automatically loaded for test and development environments (as a plugin, or do it manually as below with the unless statement). I find this really comes in handy for using it in the rails console to populate your database.

Get It

Add it as a gem dependency to your rails environment.rb:


  config.gem 'dfl-factories-and-workers', :lib => 'factories-and-workers', :source => 'http://gems.github.com' unless RAILS_ENV=='production'

Or install it as a gem manually:

  gem sources -a http://gems.github.com
  sudo gem install dfl-factories-and-workers

Or grab the source:

  git clone git://github.com/dfl/factories-and-workers.git

haml - don't use end tags

so I’m surprised I didn’t figure this out earlier… it was this message that finally drove the point home.

When using haml in your rails view templates, you do not need to close ruby blocks and conditional statements.

for example:

Here's how I was doing it before:

- content_for :head do
=   stylesheet_link_tag    'jquery.autocomplete'
=   javascript_include_tag 'jquery.autocomplete'
- end
- javascript_behaviour '$("input#user_full_name").autocomplete("project_roles/auto_complete_for_user_full_name")'

And here's how it really should be:

- content_for :head do
  = stylesheet_link_tag    'jquery.autocomplete'
  = javascript_include_tag 'jquery.autocomplete'
- javascript_behaviour '$("input#user_full_name").autocomplete("project_roles/auto_complete_for_user_full_name")'

So much nicer and readable, just as the haml gods intended :)

+1 for making ”- end” an error.

UPDATE:

On Oct 10, 2008, at 5:18 PM, Nathan Weizenbaum wrote: ”- end” actually is an error in the most recent Haml release, 2.0.3.

tips for Ruby/Rails newbies

I shared some tips with a colleague who is relatively new to rails, and thought I’d post them here too.


##before:
content_tag :div, @event.description_html unless @event.description_html.to_s.blank?

## better:
# blank works on nil, string, array, and hash. It's a rails thing from ActiveSupport.
content_tag :div, @event.description_html unless @event.description_html.blank?

## best:
#But even better is to use the special #{attribute}? methods which we get with Rails 2. These basically just call !#{attribute}.blank?
content_tag :div, @event.description_html if @event.description?

the returning method is also handy at times… also from ActiveSupport. Here is an abstracted example from a rails helper: I’m not sure why yet, but returning doesn’t seem to work with strings


## before:
html = "" 
html += "some stuff" if some_condition
html += "123" 
html

## after:
returning [] do |html|
  html <<  "some stuff" if some_condition
  html << "123" 
end.join("\n")


and also the pluralize method is part of ActionView::Helpers


## before:
 def people_or_person(count)
   (count == 1) ? 'person' : 'people'
 end
## after:
pluralize count, "person" 

using case…



## before:
   word.each_char do |x|
     if ((x =~ /[a-zA-Z]/) == 0)
       str <<  "[#{x.to_s.downcase}#{x.to_s.upcase}]" 
     else
       str << "[#{x}]" 
     end
   end

## after:
   word.each_char do |x|
     case x
     when /[a-zA-Z]/
       str << "[#{x.to_s.downcase}#{x.to_s.upcase}]" 
     else
       str << "[#{x}]" 
     end
   end


using map_with_index (or the closest thing we have in Ruby 1.8) aka collect_with_index


## before:
   roles = []
   @events_summary[:role_groups].each_with_index do |role, idx|
     roles << [role[:for], idx]
   end
   roles

## after:
   require 'enumerator'
   @events_summary[:role_groups].enum_with_index.map{ |role, idx| [ role[:for], idx ] }

## Ruby 1.9:
   @events_summary[:role_groups].map.with_index{ |role, idx| [ role[:for], idx ] }


require 'rubygems'
require 'activesupport'

# this doesn't work as expected. does anyone know why?
z = returning html="" do
  html += "stuff" 
  html += "more stuff" if true
end
p z
#>> "" 

# this works though
z = returning html=[] do
  html << "stuff" 
  html << "more_stuff" if true
end.join("\n")
p z
#>> "stuff\nmore_stuff" 

Update: Thanks DrMark, I should have done it like this: (I didn’t realize that one could use << on strings)

z = returning html="" do
  html << "stuff" 
  html << " more stuff" if true
end

So I just switched over one of our projects to jQuery this weekend, to see what all the hype is about. It’s pretty good. Most notable is that jQuery $() is the same as Prototype $$() [find by CSS selector]. There seems to be no equivalent to Prototype’s $() [find by dom id]. The switch is as simple as adding # [dom id selector] in front of your old prototype $() methods.

jRails is pretty good, I’d say it was 95% plug and play. Just a few quirks where I had to modify some parameters to my helper method calls.

I previously posted about global params hashes for nested shoulda contexts in functional tests. This post is a followup that shows how my solution has since crystallized.

Here’s the special sauce to add to your test_helper.rb:

ActionController::TestCase.class_eval do
  # special overload methods for "global"/nested params
  [ :get, :post ].each do |overloaded_method|
    define_method overloaded_method do |*args|
      action,params,extras = *args
      super action, @params.merge( params || {} ), *extras
    end
  end

  def setup
    super
    @params = {}
  end
end

This creates a @params variable that gets merged with all HTTP requests. Now you can use global params in your nested contexts as such:

class FooControllerTest < ActionController::TestCase
  def setup
    super
    @params[:security_token] = 'abc123' # add any global params you need here
    @event = create_event    
  end

  context "A POST to :action" do
    setup do
      @action = lambda{ post :action, :id => @event.id }
    end

    %w[ attending not_attending maybe_attending ].each do |status|
      context "with :status = '#{status}'" do
        setup do
          @params[:status] = status
          @action.call
        end          
        should_respond_with :success
        should_change "Rsvp.count", :by => 1
        should "create the proper Rsvp object" do
          assert Rsvp.find_by_user_id_and_event_id_and_status( @user.id, @event.id, status )
        end
      end
    end
  end

end