Five Common Rails Mistakes »

Created at: 06.05.2012 06:01, source: Mike Perham, tagged: rails ruby

I’ve worked with Rails for quite a while now and in that time I’ve seen a lot of Rails applications and both read and written a lot of bad Ruby code. Here’s five common mistakes that I see in almost every Rails codebase.

1. Migrations with no schema specifics

Your data model is the core of your application. Without schema constraints, your data will slowly corrode due to bugs in your codebase until you can’t depend on any fields being populated. Here’s a Contact schema:

  create_table "contacts" do |t|
    t.integer  "user_id"
    t.string   "name"
    t.string   "phone"
    t.string   "email"
  end

What is required? Presumably a Contact must belong_to a User and have a name — use database constraints to guarantee this. By adding :null => false, we ensure that the model is always consistent even if we have bugs in our validation because the database will not allow a model to be saved if it fails those constraints.

  create_table "contacts" do |t|
    t.integer  "user_id", :null => false
    t.string   "name", :null => false
    t.string   "phone"
    t.string   "email"
  end

Bonus points: use :limit => N to size your string columns appropriately. Strings default to 255 characters and phone probably doesn’t need to be that big, does it?

2. Object-Oriented Programming

Most Rails developers do not write object-oriented Ruby code. They write MVC-oriented Ruby code by putting models and controllers in the expected locations. Most will add utility modules with class-methods in lib/, but that’s it. It takes 2-3 years before developers realize: “Rails is just Ruby. I can create simple objects and compose them in ways that Rails does not explicitly endorse!”

Bonus points: introduce facades for any 3rd-party services you call. Provide a mock facade for use in your tests so that you don’t actually call the 3rd party service in your test suite.

3. Concatenating HTML in helpers

If you are creating helper methods, kudos, at least you trying to keep your view layer clean. But developers often don’t know the basics of creating tags within helpers, leading to messy string concatenation or interpolation:

str = "<li class='vehicle_list'> "
str += link_to("#{vehicle.title.upcase} Sale", show_all_styles_path(vehicle.id, vehicle.url_title))
str += " </li>"
str.html_safe

Yikes, it’s ugly and can easily lead to XSS security holes! content_tag is your friend.

content_tag :li, :class => 'vehicle_list' do
  link_to("#{vehicle.title.upcase} Sale", show_all_styles_path(vehicle.id, vehicle.url_title))
end

Bonus points: start introducing helper methods that take blocks. Nested blocks are a natural fit when generating nested HTML.

4. Giant queries loading everything into memory

You need to fix some data so you’ll just iterate through it all and fix it, right?

User.has_purchased(true).each do |customer|
  customer.grant_role(:customer)
end

You have an ecommerce site with a million customers. Let’s say each User object takes 500 bytes. This code will take 500MB of memory at runtime! Better:

User.has_purchased(true).find_each do |customer|
  customer.grant_role(:customer)
end

find_each uses find_in_batches to pull in 1000 records at a time, dramatically lowering the runtime memory requirements.

Bonus points: use update_all or raw SQL to perform the mass update. SQL takes time to learn well but the benefits are even more tremendous: you’ll see a 100x improvement in the performance.

5. Code review!

I’m guessing you are using GitHub and I’m also guessing you aren’t using pull requests. If you spend a day or two building a feature, do it on a branch and send a pull request. Your team will be able to review your code, offer suggestions for improvement and possible edge cases that you didn’t consider. I guarantee your code will be higher quality for it. We’ve switched to using pull requests for 90% of our changes at TheClymb and it’s been a 100% positive experience.

Bonus points: Don’t merge pull requests without tests for at least the happy path. Testing is invaluable to keep your application stable and your sleep peaceful.

Did I miss any common issues? Leave a comment and let me know!

Update: use find_each rather than find_in_batches, thanks readers!


more »

Converting a MySQL database from Latin1 to UTF8 »

Created at: 31.03.2012 17:27, source: Mike Perham, tagged: rails

We had a problem at TheClymb: our database and tables were created with the default Latin1 encoding. Now all of the data in those tables is actually UTF8 because it was all imported via the web browser (which defaults to UTF8) and MySQL doesn’t actually validate or convert data encoding when inserting.

A suggestion was to just set this in an initializer:

Mysql2::Client::CHARSET_MAP['latin1'] = Encoding::UTF_8

This will solve your problems in Ruby but will not solve your problems in the database: MySQL will still sort and compare strings thinking they are latin1 and thus do so incorrectly. Here’s an example:

CREATE TABLE names_latin1 (name varchar(32) character SET latin1);
INSERT INTO names_latin1 VALUES ('Martin Strauße');
INSERT INTO names_latin1 VALUES ('Martin Straure');
INSERT INTO names_latin1 VALUES ('Martin Strausse');
INSERT INTO names_latin1 VALUES ('Martin Straute');
 
CREATE TABLE names_utf8 (name varchar(32) character SET utf8);
INSERT INTO names_utf8 VALUES ('Martin Strauße');
INSERT INTO names_utf8 VALUES ('Martin Straure');
INSERT INTO names_utf8 VALUES ('Martin Strausse');
INSERT INTO names_utf8 VALUES ('Martin Straute');

I’m not a linguist but to the best of my knowledge the German ß is essentially “ss”. When we ask MySQL to sort our names, you can see that the UTF-8 results put the ß character between “r” and “ss” but the Latin1 results don’t. If a German were to see this, they would be enraged due to your culturally insensitive code!

> select * from names_latin1 order by name;
+-----------------+
| name |
+-----------------+
| Martin Straure |
| Martin Strausse |
| Martin Straute |
| Martin Strauße |
+-----------------+


> select * from names_utf8 order by name;
+-----------------+
| name |
+-----------------+
| Martin Straure |
| Martin Strauße |
| Martin Strausse |
| Martin Straute |
+-----------------+

We need to update the CHARACTER SET without doing any conversion of the data. This is simple to do: you convert the columns to a blob format and then convert them back to a string format with the proper encoding declared; MySQL will not do any conversion of raw binary data. For example:

ALTER TABLE categories CHARACTER SET utf8 COLLATE utf8_unicode_ci, CHANGE title title VARBINARY(255)
ALTER TABLE categories CHANGE title title VARCHAR(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci

With this in mind, I wrote a rake task to convert our application’s database. Here’s the full script in a Github Gist. You’ll need to run it with a DOIT parameter to actually make the changes otherwise it will just print the SQL it will run to the terminal. The script will take a long time for large databases since it has to ALTER TABLE, which means MySQL will write out the table to disk in full; the script does minimize the number of ALTER TABLEs it runs to two per table.

rake convert_to_utf8 DOIT=1

So please think of the Germans and the rest of our international friends: converting the character set of your database to the proper value is important to get correct sorting of results.


more »

rspec-2.9.0 is released! »

Created at: 18.03.2012 01:10, source: David Chelimsky, tagged: rspec rails ruby

rspec-2.9.0 is released wtih lots of bug fixes and a few minor feature improvements as well. Enjoy!

rspec-core-2.9.0 / 2012-03-17

full changelog

Enhancements

  • Support for “X minutes X seconds” spec run duration in formatter. (uzzz)
  • Strip whitespace from group and example names in doc formatter.
  • Removed spork-0.9 shim. If you’re using spork-0.8.x, you’ll need to upgrade to 0.9.0.

Bug fixes

  • Restore --full_backtrace option
  • Ensure that values passed to config.filter_run are respected when running over DRb (using spork).
  • Ensure shared example groups are reset after a run (as example groups are).
  • Remove rescue false from calls to filters represented as Procs
  • Ensure described_class gets the closest constant (pyromaniac)
  • In “autorun”, don’t run the specs in the at_exit hook if there was an exception (most likely due to a SyntaxError). (sunaku)
  • Don’t extend groups with modules already used to extend ancestor groups.
  • its correctly memoizes nil or false values (Yamada Masaki)

rspec-expectations-2.9.0 / 2012-03-17

full changelog

Enhancements

  • Move built-in matcher classes to RSpec::Matchers::BuiltIn to reduce pollution of RSpec::Matchers (which is included in every example).
  • Autoload files with matcher classes to improve load time.

Bug fixes

  • Align respond_to? and method_missing in DSL-defined matchers.
  • Clear out user-defined instance variables between invocations of DSL-defined matchers.
  • Dup the instance of a DSL generated matcher so its state is not changed by subsequent invocations.
  • Treat expected args consistently across positive and negative expectations (thanks to Ralf Kistner for the heads up)

rspec-mocks-2.9.0 / 2012-03-17

full changelog

Enhancements

  • Support order constraints across objects (preethiramdev)

Bug fixes

  • Allow a as_null_object to be passed to with
  • Pass proc to block passed to stub (Aubrey Rhodes)
  • Initialize child message expectation args to match any args (#109 - preethiramdev)

rspec-rails-2.9.0 / 2012-03-17

full changelog

Enhancments

  • add description method to RouteToMatcher (John Wulff)
  • Run “db:test:clone_structure” instead of “db:test:prepare” if Active Record’s schema format is “:sql”. (Andrey Voronkov)

Bug fixes

  • mock_model(XXX).as_null_object.unknown_method returns self again
  • Generated view specs use different IDs for each attribute.


more »

Modularized Association Methods in Rails 3.2 »

Created at: 20.01.2012 21:03, source: has_many :through, tagged: activerecord associations rails

Happy Friday! It's Rails 3.2 day! The official release announcement mentions a few of the big changes, but I'd like to take a moment to highlight a relatively small change I was responsible for, one that I hope may make your life a little easier.

From the ActiveRecord CHANGELOG:

Generated association methods are created within a separate module to allow overriding and
composition using `super`. For a class named `MyModel`, the module is named
`MyModel::GeneratedFeatureMethods`. It is included into the model class immediately after
the `generated_attributes_methods` module defined in ActiveModel, so association methods
override attribute methods of the same name. *Josh Susser*

The point of this change is to allow more flexibility in working with associations in your model classes. When you define an association, ActiveRecord automagically generates some methods for you to work with the association. For example, a has_many :patches association generates the methods patches and patches= (and a few others).

Previously, those association methods were inserted directly into your model class. This change moves those methods into their own module which is then included in your model class. Your model gets the same methods through inheritance, but also gets to override those methods and still call them using super. Let's take a look at two ways this makes things easier for you.

Sometimes you want to replace the standard generated association methods. That's always been easy to do simply by defining new methods in your model class. The only wrinkle was that you had to make sure you defined your method after you set up the association, or calling has_many would overwrite your method, since last writer wins. That was usually not a problem, but sometimes plugins or other monkey patching extensions could add an association after your model's class was defined, which wouldn't give you a chance to add your method afterwards. With this change, you don't have to worry about those order dependencies anymore. Since those methods are generated in their own module, the order doesn't matter. This is a pretty small issue all told and I doubt it affected many people, but it's still worth mentioning.

The real reason for this change is being able to compose your own methods with the standard generated methods. Before this change, you'd have to use alias_method_chain or some other fancy footwork to layer your own logic on top of the standard association functionality. Either that or you'd have to somehow duplicate the standard behavior in your own method. Ick. Now you can compose methods using inheritance and super, the way Alan Kay intended you to. Here's the example from the docs:

class Car < ActiveRecord::Base
  belongs_to :owner
  belongs_to :old_owner

  def owner=(new_owner)
    self.old_owner = self.owner
    super
  end
end

If you're familiar with ActiveRecord it's probably fairly obvious what's going on there, but I'll spell it out for the new kids. When you define the belongs_to :owner association, that generates a standard owner= method, and puts it in the module named Car::GeneratedFeatureMethods, which is the closest ancestor of class Car. If you're curious what this looks like, fire up the rails console and type Car.ancestors to see the class's inheritance chain. (Or use your own app and model, since that will be much easier than making up a new app just to see that one thing.)

In this Car class, you can see that changing owners keeps track of the old owner, so the new owner knows who to call when he can't figure out how to open the trunk. The generated owner= method does a fair amount of stuff including managing counter caches, running callbacks, setting inverse associations, etc. Skipping that could break a number of things, so after saving the old owner, you also want to run the generated method. Since it's in a module that Car inherits from, you only have to call super to get that to run. No muss, no fuss!

One more step towards simpler OOP in Rails! Thanks to my fellow Ruby Rogues Avdi Grimm and James Edward Gray II for complaining about the old state of things enough to motivate me to finally go fix this.


more »

rspec-rails-2.8.1 is released »

Created at: 05.01.2012 07:43, source: David Chelimsky, tagged: bdd rspec rails ruby

Bug fix release

The rails-3.2.0.rc2 release broke stub_model in rspec-rails-2.0.0 > 2.8.0. The rspec-rails-2.8.1 release fixes this issue, but it means that when you upgrade to rails-3.2.0.rc2 or greater, you’ll have to upgrade to rspec-rails-2.8.1 or greater.

Because rspec-rails-2.8.1 supports all versions of rails since 3.0, I recommend that you upgrade to rspec-rails-2.8.1 first, and then upgrade to rails-3.2.0.rc2 (or 3.2.0 once it’s out).

Changelog

http://rubydoc.info/gems/rspec-rails/file/Changelog.md

Docs

http://rubydoc.info/gems/rspec-rails
http://relishapp.com/rspec/rspec-rails


more »