Sword 1.0b5 · A simple and friendly weblogging tool for academic environments created by Fingertips design & development.

 
 
 
 
 

How to make sure an attribute doesn't get updated

TrackBack link: http://blog.markjuh.net/markjuh/trackback/2005/8/8/how_to_make_sure_an_attribute_doesn_t_ge

Published on 8 August 2005 at 10:55, updated at 20:53.

In category Rails.

I have a Rails model in which I don’t want the title attribute to change. I implemented this with a before_update:

  before_update :check_title_unchanged

  protected

  def check_title_unchanged
    Post.find(id).title == title
  end

This didn’t quite work because of an ActiveRecord bug I wrote about before.

When talking to Michael Koziarski at #rubyonrails about this bug, he made clear that I have at least one problem with my code and that I could have made a different implementation.

First to tackle the real problem in my code. The problem with my implementation is that it isn’t thread-safe, because the title could have been changed by another thread after we’ve fetched the object from the database. And hence our title wouldn’t have changed, but we still wouldn’t be able to save our model.

So the better way of doing it is by adding an after_find which is documented at the Rails Wiki. And also updating the before_update method accordingly.

  before_update :check_title_unchanged

  attr_accessor :original_title

  protected

  def after_find
    @original_title = title
  end

  def check_title_unchanged
    @original_title == title
  end

Now to take a look at another approach to tackle this. We could also override the validate method. The validate method has the following documentation:

Overwrite this method for validation checks on all saves and use Errors.add(field, msg) for invalid attributes.

So we could now change our code to:

  attr_accessor :original_title

  protected

  def after_find
    @original_title = title
  end

  def validate
    @original_title==title
  end

And if we would want to make it really nice, we would use Errors.add to add a descriptive error message, which is then added to the list of errors that will be displayed by error_messages_for. This would be the only reason I would use this approach instead of using a before_update.

So we could do:

  def validate
    result = (@original_title==title)

    if !result
      Errors.add(:title, "Title cannot be changed.")
    end
  end

Conclusion: in my project I have no use for the error message, so I don’t really see the point in overriding the validate method. But now I at least know the available options and can choose one wisely. So if I just need to make sure the title cannot be updated by a programming mistake in a controller, I use the before_update. If the user needs to be presented with a nice error message why they cannot change the title, then I would choose for overriding the validate method.

Update: I tried to put this nice piece of code to work, but it breaks my CRUD tests. My CRUD tests might behave a little different from the actual application, but still, they shouldn’t break. I actually create an object, save it, update it and then save it again. This failed, because I did not do a find for the same object and hence original_title is not yet set. So I’ll leave my implementation as it is currently. The issue with thread-safety is not that shocking in my case, since the title really shouldn’t be changed anyway.

Significance of Unit Testing in Rails

TrackBack link: http://blog.markjuh.net/markjuh/trackback/2005/8/7/significance_of_unit_testing_in_rails

Published on 7 August 2005 at 03:14, updated on 8 August 2005 at 09:50.

In category Test Driven Development.

Today I’ve been explaining some basics of Ruby on Rails to a friend of mine. With a couple of friends we’re building a front-end for LEDR, which is basically an educational document repository, which we intend to use at Eindhoven University of Technology.

We only went into the model part of the MVC design pattern. And since Rails applications are especially suited for Test Driven Development, I decided to start showing Rails by writing unit tests. So we first started with the generation of a model and then wrote some basic CRUD tests for them. We had been writing down requirements for some of our models earlier today and could now write these requirements in the tests directly. We heavily borrowed test code from other projects I’m doing. So we had some default tests for each possible model requirement we had come up with.

Time to run rake! And boy, did we get failures and errors!

We were using SQLite, had created the databases and even made sure config/database.yml contained the right settings. All nice, but we had forgotten to install sqlite-ruby on our development system. (Ironically enough, our production server already has it installed.) We should have definitely read Howto Use SQLite in Rails a bit better.

If you are not even interested in writing tests, running the automatically generated test for the model would have already shown this.

After getting this out of the way, our tests were still failing… Well, duh! We forgot to even create a table in the database for the model. Again, just running the automatically generated test would have shown this.

So, writing the table and importing it in SQLite (which by the way is a painful process when you are used to PHPMyAdmin or YourSQL for all your database editing) solved at least a bunch of the errors/failures. Basic CRUD tests were now at least passing.

So on with the more interesting aspects. Our tests clearly showed we were missing validates_presence_of, validates_uniqueness_of and validates_format_of. Since we had some well-defined tests, it was really clear what to add in the corresponding model.

Still, one test was failing… The test that made sure that we couldn’t change the code field of the model. Maybe not as clear, but after some thinking we added the following straightforward before_update to the model:


  def check_code_changed
    Course.find(id).code == code
  end

Now our test should have all passed.

Unfortunately there is a bug in Rails I wrote about some time ago. So we first had to apply the patch I had made for the bug. Then everything worked just fine.

After this we wrote another two models and created relationships between them, all driven by tests defined before actually writing any model code. And every since test-case that failed showed us exactly where we had to find the problem and showed us at least a hint of where to find our mistakes or where to add model code; even more specific, it often showed us which code to add.

We also had a problem in the SQL code that updated our SQLite database. It wasn’t actually a query performed by Rails, but we needed it to update our table to the lastest version. We had totally forgotten to add a commit at the end of the transaction. Our tests showed our missing field in the table quite nicely.

I can conclude that it convinced us both that Test Driven Development is the way to go when developing Rails applications. It helps you detect so many things, sometimes not even directly related to the specific model (as we can see above). Lots of times before I had seen tests as a necessary evil, but today we both got really excited by writing tests and seeing all the positive results we got from it. Our tests even made it possible to write Rails code after having a couple of glasses of wine. ;)