All opinions expressed are those of the authors and not necessarily those of OSNews.com, our sponsors,
or our affiliates.
I've been heavily involved in an ecommerce project running on Rails 3, using Piggybak, RailsAdmin, Paperclip for file attachment management, nginx and unicorn. One thing that we've struggled with is handling large file uploads both in the RailsAdmin import process as well as from the standard RailsAdmin edit page. Nginx is configured to limit request size and duration, which is a problem for some of the large files that are uploaded, which are large purchasable, downloadable files.
To allow these uploads, I brainstormed how to decouple the file upload from the import and update process. Phunk recently worked on integration of Resque, a popular Rails queueing tool which worked nicely. However, I ultimately decided that I wanted to go down a simpler route. The implementation is described below.
Upload Status
First, I created an UploadStatus model, to track the status of any file uploads. With RailsAdmin, there's an automagic CRUD interface connected to this model. Here's what the migration looked like:
class CreateUploadStatuses < ActiveRecord::Migration
def change
create_table :upload_statuses do |t|
t.string :filename, :nil => false
t.boolean :success, :nil => false, :default => false
t.string :message
t.timestamps
end
end
end
RailsAdmin also leverages CanCan, so I updated my ability class to allow list, reads, and delete on the UploadStatus table only, since there is no need to edit these records:
cannot [:create, :export, :edit], UploadStatus
can [:delete, :read], UploadStatus
KISS Script
Here's the simplified rake task that I used for the process:
namespace :upload_files do
task :run => :environment do
files = Dir.glob("#{Rails.root}/to_upload/*.*")
files.each do |full_filename|
begin
ext = File.extname(full_filename)
name = File.basename(full_filename, ext)
(klass_name, field, id) = name.split(':')
klass = klass_name.classify.constantize
item = klass.find(id)
if item.nil?
UploadStatus.create(:filename => "#{name}#{ext}", :message => "Could not find item from #{id}.")
next
end
item.send("#{field}=", File.open(full_filename))
if item.save
FileUtils.rm(full_filename)
UploadStatus.create(:filename => "#{name}#{ext}", :success => true)
end
rescue Exception => e
UploadStatus.create(:filename => "#{name}#{ext}", :message => "#{e.inspect}")
end
end
end
end
And here's how the process breaks down:
- The script iterates through files in the #{Rails.root}/to_upload directory (lines 3-4).
- Based on the filename, in the format "class_name:field:id.extension", the item to be updated is retrieved (line 11).
- If the item does not exist, an upload_status record is created with a message that notes the item could not be found (lines 13-16).
- If the file exists and the update occurs, the original file is deleted, and a successful upload status is recorded (lines 18-23).
- If the process fails anywhere, the exception is logged in a new upload status record (lines 24-26).
This rake task is then called via a nightly cron job to slurp up the files. The simple script eliminates the requirement to upload large files via the admin interface, and decouples the upload from Paperclip/database management. It also has the added benefit of reporting the status to the administrators by leveraging RailsAdmin. Many features can be added to it, but it does the job that we need without much development overhead.
Comments
Today I came across a great opportunity to illustrate dependency injection in a simple context. I had a Rails partial that was duplicated across two subclasses. The partial was responsible for displaying options to create a new record from the data of the current record. It also offered two types of copy, shallow and deep. The shallow copy used a button to POST data, while the deep copy offered a form with some additional options. The only difference between the partials was the path to post data to. Let's see this in code.
#app/views/fun_event/_copy_options.html.erb
button_to(t("create_and_edit_shallow_copy"), fun_event_path(:from_event => @event.id, :return => true), : id => "shallow_copy_btn")
form_tag(fun_event_path(:return => true)) do
#form code
end
#app/views/boring_event/_copy_options.html.erb
button_to(t("create_and_edit_shallow_copy"), boring_event_path(:from_event => @event.id, :return => true), : id => "shallow_copy_btn")
form_tag(boring_event_path(:return => true)) do
#form code
end
The first, failed iteration
To remove the duplication, I passed in a path option into the partial, replacing specific references with the generic.
#app/views/fun_events/copy.html.erb
<%= render :partial => "events/copy_options", :event_path => fun_event_path %>
#app/views/boring_events/copy.html.erb
<%= render :partial => "events/copy_options, :event_path => boring_event_path %>
#app/views/events/_copy_options.html.erb
button_to(t("create_and_edit_shallow_copy"), event_path(:from_event => @event.id, :return => true), : id => "shallow_copy_btn")
form_tag(event_path(:return => true)) do
#form code
end
Can you guess where this led?
undefined method `event_path' for ActionView::Base:0xd6acf18
Dude! Inject the dependency!
Obviously the event_path variable I was passing was a string, not a method. I needed the method so I could pass in the appropriate arguments to construct the URL I needed. Had there not been two different calls to the routes, I would likely have just passed in the string needed in each context. But in this case, I was forced to think outside the box. Here's what I ended up with.
#app/views/fun_events/copy.html.erb
<%= render :partial => "events/copy_options", :event_path => method(:fun_event_path) %>
#app/views/boring_events/copy.html.erb
<%= render :partial => "events/copy_options, :event_path => method(:boring_event_path) %>
#app/views/events/_copy_options.html.erb
button_to(t("create_and_edit_shallow_copy"), event_path.call(:from_event => @event.id, :return => true), : id => "shallow_copy_btn")
form_tag(event_path.call(:return => true)) do
#form code
end
The changes are really quite subtle, but we use Object's method method to pass the reference to the method we want to call, and simply pass in the arguments when needed. Mind == Blown
Comments
It's been a fairly straight forward week at work, but I have stumbled a few interesting finds along the way this week.
Finally! A game based approach to learning Vim keyboard commands. I was hoping someone would do this. It's just getting started (only two levels) and sadly, it looks like it'll be charging money to unlock higher levels. However, some things are worth paying for. I've found just playing the first two levels a few times have helped retrain my brain to not take my fingers off the home row. It's still quite buggy and seems to only work in Chrome. I found several times I needed to close all my Chrome windows after playing. Also, incognito mode seems to help with the bugs, as it disables all extensions you may have installed.
Ever wanted to know where that slow query was being called from? Well, if you're using MySQL with your Rails 2.3.x or 3.x.x app, you can get debug information about what controller's action made the call. Check out 37Signals new marginalia gem.
Kevin Burke provides a very detailed HOWTO article for working around restrictions you may experience in the course of an Internet based life. Pretty amazing what Amazon's free usage tier puts out there; of course it's only free for 12 months.
For many Rails developers we get comfortable looking at development log files. Sometimes when I have to investigate a customer issue on a production server using logs, I wished I had the level of detail the development logger had. While that's a wish, I'm finding it mandatory to include PID numbers in my production logs. In production systems with multiple requests being handled simultaneously, Rails logs start to become unusable. It's not clear which log lines are from which requests. Adding a PID in front of the time stamp can help untangle the mess. Here are some example approaches to this for Rails 3.x.x and Rails 2.3.x. Also, if you're really a log-lover and manage a lot of servers, check out Papertrail, it looks very impressive for $7/mo.
Telecom is not an area I have much familiarity with, but I found this article to be an interesting read. For example did you know that that largest owner of spectrum licenses are "under-capitalized or unwilling to build out networks" to use the spectrum? So while AT&T and Verizon struggle to meet the iPhone 4S's data demands (twice as much as iPhone 4!), "there are some companies that have spectrum, but they're struggling financially. Or they aren't quite sure what to do with the spectrum. And others that have the money and business model, but need the spectrum." It seems the way out of the mess is 4G, offering to improve the efficiency of spectrum use by 700 percent.
Comments
This is the second article in the series on how to perform a bulk edit in Rails. Let's recap our user's story from Part 1.
- User makes a selection of records and clicks "Bulk Edit" button
- User works with the same form they would use for a regular edit, plus
- check boxes are added by each attribute to allow the user to indicate this variable should be affected by the bulk edit
- only attributes which are the same among selected records should be populated in the form
Part 1 addressed the first part of our user story. Now that we have our user's selection, we need to create an interface to allow them to select attributes affected by the bulk edit. Let's start with the form we'll use to POST our input.
# app/controllers/bulk_edits_controller.rb
def new
@foos = Foo.find(params[:stored_file_ids]) #params collected by work done in Part 1
@foo = Foo.new
end
# app/views/bulk_edit/new.html.erb
<%= form_for @foo, :url => "/bulk_edits" do |f| %>
<% @foos.each do |foo| %>
<%= hidden_field_tag "foo_ids[]", foo.id %>
<% end %>
<%= render "foos/form", :f => f %>
<%= f.submit %>
<% end %>
Let's first look at how we formed our form_for tag. Although this is a form for a Foo object, we don't want to POST to foos_controller#create so we add :url => "/bulk_edits" which will POST to the bulk_edits_controller#create. Additionally, we need to send along the foo_ids we eventually want to bulk update. Finally, we don't want to re-create the form we already have for Foo. By modifying one master form, we'll make long term maintenance easier. Now that we've got our form posting to the right place, let's see what modifications will need to make to our standard form to allow the user to highlight attributes they want to modify.
# app/views/foos/_form.html.erb
<%= check_box_tag "bulk_edit[]", :bar %>
<%= f.label :bar %>
<%= f.text_field :bar %>

Bulk edit check boxes appear in front of field names to let users know which fields will be modified.
We've added another check_box_tag to the form to record which attributes the user will select for bulk updating. However, we only want to display this when we're doing a bulk edit. Let's tweak this a bit further.
# app/views/foos/_form.html.erb
<%= bulk_edit_tag :bar %>
<%= f.label :bar %>
<%= f.text_field :bar %>
# app/helpers/foos_helper.rb
def bulk_edit_tag(attr)
check_box_tag("bulk_edit[]", attr) if bulk_edit?
end
def bulk_edit?
params[:controller] == "bulk_edits"
end
With these modifications to the form in place, the user can now specify which fields are eligible for bulk editing. Now we need the logic to determine how to populate the bar attribute based on the user's selection. This way, the user will see that an attribute is the same across all selected items. Let's revise our bulk edit controller.
# app/controllers/bulk_edit_controller.rb
def new
@foos = Foo.find(params[:foo_ids])
matching_attributes = Foo.matching_attributes_from(@foos)
@foo = Foo.new(matching_attributes)
end
# app/models/foo.rb
def self.matching_attributes_from(foos)
matching = {}
attriubtes_to_match = Foo.new.attribute_names #see attribute_names for more details
foos.each do |foo|
attributes_to_match.each do |attribute|
value = foo.__send__(attribute) #see send, invokes the method identified by symbol, use underscore version to avoid namespace issues
if matching[attribute].nil?
matching[attribute] = value #assume it's a match
elsif matching[attribute] != value
matching[attribute] = "" #on the first mismatch, empty the value, but don't make it nil
end
end
end
end

Only fields which are the same across all selected records will be populated. Other fields will be left blank by default.
With Foo#matching_attributes_for generating a hash of matching attributes, the form will only populate fields which match across all of the user's selected items. With our form in place, the last step is to actually perform the bulk edit.
# app/controllers/bulk_edits_controller.rb
def create
if params.has_key? :bulk_edit
foos = Foo.find(params[:foo_ids])
foos.each do |foo|
eligible_params = {}
params[:bulk_edit].each do |eligible_attr|
#create hash of eligible_attributes and the user's value
eligible_params.merge! { eligible_attr => params[:foo][eligible_attr] }
end
#update each record, but only with eligible attributes
foo.update_attributes(eligible_params)
end
end
end
We've now completed the entire user story. Users are able to use check boxes to identify which attributes should be bulk updated. They also get to see which attributes match across their selection. Things are, of course, always more involved with a real production application. Keep in mind this example does not make good use of mass assignment protection using attr_accessible and forcing an empty whitelist of attributes by using config.active_record.whitelist_attributes = true. This is a best practice that should be implemented anytime you need sever-side validation of your forms.
Additionally, there may be cases where you want to perform bulk edits of more complex attributes, such as nested attributes. Consider appending your additional attributes to the Foo.new.attribute_names array and then tweaking the eligible attributes logic. Also consider implementing a maximum number of records which are able to be bulk edited at a time; wouldn't want your server to time out. Good luck!
Comments
This will be the first article in a series, outlining how to implement a bulk edit in Rails 3.1.1 (although most any version of Rails will do). Today we'll be focusing on a simple user interface to allow the user to make a selection of records. But first, let's look at our user story.
The user story
- User makes a selection of records and clicks "Bulk Edit" button
- User works with the same form they would use for a regular edit, plus
- check boxes are added by each attribute to allow the user to indicate this variable should be affected by the bulk edit
- only attributes which are the same among selected records should be populated in the form
An example UI from Google's AdWords interface for
selecting multiple records for an action.
Sounds straight forward, right? Well, there are a couple of gotcha's to be worked out along the way.
Capturing the user's selection
We'd like to offer the user a form with check boxes to click so when submitted, our controller gets an array of IDs we can pass to our ActiveRecord finder. It's best implemented using check_box_tag which means it's not auto-magically wired with an ActiveRecord object, which makes sense in this case because we don't want our form manipulating an active record object. We simply want to send our user's selection of records along to a new page. Let's see what this looks like.
# app/views/search/_results.html
<% @foos.each do |foo| %>
<%= check_box_tag "foo_ids[]", foo.id %>
<% end %>
# when posted looks like
# "foo_ids"=>["4", "3", "2"]
Because we now have an array of IDs selected, it becomes very easy for us to work with our user's selection.
# app/controller/bulk_edit_controller.rb
def new
if params[:foo_ids].is_a?(Array) && params[:foo_ids].length > 1 #let's make sure we got what we expected
@foos = Foo.find(params[:foo_ids])
else
redirect_to search_path
end
end
Refining the UI with Javascript and CSS
It's not just enough to have these check boxes. We need our "Bulk Edit" button only to appear when the user has made an appropriate selection. Let's update our view code to give our tags some class.
# app/views/search/_results.html
<%= form_tag new_bulk_edit_path, :method => "GET", :id => "bulk-edit-form" do %>
<%= submit_tag "Bulk Edit", :id => "bulk-edit-submit" %>
<% end %>
<div class="search_results">
<% @foos.each do |foo| %>
<%= check_box_tag "foo_ids[]", foo.id, false, :class => "downloadable" %>
<% end %>
</div>
# app/assets/stylesheets/search.css
#bulk-edit-submit {
input { display: none; }
}
We've added the downloadable class tag to our check boxes, while adding a simple form to send data to the new_bulk_edit_path. This path corresponds to the new action, which typically, you don't post forms to (which is why we needed to be explicit about setting the GET method). However, in this case we need this information before we can proceed with a new bulk edit. We've also hidden the submit button by default. We'll need some Javascript to show and hide it.
# app/assets/javascripts/search.js
$('.downloadable').click(function() { //when an element of class downloadable is clicked
var check_count = $('.downloadable:checked').size(); //count the number of checked elements
if( check_count > 1 ) {
$("#bulk-edit-submit").show();
} else {
$("#bulk-edit-submit").hide();
}
});
At this point, you might have noticed that we're submitting a form with no fields in it! While we could simply wrap our form_tag around our search results, but we may not always want this. For example, what if we need multiple forms to be able to send our selection to different controllers in our application? Right now we're working on a bulk edit, but you know the client is expecting a bulk download as well. We can't wrap the same search results partial in multiple forms. Let's see how we would populate the our form using more Javascript.
# app/assets/javascripts/search.js
$('#bulk-edit').submit(function() { //When the bulk-edit form is submitted
$('#bulk-edit input:checked').remove(); //clear all checked elements from form
var selected_items = $('.downloadable:checked').clone();
$('#bulk-edit').append(selected_items);
return true; //VERY IMPORTANT, needed to actually submit the form
});
This is a simple, unobtrusive way to give your forms a little more flexibility. It's also a good example of how to use :checked as a modifier on our jQuery selector.
Namespacing and Refactoring our Javascript
Knowing you'll need to implement a bulk-download form later in this same style, so let's refactor out this cloning functionality.
# app/assets/javascripts/search.js
$('#bulk-edit').submit(function() {
MyAppName.clone_downloadable_checkboxes_to($(this)); //You MUST wrap "this" inside $()
return true;
});
if(!window.MyAppName) {
MyAppName = {}; //Initialize namespace for javascript functions
}
MyAppName.clone_downloadable_checkboxes_to = function(destination) {
destination.children("input:checked").remove();
var selected_items = $('.downloadable:checked').clone();
destination.append(selected_items);
};
One of the big highlights here is namespacing our Javascript function. While the chances are low that someone out there is going to have clone_downloadable_checkboxes_to in the global namespace too, it's always best to use proper namespaces.
Well, we've made it through the first part of our user story. The user can now check their boxes, and submit a form to the appropriate Rails resource. Stay tuned to see how we implement the second half of our user's story.
Comments
A couple of the sessions I attended on Day 1 of RailsConf 2011 were along the lines of how to write good Rails code: Keeping Rails on the Tracks by Mikel Lindsaar and Confident Code by Avdi Grimm. Here's a mishmash summary from these talks. Although the talks didn't focus on Ruby and Rails techniques, both talks had plenty of examples and tips for writing maintainable code that apply to my world of consulting and development.
In the first talk, Mikel talked about what he meant by keeping Rails on the Tracks and the balance that we might often experience between just working code and sexy code. Maintainable code lands in the middle there somewhere. And he briefly touched on the fact that budget matters in writing maintainable code, trade-offs are part of life, and that you are selling a solution and not code, which means that clients don't really care about the technology as long as it's maintainable.
Mikel's first pro tip for building a maintainable Rails app is to treat deployment as a first class citizen by having trivial deployment that takes advantage of existing tools like Chef and Puppet. Using Bundler will help you avoid additional pain, but be careful of avoiding locking on git repos that you don't own since you can't control these other repos. This really speaks to End Point's camps project ? it's so easy to deploy changes on many of our campified Perl-based Interchange sites, but more difficult for clients that aren't on camps. The bottom line is that having trivial deployment saves time & money.
Mikel also mentioned several performance tips that make clients happy, listed below. I wasn't sure how these recommendations fit into the talk on how to keep Rails on the tracks by writing maintainable code, but nonetheless here they are:
- combining JS, CSS, CSS sprites, utilizing the Rails 3.1 asset pipeline
- caching optimization: fragment caching, action caching, page caching
- avoid a bloated session, and avoid storing objects in the session
- push things out to the browser if possible to minimize data and web-app load
Another topic that Mikel touched on was how being smart can be stupid. He recommends to not use before and after filters for object instantiation and to minimize their use to state modifications such as authentication, authorization or related to the user session. Mikel mentioned that while meta programming is sexy and has its place, that that place is not in your Rails app because it's harder for other developers and even yourself to understand what's automagically happening when you look at the code 3 months after you wrote it.
Mikel mentioned a few examples of using the right tools for the job. He discussed two examples where using simple SQL reduced a Ruby report run-time from 45 minutes down to 15 seconds and a implementing a PostgreSQL COPY statement that completed a data migration in 74 minutes down from 150 hours. Mikel also noted that Cucumber is not unit testing, so just write unit tests!
Confident Code
Next up, Avdi gave a nice talk about writing confident code and explained the standard narrative of a method or function:
When gathering input, Avdi recommends that developers employ strategies to deal with uncertain inputs such as coercion (e.g. to_s, to_i), rejection (guard clause or something more complex to return method), or ignoring invalid inputs. He also talked about his approach to having a zero tolerance for nil in Ruby because it's overused and can be indicative of too many things such as an error, missing data, the default behavior, and an uninitialized variable.
In part 2 of the narrative, perform work, I liked Avdi's comments about how conditionals should be reserved for business logic. For example, consider the following two conditionals:
A) if post.published? ... end
B) if post ... end
The first line of code most likely represents business logic, while the second line may not. We want to minimize occurrences of instances of the second where conditionals are not representative of business logic. Avdi also touched on writing with the confident styles of chaining and iterative style such as that used in jQuery, where a jQuery selector does nothing when empty.
In part 3 of the narrative, deliver results, Avdi suggested to employ a style to raise a special case or a null object rather than a meaningless nil if there are no results. Finally, while handling failures, Avdi suggested to write the happy path first and have a visual divide in the code between the happy and unhappy paths.
Conclusion
My takeaways from the talks are:
- write code for people first (even yourself), then computers. This seemed to be a recurring recommendation at RailsConf.
- writing code is communicating the business logic. make sure it's clear, componentized, and each method has a single responsibility.
- like photography, sometimes the art is more about what you leave out rather than what you include.
While these takeaways aren't novel, I did like the insight into how both speakers approach development.
Comments
A keynote by DHH kicked off Day 2 of RailsConf, where much of his talk was spent discussing new asset behavior in Rails 3.1. Here's a run down of a few topics I found worth noting:
Asset Pipeline
DHH started by explaining how although a Rails application has lovely organization in terms of Ruby files, the assets (stylesheets, images, and JavaScripts) have become a junk drawer where junk continues to pile in. Once there's more than a handful of files, the broken window theory applies and no one tries to maintain organization of those assets. This gets nasty, like a honey badger.
With Rails 3.1, the asset pipeline addresses the junk drawer. Assets directories (images, stylesheets, and JavaScripts) are now created in the app, lib, and vendor assets directories as they pertain to the main app, plugin dependencies, or introduce new library dependencies like a jquery calendar date select plugin. There's also the introducton of appending to config.assets.paths, which allows you to add new directories that store these assets in arbitrary directories. The new asset pipeline allows you to store these assets in different organization, which encourages JavaScript files to be stored based on their level of abstraction, and the asset pipeline combined with Bundler enables you to track the jquery version with a jquery-rails gem yielding better maintenance.
New Defaults
Rails 3.1 now assumes the default of CoffeeScript and scss (Sass). Jason discussed a Sass talk he attended yesterday at BohConf which includes things like nesting to reduce duplicate code and variables to improve maintainability. I haven't worked with CoffeeScript much, so I'll just link to the CoffeeScript documentation and possibly attend a CoffeeScript talk tomorrow. The argument between setting defaults and setting no defaults was revisited, and defaults won. The new defaults use Bundler to include Sass and coffee-script in the Gemfile:
gem 'sass'
gem 'coffee-script'
And these can simply be commented out of the dependency list if desired. In my case, if I were developing a Rails app tomorrow with a limited budget, I might choose to use Sass since I've worked with it before, but pass on CoffeeScript until I learned more and felt confident working with it.
Scalability and Compiling
Another question that comes up with these new defaults is the scalability and compilability. A new rake task is introduced:
rake assets:precompile
The rake task goes through the load path to precompile application JavaScript and CSS into application-*md5_hash*.js or application-*md5_hash*.css and copy over the images to the application public directory. This new file based method ensures that users will request the correct application file in addition to keeping the older compiled files around. Finally, compression tools are built straight into Rails, uglifier for JavaScript compression and scss for CSS compression. There is no penalty to writing comments or white-space rich code with these compression tools built in.

We need a photo break. A Honey Badger.
The second talk I attended was "SOLID Design Principles Behind the Rails 3 Refactoring" by José Valim, a member of the Rails core team.
Single Responsibility Principle
Jose spent the most time talking about the single responsibility principle, or that a class should have one and only one purpose. José discussed the evolution of the ActionView::Base that was responsible for tracking details, finding templates, compling templates, and rendering to gradually be divided into the following components and responsibilities in Rails 3:
- View Path: holds resolvers
- Resolver: finds template. The resolvers abstraction no longer restricts templates to the filesystem (can be anywhere in the app,
web service, or even database) which simplifies testing and therefore improves maintainability.
- Lookup Context: tracks details, lookup context object is passed to the view.
- AV::Renderer: renders templates
- ActionView::Base: renders context
By applying the single responsibility principle to the view rendering functionality in Rails, modularization now allows us to extend or override individual points of the process (such as grabbing a template from a CMS-driven database, or passing a different lookup context object to the view) and ensure maintainability by enabling more testable code.
José talked about the other principles, but some pertain to static languages more so than Ruby as the book was originally written with static languages in mind. These included:
Since I missed a few points here and there, feel free to check out the conference keynote videos here and I'll add a link to José's talk when it becomes available. Undoubtedly, Rails 3.0 and related Rails 3.0 topics will continue to be a highlight of the conference and I look forward to sharing more!
Comments
Last Thursday and Friday, I attended MountainWest RubyConf in Salt Lake City. As usual with any conference, my notebook is full of various tools and tips jotted down for later investigation. I thought I'd summarize some of the topics that grabbed my interest and go more in depth on a couple of topics in the next couple of weeks.
- Lambda: In a talk early in the conference, lambda in Ruby came up. I had a hard time coming up with use cases for its use in Rails or a web app in another server side language with equivalent functionality (Python, Perl), but I'd like to look into it. An example was presented using Ruby's lambda to calculate Google's PageRank value, which is particularly appealing to my interest in SEO.
- Chef: I've heard of Chef since I started working with Rails, but have yet to use it. After my recent adventures with Spree on Heroku, I see the value in becoming more familiar with Chef or another configuration management software tool such as Puppet. I'm particularly interested in creating some Chef recipes for Spree.
- RVM: RVM, or Ruby Version Manager, is a nice tool to work with multiple Ruby environments from interpreters to sets of gems. For a couple of I/O-intensive Rails apps that I work on, I'm interested in performance benchmarking across different Ruby environments to investigate the business case for updating Ruby. RVM also provides functionality for gem bundle management, which might be of particular value when testing code and applications running from different gem versions.
- Rails 3: I'm pretty excited for Rails 3. Yehuda Katz talked about Rails topics such as method aliasing and method lookup. He talked a bit about how the lack of modularity hurts development, and modularity in code may be defined as reducing assumptions to increase reuse. He also struck a chord with me when he talked about premature optimization: making decisions about modularity or functionality before it's needed and how this can be a mistake. I read some documentation on Rails 3 over the weekend and am looking forward to its release.
- Rack and Sinatra: I haven't spent much time playing around with Rack or Sinatra, but have certainly heard a lot about these tools. There was a nice lightning talk given on how to create a simple ecommerce site in a very short time using the active merchant gem (also used by Spree), Sinatra, Rack, and the Rack/Payment gem. I'd like to expand on this in a blog post later.
- NoSQL: While Jon and I attended this conference, Ethan Rowe attended the NoSQL Live conference in Boston that he blogged about here and here. There was a decent talk at MWRC on MongoDB with some examples on data interactions. The speaker discussed how NoSQL data "would be great" for CMS systems because of the diversity and amount of unknown attributes. I'm not quite sure I agree with that statement, but I'm interested in learning more about the business cases for NoSQL.
- A couple of random book recommendations:
- Random tools:
- git hooks with gitty
- git instaweb to browse git file structure and commit history without an internet connection
- memprof - profiler to watch for object allocations in Rails
- yardoc - documentation tool for ruby
- ruby-processing - a data visualization tool
- Productivity and Happiness: A common principle that comes up in Ruby/Rails conferences: A happy developer yields good productivity which leads to a happy developer. And Ruby/Rails makes developers happy, right? Well, I can't speak for anyone else, but I like Ruby.
As I said before, I hope to dig more into a couple topics and blog about them later.
Comments
Recently, I wrote up a new class and some tests to go along with it, and I was
lazy and sloppy. My class had a fairly simple implementation (mostly a set of accessors, plus a to_s method). It looked something like this:
class Soldier
attr_accessor :name, :rank, :serial_number
def initialize(name,rank,serial_number)
@name = name
@rank = rank
@serial_number = serial_number
end
def to_s
"#{name}, #{rank}, #{serial_number}"
end
end
I had been trying to determine the essential attributes of the class (e.g., what are the minimal elements of this class? should I have a base class, then sub-class it for the various differences, or should I have only a single class that contains everything I need?)
As a result of the speculative nature of the development, my tests only included a few of the attributes.
What's wrong with that?
On the surface, there is nothing technically wrong with skipping accessor tests: after all, testing each accessor individually is really testing Ruby, not the code I wrote. Another excuse I made is that testing each individually is very non-DRY - the testing code itself has lots of duplication.
The problem is that the set of tests should be considered a contract between the class writer and the outside world. By not including the correct and complete list of accessors, I left out important information; it's a check, already signed by the class developer, but with the amount left blank.
I've seen some code solve the non-DRY-ness problem like the following:
class Soldier
Attributes = [:name, :rank, :serial_number]
Attributes.each {|attr| attr_accessor attr}
...
then testing code of:
Attributes.each do |attr|
it "should have an accessor for #{attr}" do
...
That let's the testing code be nice and compact; simply load in the class, then iterate over the Attributes to verify that the accessors are present.
From a tests are contracts standpoint, this approach is terrible, though, perhaps even worse than the original, incomplete set of tests I had written. All the reader of the tests learns is that there is an array of attributes; the reader has to go look at the implementation itself to see what those attributes are.
Better is to use an anonymous array in the test, duplicating the attribute list; i.e.,
[:name,:rank,:serial_number].each do |attr|
it "should have an accessor for #{attr}" do
...
end
end
That seems to be a good balance between keeping tests as contacts yet keeping them DRY.
Comments