Showing posts with label rails. Show all posts
Showing posts with label rails. Show all posts

Tuesday, July 28, 2009

Software Cultures

I think every programming language (and frameworks too) generates a dominant culture. It might be implicit in the language/tool itself, or it might happen more gradually through self selection. For example, here's how I distinguish three cultures (the boundaries are inherently fuzzy, and I'm aware that this is totally personal opinion, so people are bound to disagree by either raising counter-examples or saying they don't agree, and that's fine). I also have my tongue firmly in my cheek, but I believe there's an element of truth in each statement.

Java - a fascination with flexibility, generally achieved through complexity (and a healthy(?) does of XML). Ten years ago I would have said Java was focussed on solidity and reliability (in the meeting expectations sense), but I think this has changed over time. New functionality is implemented by adding new options. If it doesn't work, it's because you didn't configure it properly.

Rails - a fascination with bright, shiny new toys. Encourages the image of the developer as a person young enough to live in the code, who can deal with either new tools or tools that fundamentally change every three months or so. New functionality is achieved by implementing a new plugin/gem which needs to be compared to all existing alternatives. No solutions is 100% complete because people lose interest first. If it doesn't work, get in there and change the code yourself. [note - I don't comment on Ruby itself as I don't have enough exposure to the non-Rails community]

Seaside - I'm an old-hand in Smalltalk, just learning Seaside. So far I see a fascination with having something that just frackin' works (though it might be heavily engineered). The documentation sucks though, so get into the code. Changes are generally transparent and backwardly compatible. If it doesn't work, you did something wrong - the community is the documentation.

Each of these cultures has strengths and weaknesses. I'm 46 years old (shit, when did that happen), my raw memory isn't as strong as it was, and I don't think as quickly as I used to. But I have a load of experience in developing design models and applications - I want tools that get out of my way and let me build things. Given my Smalltalk experience, Seaside is a great match for me - much better than Rails. If there was the equivalent of "Agile Web Development Using Rails" then it would rock!

In an age when everyone needs to use portfolio theory to manage their software investments, starting low-cost initiatives by the dozen then abandoning the ones that don't work out, Rails is a clear winner over Java. It lets me experiment at low cost, it lets me get something out-the-door quickly. In that environment Java is a set of leg-irons. But the Rails culture thrives when people have enough time and energy to deal with a firehose of changes and alternatives - as an entrepreneur a lot of this is simply waste. One solution might be to standardise on a particular configuration - that seems like a false.optimisation.

Tuesday, July 14, 2009

What would you like in a code quality tool?

Although continuous integration has been a huge step forwards for the development community I think that over time we've tended to treat it as a rather large bucket that we could keep tossing things into. Sure, someone should immediately address that method that's too long, but should it really stop the build and the release of the next version if all the tests pass? My feeling is no, it shouldn't.

I think that the agile community could do more to encourage various parallel continuous processes that test different aspects of the application. In particular, we could separate the static analysis of the code quality from the behavioural analysis that we perform with tests, be they unit, integration or functional tests. If we separate the two aspects then we can look to improve them at different rates and in different dimensions. This separation is the motivation behind an idea that Marty Andrews and Simon Harris have been kicking around for a few years (Marty is the author of Roodi and Complexian, and Simon is the author of Simian, so they have a long-running interest in code quality). Cogent is now planning to turn this into a product - something that you can point towards your source code repository and simply unleash.

We don't have a name for the product yet (suggest one if you like), but it will run static analysis tools (like Checkstyle, Simian, Roodi, Reek, Flay) against each revision of your code base so that you can see trends over time, and it will produce warnings when the values of the metrics exceed certain thresholds. The product will be free for open source projects.

We'll start with a simple, web-based version, probably to run over Ruby projects in public Git repositories (because that's low-hanging fruit for us), but we're going to be very community driven. With this in mind we have a survey that will let you influence the development direction, even before we start. If a tool like this is something that interests you, please fill in our product survey. As a bonus, the first 500 people to complete the survey will get unlimited free access to the product in recognition of their contributions.

Wednesday, January 14, 2009

The cloud...it's full of *people*!

This is the story of the development of a small piece of software to deploy to the cloud, developed by a cloud of people, and how nominally unrelated groups interacted along the way.

I'm interested in having a way to easily create new Rails servers in the cloud (specifically AWS) and to deploy my apps to them. I'm familiar with the bits and pieces that are involved but I hadn't stitched them together. I started to write a Rails app to manage my AWS assets, partially as a path to learning extjs, but when the AWS Console came out I ditched that and started back on the original problem. Here are the different bits that I planned to use, and how I became aware of them:


  • Sprinkle, by Marcus Crafter (http://github.com/crafterm/sprinkle/tree/master) - Sprinkle handles deployment of resources on Ubuntu boxes in a pretty easy-to-understand way. Unfortunately I have a strong preference for Postgres over MySql (blame Simon Harris) and the default Sprinkle deployment recipes all use MySql. Still, it didn't take me long to make some extensions to install Postgres instead of MySql.


  • Passenger-Stack, by Ben Schwarz (http://github.com/benschwarz/passenger-stack/tree/master) - Sprinkles for Apache, Passenger, Mysql, Memcached & Git. I found out about this via a Ryan Allen tweet.


  • Amazon Web Services (http://aws.amazon.com/) - well known, but the fairly recent addition of Elastic Block Storage (EBS) gives a way to easily create persistent data that outlives a given instance, say for a Postgres database.



The last piece to this puzzle is a web site called oDesk (http://www.odesk.com/). I was talking to a client last week and she mentioned that she thought Australian Rails developers were the most expensive in the world. When I asked her what she was doing she said that she was using off-shore Rails developers and was finding good people who would work for US$20/hr, from India but also from the USA. It seemed like the recession in the USA was having interesting side affects. I was definitely curious how using oDesk would work from a client perspective.

So I had this small problem that I didn't have time to work on, and my curiousity about oDesk, and it seemed natural to solve them together. I posted my job on oDesk - produce a script that, given an EC2 instance and an ESB volume, would automatically configure the instance to use Rails with Passenger and Postgres, storing the data on the EBS volume. In addition, provide a test application so I can verify the Capistrano deployment to the instance. I posted this as a fixed price job, and my plan was to use a few different people and compare the outcomes. I posted my job last Sunday (it's currently Thursday).

I had my first response in a few hours, from within Australia! Twenty four hours didn't bring any more responses, so I pushed my job in front of a number of Rails developers who had high ratings. Many declined as they were too busy, some thought the suggested price was too low given the need to learn about AWS, and some still haven't responded. I got two more positive responses, one from an experienced developer in St. Petersburg and one from a less experienced developer on the east coast of the USA. The bids were $333.33 (Australia), $250.00 (Russia), and $166.67 (USA). The expected development times were 1 day, 1 day and 1 week.

I accepted the responses and nominated a starting date of Tuesday. On Wednesday I got my solutions from Australia and St Petersburg. I've tested the Russian solution and it works fine. There seem to be couple of issues in the Australian solution but the developer
is being very responsive, and the USA solution isn't due yet.

What's more interesting is what else happened in the 24 hours of development. The Russian developer forked passenger-stack on GitHub and hosted the fork as a public repository on GitHub (which was fine by me). However, I found out about this through an email from Marcus Crafter, who knew I was looking for a Postgres solution and pointed the fork out to me! Then I saw these tweets from Ben Schwarz (@benschwarz) "@kouky Just so you know, someone forked passenger-stack overnight rolling postgres support in, I'll try and pull everything together :)" and "@atnan @kouky, I've just blindly merged the PostgreSQL stuff as I'm up in the hills. I'll check it all out tomorrow :)" The status message for passenger-stack on GitHub now says "Adding support for PostgreSQL. Sprinkle will prompt the user to select MySQL/PostgreSQL server and the relevant Ruby database drivers."

So it looks like my little experiment ended up sponsoring the change that I wanted in the core implementation of passenger-stack. I suppose I could have asked Ben to do it originally, but that seemed like an imposition and I would have only achieved one of my goals. Now I have a script to configure my EC2 instances automatically, a better feeling for oDesk and off-shore development, and I've discovered that you can always sponsor the features that you want in a piece of open source software. Well worth my costs :-)

Tuesday, March 11, 2008

Adventures in BackgroundRb

We've been asked to produce some "nice" reports in PDF for a client, and getting it all done has been quite an adventure. We use Ruport for the reporting, which unfortunately means we also need to learn quite a bit about PDFWriter to get everything looking spiffy, and the report rendering does take a while, but we had it all working in a controller yesterday and things were looking good. Then we deployed the application to EngineYard.

The first thing that happened was that we got timeout errors that looked like this (courtesy of ExceptionNotifier):


A Mongrel::TimeoutError occurred in reports#cost_detail_pdf:
Mongrel timed out this thread: shutdown
/usr/lib64/ruby/gems/1.8/gems/mongrel-1.1.3/lib/mongrel.rb:221:in `within'


The support folks at EngineYard suggested that we shouldn't be running PDF generation from within our controller, and that we should use something like background job, but since we wanted to report status as the job proceeded and we only had a small number of jobs, we went with BackgroundRb instead. (If you care about the difference, look for 'Backgroundrb and Bj serve different purposes' in this thread).

BackgroundRb works great once you get it going, but there were examples in the documentation that simply didn't work for me. Here's what worked for me in the end:

In my controller:




[sourcecode language='ruby']
MiddleMan.new_worker(:worker => :cost_detail_pdf_worker, :job_key => current_user.id)
MiddleMan.ask_work(:worker => :cost_detail_pdf_worker, :job_key => current_user.id,
:worker_method => :build_report,
:data => [session[:production_id], file_name])
[/sourcecode]



and my worker:




[sourcecode language='ruby']
class CostDetailPdfWorker calculated_report, :template => :default)
register_status("Finished Rendering")
File.open(file_path, "w") do |file|
file << pdf
end
register_status("File available")
end

end
[/sourcecode]


The next part of the adventure was getting this running on our Slicehost slice. Unfortunately, BackgroundRb requires Ruby 1.8.5, and we only had 1.8.4 (on Ubuntu 6.06 LTS). Apt-get for Ubuntu 6.06 doesn't include Ruby 1.8.5, so I needed to install Ruby 1.8.5 from source using these excellent instructions.

Unfortunately, I also needed to reinstall all our gems to go with the new version of Ruby. This was mostly stragithforward, except for Postgres. The postgres gem wouldn't build the native extensions, failing to find pg_config. These instructions were close, but before that would work I also needed to do "apt-get install libpq-dev" (you might also need libpgsql-ruby and/or libpgsql-ruby11.8, but I already had those installed).

Ok, looks pretty good now. I started the backgroundrb server manually as a test, and could produce my pdf files, so the next step was to move to our production environment at Engineyard. This was the first time I tried to stop and start the BackgroundRb using Capistrano - unfortunately you can't simply repeat the command lines in your Capistrano tasks. Fortunately, there's also a description of some tasks that do work (make sure you use the start task in the comments at the end of post).

All in all, a nice outcome but with much more pain than I would have liked.

Thursday, February 21, 2008

Coupling rollback actions with transaction actions

The system I'm currently developing needs to import a variety of data from another system. The data arrives in batches, and within a batch some items might be valid and some might be invalid - I expect that this is a common problem. An item in the batch might also update many different objects within my domain model, and I won't necessarily know if the item is invalid until I try to commit the changes to the domain model. It became fairly clear that I had some recurring code patterns in my application, so of course I wanted to extract them into some abstraction and Ruby's reflection gave me a nice way to capture this.



What I needed was to be able to associate a piece of code that created or modified some domain objects with the code that cleaned up the changes to the domain model on rollback (assuming that the database transaction handled the persistence parts). I could have used blocks, but I find that unlabelled blocks aren't very expressive so I decided to use methods (and their names) instead of blocks. Here's an example of using my method, #within_transaction_with_rollback; in the example :create_reference_objects and :clear_reference_objects are methods defined somewhere else in the class.




[sourcecode language='ruby']
def process
within_transaction_with_rollback(:create_reference_objects, :clear_reference_objects)
end
[/sourcecode]


And here's the implementation of my method, for those who are interested. I'm sure that there are plenty of other possible implementations :-)




[sourcecode language='ruby']
module Transactions

def within_transaction_with_rollback(transaction_method, rollback_method)
begin
within_transaction(transaction_method)
rescue
self.send(rollback_method)
end
end

def within_transaction(create_method)
self.class.transaction do
self.send(create_method)
end
end

end

[/sourcecode]


Wednesday, February 20, 2008

Interaction between finder_sql and Active Scaffold

I came across an interesting interaction in Rails that depended on two things that I didn't know (of course, that list is infinite). The two things were:


  1. Active Record ignores finder_sql when you do an eager load of an association (though I can't find a definitive reference for this, it is the observed behaviour);
  2. Active Scaffold uses eager loading by default


I didn't have any specs that explicitly tested that my model could eagerly load its associations, but everything else seemed to be working, so I was surprised that Active Scaffold was unhappy. The solution was to tell Active Scaffold not to use eager loading on that particular association, following the instructions in the FAQ under "my database is choking" (which kind of describes what was happening to me, if the choking was fatal).

Monday, February 18, 2008

Rspec View specs and integrate_views

We've recently had the pleasure of having Craig Ambrose working on a project for us, and as you'd expect when you bring someone new into the team, we've been doing some storming. One of the topics that comes up repeatedly within development teams I'm familiar with, including ours, is how fanatically we should adhere to clear separation of unit and integration testing.



I'm fairly laissez faire about the issue. I'm quite happy to write tests for Rails models that actually invoke ActiveRecord and interact with the database. I'll also happily set up a network of associated objects in the database rather than mock out everything except the class under test, though I do have some ill-defined limit that makes me uncomfortable.



I'm more strict about controller tests - I want the controller itself to be wafer thin, and I'll generally only test that the controller sets the right instance variables (along with flash notices etc). The tests end up being fairly small, don't touch the view at all, and I think even the evangelical TDD folk would call them unit tests.



However, Craig rightly points out a couple of problems with this. I'm typically working with server side functionality (if I had to say what I was best out, I'd initially claim domain modelling), so I write crap view code and I rarely write tests for it (someone's just going to come along and rewrite it to be presentable anyway). It's a fairly pathetic defence of my laziness, but anecdotally it seems lots of people don't write view specs. So there's a whole area of my application that's not tested very well.



Craig also points out that one of the biggest sources of defects for him is that his controller doesn't set up what the view expects (or you can express if from the opposite perspective if you prefer) - the failure occurs during the interaction between the controller and the view. These sorts of failures are quite difficult to find with unit tests alone, and probably won't get picked up by distinct controller specs and view specs.



We could write separate integration specs, but RSpec gives a simple way to catch at least the most egregious of these problems. Unit testing purists might object, but the laissez faire'sts won't mind. Put "integrate_views" into your descriptions. When integrate_views is specified, RSpec renders the real view rather than mocking out the rendering, and if objects are missing or badly misconfigured you'll get a rendering exception.



I remembered the integrate_views option from earlier versions of RSpec - it's not particularly conspicuous in the current version's documentation, but it can be very useful.

Sunday, February 10, 2008

Using the latest attributes in a Rails migration

Occasionally I need to make some change to a table via migrations, and then immediately use the attributes associated with the latest table inside the migration. Most of the time you can do this by defining the aspects of the class inside the migration - for example:


[sourcecode language='ruby']
class AddDepartmentToProducts < ActiveRecord::Migration

class Product < ActiveRecord::Base
belongs_to :department
end

def self.up
add_column :products, :department_id, :integer
# do something with product.department
end

def self.down
#...
end

end
[/sourcecode]


There's also another way that's not as neat, but gives you finer grained control, for example if you need to have different definitions of the class in the up and down migrations:


[sourcecode language='ruby']
class AddDepartmentToProducts < ActiveRecord::Migration

def self.up
add_column :products, :department_id, :integer
Object.class_eval <<-end_eval
class Apparel21::Product < ActiveRecord::Base
belongs_to :department
end
end_eval
# do something with product.department
end

def self.down
#...
end

end
[/sourcecode]


Wednesday, January 30, 2008

ActiveScaffold reverse associations to models in modules

I really enjoy using ActiveScaffold, and encourage everyone to use it to generate administration style interfaces. I came across an apparent defect today that I wanted to share.



I have a set of models that are contained in a module, and I'm using AS to do administration for them. Mostly fine. However there's a problem when I click a link on an element of a many relationship that's being displayed in a list. In this image,


Metalicus


ranges is the result of a has_many relationship, and when I click on the range 'Summer Selection', I get this:


Metalicus


and the following stacktrace:


[sourcecode language='ruby']
ActionView::TemplateError (undefined method `reflect_on_all_associations' for Range:Class) on line #19 of vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:
16: # determine what constraints we need
17: if column.through_association?
18: @constraints = {
19: association.source_reflection.reverse => {
20: association.through_reflection.reverse => parent_id
21: }
22: }

vendor/plugins/active_scaffold/lib/extensions/reverse_associations.rb:26:in `reverse_matches_for'
vendor/plugins/active_scaffold/lib/extensions/reverse_associations.rb:12:in `reverse'
vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:19:in `_run_erb_47vendor47plugins47active_scaffold47frontends47default47views47_nested46rhtml'
vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:11:in `each'
vendor/plugins/active_scaffold/frontends/default/views/_nested.rhtml:11:in `_run_erb_47vendor47plugins47active_scaffold47frontends47default47views47_nested46rhtml'
vendor/rails/actionpack/lib/action_view/base.rb:637:in `send'
vendor/rails/actionpack/lib/action_view/base.rb:637:in `compile_and_render_template'
vendor/rails/actionpack/lib/action_view/base.rb:365:in `render_template'
vendor/rails/actionpack/lib/action_view/base.rb:316:in `render_file'
vendor/rails/actionpack/lib/action_view/base.rb:331:in `render_without_active_scaffold'
[/sourcecode]



The problem is that class should be MyModule::Range, not Range.



AS is looking up the class using code on ActiveRecord::Reflection::AssociationReflection, from the file activescaffold/lib/extensions/reverse_associations.rb. The fix (apparently - I haven't tested this exhaustively yet) is to change the code that sends "class_name.constantize" to the association to send "klass" instead (you need to do this in two places). The completed code is attached below. I hope this helps someone else!



[sourcecode language='ruby']
module ActiveRecord
module Reflection
class AssociationReflection #:nodoc:
def reverse_for?(klass)
reverse_matches_for(klass).empty? ? false : true
end

attr_writer :reverse
def reverse
unless @reverse
# Following line changed for compatibility with associations on classes in modules
# reverse_matches = reverse_matches_for(self.class_name.constantize)
reverse_matches = reverse_matches_for(self.klass)
# grab first association, or make a wild guess
@reverse = reverse_matches.empty? ? self.active_record.to_s.pluralize.underscore : reverse_matches.first.name
end
@reverse
end

protected

def reverse_matches_for(klass)
reverse_matches = []

# stage 1 filter: collect associations that point back to this model and use the same primary_key_name
klass.reflect_on_all_associations.each do |assoc|
# skip over has_many :through associations
next if assoc.options[:through]

## Following line changed for compatibility with associations on classes in modules
# next unless assoc.options[:polymorphic] or assoc.class_name.constantize == self.active_record
next unless assoc.options[:polymorphic] or assoc.klass == self.active_record
case [assoc.macro, self.macro].find_all{|m| m == :has_and_belongs_to_many}.length
# if both are a habtm, then match them based on the join table
when 2
next unless assoc.options[:join_table] == self.options[:join_table]

# if only one is a habtm, they do not match
when 1
next

# otherwise, match them based on the primary_key_name
when 0
next unless assoc.primary_key_name.to_sym == self.primary_key_name.to_sym
end

reverse_matches < 1

reverse_matches
end

end
end
end
[/sourcecode]

Monday, January 28, 2008

Rspec 1.1.2 and Textmate

A quick warning to everyone - if you upgrade to RSpec 1.1.2 and you use Textmate, you'll probably need to update your textmate bundle as well.



cd ~/Library/Application\ Support/TextMate/Bundles/

svn co svn://rubyforge.org/var/svn/rspec/trunk/RSpec.tmbundle

Wednesday, January 23, 2008

Freezing gems with architectures

So I don't ever lose this reference again (hopefully), here's a reminder to myself (and maybe to you) that I want to freeze all my gems, and accommodate different architectures, so I should be using gems_with_architecture. See
usage, and browse the
repository.

Wednesday, January 16, 2008

Interesting behaviour for defined?

I've been working with Pete Yandell's Not-a-mock plugin, which I like a lot, and this morning a failing spec led me to some interesting behaviour for the Ruby defined? operator. Try these two things:

defined? arbitrary_attribute

defined? arbitrary_attribute=


With Ruby 1.8.5 on my Mac, the second one fails! From experimenting, it doesn't seem like you can use any setter. The workaround (which I haven't confirmed with Pete yet) is to use 'self.methods.include?("arbitrary_attribute-").

Sunday, August 19, 2007

Active Scaffold

After quite a bit of writing and business related non-programming work, I've got my hands back onto Rails. I'm actually crossing a few boundaries by writing an application to help me manage the invoices and employee payments for Cogent (the accounting system does a great job from a purely financial perspective, but I need something that can reflect our internal policies and procedures, which still makes it sound much grander than it is). I've also made a personal commitment to try to understand more about the gems and plugins that I'm using, which brings me to Active Scaffold.

I've used ActiveScaffold in the past for the administrative part consumer facing applications, but this time around I decided to have a closer look at what I could do with it. It's still an exercise in progress, but I've been very impressed with what I've seen so far. Let's look at three simple things first - ordering columns, changing the format of a column value, and filtering the displayed rows.

You tell a controller us use Active Scaffold for CRUD actions by adding a line to your controller. For example, if I want to use ActiveScaffold to maintain the Sale model object then I could set up a controller like this:

active_scaffold :sale

However, the active_scaffold method also takes an optional block, which you can use to customise the behaviour of the scaffold. In particular, there is a method to set the columns in the list view, that you can use like this:

active_scaffold :sale do |config|
config.list.columns = [:invoice_number, :customer, :amount, :status, :salary_package, :entry]
end

In this case I've specified that there will be six columns in the list view, in precisely the order specified in the array, where the symbols in the array correspond to accessor methods on the Sale model object.

Changing the format of a column is done outside the scaffold configuration, in a helper method. The :amount column is some amount of money, so it would be nicer to format is as a currency amount. The formatter needs to be named {column_name}_column, and it receives the entire model object as its only parameter, so a method to format the amount column in the Admin::SalesScaffoldController would look like this:

module Admin::SalesScaffoldHelper
def amount_column(sale)
number_to_currency(sale.amount)
end
end

Finally, filtering the displayed rows is so simple that I stumbled across the functionality by accident. In my Sales model, the status attribute is an enumerated value that contains, among other values, 'Open' and 'Closed'. I set up a link to the controller like this
link_to :controller => 'admin/sales_scaffold', :action => 'index', :status => 'Open'

and expected to need to explicitly provide some filtering inside the controller. Instead, it worked before I'd done any coding at all! It turns out that any parameters that correspond to attributes are used to filter the results in the table. You can specify a single parameter, or you can specify multiple parameters, in which case they'll be and'd together.

I'm really impressed with the capabilities of Active Scaffold, and I intend to explore them in more depth for applications where the tabular presentation of Active Scaffold is a good fit, and for providing functionality quickly while a user interface design is refined.

Thursday, July 5, 2007

Introduction to Rails Workshop

It's been quite a while since I blogged, mainly because I've had my head down writing new workshop material. We're running an introductory Ruby On Rails workshop on July 21 (just two weeks away now), and although it's been a lot of effort I'm pretty happy with the way the material is working out.

One day isn't very long to look at the equivalent of Java and a full Java web development stack, so rather than simply repeat what's already available in books and screencasts we're going to cover things that we find useful in our day to day development work. We'll be giving attendees a whirlwind tour of Ruby, a brief guide to Rails, and then we'll focus on tools and practices that aren't quite so accessible to the ruby newbie.

So if you've heard about Ruby and/or Rails but haven't made the plunge yet, or you know someone like that, go have a look at the course description, check out our pricing policies and send us a bid. At the moment all you need to bid is $100, so you may be getting some really cheap training!

Tuesday, May 8, 2007

RailsBrain

If you need to refer to the Rails API documentation frequently, as I do, then you'll really appreciate RailsBrain.

Friday, April 13, 2007

RCov measurements

I'm busy setting up a Rails development project at a client site, and we've chosen to use rSpec for specification/testing, and rCov to report coverage. They work quite well together, but there's one caveat - classes that aren't loaded don't appear in the coverage report at all, so for a single class there's effectively no difference between 0% coverage (no tests at all) and 100% coverage. Of course this is an oversimplification, since Ruby loads files, not classes, but it's a good enough approximation on most projects, and there's clearly some sort of problem regardless of the details.

Our solution has been to force rSpec to load everything in app/models and app/controllers before the specs are run. We do this in the rspec_helper, and since this is loaded multiple times (on different paths) it's also useful to restrict this code so it only runs once.

First, here's the code that loads the models and controllers:


class ForceLoader
def self.run
["models", "controllers"].each do | app_component |
directory = File.join(RAILS_ROOT, "app/") + app_component
Dir[directory + "/**/*.rb"].each { |file| require_dependency file }
end
end
end


There are two things to note about this code:

  1. we use require_dependency for consistency with other Rails loading, rather than require or load;

  2. we need to ensure that the path of the file passed to require_dependency is the same as the path used by default by Rails. Ruby loading is path passed, and if you refer to the same file with two different path representations you may load it twice.


Next, let's look at the code that we put in rspec_helper.


begin
ForceLoader
rescue
require File.dirname(__FILE__) + '/force_loader'
ForceLoader.run
end


If we can't reference ForceLoader we load it and run it. Once the class is loaded the rescue code won't be invoked, so this ensures once only execution.

Hopefully this approach will give you more accurate coverage reports with minimal overhead - it's certainly uncovered at least one problem on our project so far. It can also be extended to cover other parts of your app in a fairly straightforward way.

Thursday, March 15, 2007

Building a store

I just finished watching the video of Building the Store from ClickableBliss, which explains why Mike Zornek build his own web store, and what he learned.

My notes from the presentation:

  • ELC Technologies RoR::PayPal - not a complete interface, doesn't provide express checkout, which is essential

  • vPayPal gem : never got it to work, seemed to have deficiencies, used as a reference

  • Tobias Lutke's PayPal gem - not specific to websites payment pro, related to instant payments

  • ActiveMerchant : couldn't get certain things related to PayPal to work, code submitted by contributor, not maintained. But it's now 1.0 and may work better.

  • SOAP : not used SOAP before, but was the eventual solution

  • These experiences were from about August 2006.

  • If you're going to accept confidential information in a Rails application, make sure you don't log it:

    class ApplicationController < ActionController::Base
    filter_parameter_logging "password"
    filter_parameter_logging "credit_card"
    end



Sorry if I'm cryptic - the video is worth watching :-)