code tunes

Web applications, software engineering, Ruby on Rails, Cake PHP, JavaScript, etc.

Archive for the ‘Model’ tag

Turning off auto timestamping for testing in Rails

without comments

Suppose that you implemented a functionality that depends on values of created_at or updated_at fields of your models. How do you test it?

If you use fixtures that reside in test/fixture/*.yml files then there is no problem, because the values you set there for created_at and updated_at fields are saved to the database ‘as is’. So you can easily have an article created one week ago:

article:
  title: What a great day
  created_at: <%= 1.week.ago.to_s(:db) %>
  updated_at: <%= 1.week.ago.to_s(:db) %>

However, I don’t use fixtures files myself. I feel a bit dirty using them ;) I find fixture replacement tools far more maintainable. Namely, I love thoughtbot’s Factory Girl. But here comes the problem. This won’t work as expected with Factory Girl:

Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)

That’s because ActiveRecord’s automatic timestamping feature sets Time.now for created_at and updated_at fields overriding our values. At least that’s ActiveRecord’s default behavior. Fortunately it can be disabled with:

Article.record_timestamps = false

Chances are that after creating a model with a custom timestamp we’ll want to turn automatic timestamping back on. But turning it off and on in many places in your unit tests would be pretty cumbersome. Wouldn’t it be cool if you could achieve all of this with a snippet below?

without_timestamping_of Article do
  Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)
end

It turns timestamping off, executes the block and turns timestamping back on. I find it clean and dry. Here’s the code to place in your test_helper.rb:

# test_helper.rb
class Test::Unit::TestCase # or class ActiveSupport::TestCase in Rails 2.3.x
  def without_timestamping_of(*klasses)
    if block_given?
      klasses.delete_if { |klass| !klass.record_timestamps }
      klasses.each { |klass| klass.record_timestamps = false }
      begin
        yield
      ensure
        klasses.each { |klass| klass.record_timestamps = true }
      end
    end
  end
end

Of course you can turn off timestamping for many models at once:

without_timestamping_of Article, Comment, User do
  Factory(:article, :created_at => 1.week.ago, :updated_at => 1.week.ago)
  Factory(:comment, :created_at => 1.day.ago)
  Factory(:user, :updated_at => 5.hours.ago)
end

Hope you like it. If so, share :)

Written by Michał Szajbe

May 9th, 2009 at 12:35 am

Tableless models in Rails

with 3 comments

If you’re developing an application in Ruby on Rails framework, there are many situations when you should consider using tableless models. The advantages you’d get are:

  • consistent application structure, because you’re using models to represent objects in your app
  • routes available for free if you also define controllers dedicated for those models, resulting in RESTful application
  • easy validation (just like with normal models) and other goodies shipped with Active Record
  • easier testing with unit tests
  • form building as easy as with normal models

If you’re familiar with “fat model, skinny controller” concept you’ll find more reasons.

An example situation: you give the users an ability to recommends object they find on the site to their friends. They can recommend photos and articles. You do not want to track those recommendations in database. Your model file would look like this.

class Recommendation < ActiveRecord::Base
  def self.columns() @columns ||= []; end
 
  def self.column(name, sql_type = nil, default = nil, null = true)
    columns << ActiveRecord::ConnectionAdapters::Column.new(name.to_s, default, sql_type.to_s, null)
  end
 
  column :recommendable_type, :string
  column :recommendable_id, :integer
  column :email, :string
  column :body, :text
 
  belongs_to :recommendable, :polymorphic => true
 
  validates_presence_of :recommendable
  validates_associated :recommendable
  validates_format_of :email, :with => /^$|^\S+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,4}|[0-9]{1,4})(\]?)$/ix
  validates_presence_of :body
end

As you can see the only difference in model definition is that you need to provide the columns yourself.

The idea was borrowed from this snippet.

In the next article I’ll cover polymorphic controllers which will help to convert above example into complete one.

Written by Michał Szajbe

July 20th, 2008 at 4:43 pm

Posted in Ruby on Rails

Tagged with , , ,

Not so obvious behavior of $uses in CakePHP

without comments

In CakePHP you declare what models you’re going to use in a controller by defining $uses variable. You can also define it in AppController to have a certain set of models available in every controller. However be careful when doing it because you may put yourself in an unexpected situation as I did recently.

I need an access to User model in beforeFilter and beforeRender actions on every request so I defined those actions in AppController along with:

$uses = array('User');

When I did this I found out that my SessionsController which handles log in and log out logic stopped working immediately. There is no Session model in the app, SessionController works with User model instead:

$uses = array('User');

The error said: “Database table sessions for model Session was not found”. So it tried to find Session model which doesn’t exist as I stated earlier. But why? I have not told Cake to include anything but User model.

To see what exactly has happened we need to look how Cake determines which models it needs to include and it’s done by merging AppController’s $uses with SessionController’s $uses. Merging is done in usual intuitive way with one exception: when SessionController’s $uses is equal to AppController’s $uses then SessionController’s “native” model (Session model here) is also loaded.

Do you have a rational explanation for such behavior? I have none. It’s very unintuitive for me.

Note that the equality of both controllers’ $uses can happen in two situations:

  • both controllers have $uses defined explicitely and they’re equal
  • SessionController doesn’t have $uses defined at all and derives it from AppController (parent)

When SessionController is (===) null or false no models are loaded at all, no mather what’s been defined in AppController.

Written by Michał Szajbe

July 7th, 2008 at 6:11 pm

Posted in CakePHP

Tagged with ,

Read-only model fields

with 2 comments

How many times you found yourself in situation when you were trying to prevent certain fields from being edited by users? I’ve done it myself and I’ve seen others doing it. The most hardcore method is to unset() long list of fields in $this->data variable in controller’s action. It can get difficult to maintain if you change fields’ values from many places - if you forget to unset a sensitive field somewhere this could happen in serious security flaw in your application.

Imagine that your user model has such fields: id, username, password, group_id. Of course you let users change their username and password, but it’s critical to keep them away from group_id field that assigns them to certain user group to make sure they don’t make themselves Administrators!

My proposal is to control these unwanted changes from model level. Let’s call these read-only fields ‘locked fields’. We are going to define which field to lock in our model’s definition and the beforeSave() callback method will take care of the rest. Here is the AppModel:

class AppModel extends Model {
  var $lockedFields = array();
 
  function beforeSave() {
    if (!empty($this->id)) {
      foreach ($this->lockedFields as $field) {
        unset($this->data[$this->name][$field]);
      }
    }
    return true;
  }
 
  function lockFields($fields = array()) {
    if (!is_array($fields)) {
      $fields = array($fields);
    }
    foreach ($fields as $field) {
      if (!in_array($field, $this->lockedFields)) {
        $this->lockedFields[] = $field;
      }
    }
  }
 
  function unlockFields($fields = array()) {
    if (!is_array($fields)) {
      $fields = array($fields);
    }
    foreach ($this->lockedFields as $k => $field) {
      if (in_array($field, $fields)) {
        unset($this->lockedFields[$k]);
      }
    }
  }
}

As you see beforeSave() does it’s job only when a record is being edited ($this->id is set) as it should. UserModel looks like this:

// user.php
class UserModel extends AppModel {
  var $lockedFields = array('group_id');
}

We defined group_id as locked field in model’s definition but thanks to lockFields() and unlockFields() methods defined in AppModel we are able to alter this dinamically. There may be occasion when administrator actually wants to move an user to another group. You can achieve it through a simple method in UserModel.

// user.php
function moveToAdmins($userId) {
  $this->unlockFields('group_id');
  $this->id = $userId;
  $this->saveField('group_id', 1);
  $this->lockFields('group_id');
}

Simple. You also can lock/unlock many fields at once passing them in array.

As always I hope this helps someone :)

Written by Michał Szajbe

November 30th, 2007 at 4:11 pm

Posted in CakePHP

Tagged with ,

Hidden model fields, revisited

without comments

Two days ago I wrote a post about hidden model fields. That solution had a drawback - you couldn’t read a field once it was hidden. There was also a bug - hidden fields were shown when their values was null. Here’s upgraded version:

class AppModel extends Model {
  var $hiddenFields = array();   
 
  /**
   * Sets given fields as hidden.
   *
   * @param array $fields List of fields
   * @return void
   */
  function hideFields($fields = array()) {
    foreach ($fields as $field)
      if (!in_array($field, $this->hiddenFields))
        $this->hiddenFields[] = $field;
  }   
 
  /**
   * Sets given fields as visible.
   *
   * @param array $fields List of fields
   * @return void
   */
  function showFields($fields = array()) {
    foreach ($fields as $field)
      foreach ($this->hiddenFields as $k => $hiddenField)
        if ($hiddenField == $field)
          unset($this->hiddenFields[$k];
  }   
 
  /**
   * Callback function.
   * Removes fields marked as hidden from the results of find operation.
   *
   * @param array $result Results of find operation
   * @param bool $primary Whether model is being queried directly
   */
  function afterSave($result, $primary = false) {
    foreach ($result as &$row)
      foreach ($this->hiddenFields as $field)
        if (array_key_exists($field, $row[$this->name]))
          unset($row[$this->name][$field]);
  }
}

The usage is pretty simple and intuitive. You can mark certain fields as hidden in your model definition, as shown in original post, but you can also show and hide them from wherever you like. For example in controller (assuming that ‘password’ field is marked as hidden in User model):

function show_me_my_password() {
  $this->User->showField(array('password'));
  $this->User->read(null, 1);
  // do something...
}

It’s just example. Generally you should avoid showing passwords as plain text for security reasons. You should always even keep them in encrypted form, ie. hashed. You won’t need to ever decrypt them. Instead of retrieving password from database for comparision during logging in, hash it and compare hashes. Instead of retrieving it to remind it to the user, generate new one.

Written by Michał Szajbe

November 6th, 2007 at 6:47 pm

Posted in CakePHP

Tagged with ,

Hidden model fields

with 4 comments

Probably there are some fields in your models, usually holding sensitive data, that you do not want to display to the end users. In my case these are passwords, activation codes, etc. I want to ensure that values stored in these fields will be never exposed, neither intentionally nor by accident. So I came up with this little solution.

This is implementation of afterFind() callback function that should be placed inside of app_model.php file.

// app_model.php
class AppModel extends Model {
  var $hiddenFields = array();
 
  function afterFind($result, $primary = false) {
    foreach ($result as &$row)
      foreach ($this->hiddenFields as $field)
        if (isset($row[$this->name][$field]))
          unset($row[$this->name][$field]);
    return $result;
  }
}

The last thing to do is to define $hiddenFields variable in your model.

var $hiddenFields = array('password', 'activation_code');

As you may have noticed, values of these fields are retrieved from the database and later unset. If you don’t want them to be retrieved at all (this would be more secure), you should take care of filtering this fields out in beforeFind() callback method, however this could be quite cumbersome. For me it was OK enough to just unset them after retrieval, so I even didn’t try to do it through beforeFind().

There are also drawbacks. For example you can’t retrieve someone’s password and send it by e-mail when someone forgets it (in fact you can still get it with model’s query() method, but it’s not a ‘clean’ way). In such situations you should generate new random password instead of reminding the forgotten one.

If you have any views on this, please leave a comment.

Written by Michał Szajbe

November 4th, 2007 at 1:55 pm

Posted in CakePHP

Tagged with ,