Nov/07 27 7:50 pm

models

rails, technical

no comments


Rails’ design provides for a complete implementation of the Model-View-Controller (MVC) pattern. The model element in particular is implemented as a self-contained layer responsible for all interactions with the data storage sub system. As such the model offers the logically correct place to implement all business rules related to the correct handling of data throughout the application.

A well developed and tested model gives a great comfort and luxury that guarantees that whatever view-controller is put over the top, or how it is implemented, the integrity of the database with its respect for all business rules is complete and guaranteed.

This sounds like a bit of mouthful but is in fact very easy to implement. There is not actually very much that needs to go into a model therefore it is quite simple to ensure it is correct.

associations

Models define the relationships with other models. For example given a one-to-many relationship between groups and users (a group contains many users, a user belongs to exactly one group) requires the following associations:

class Group < ActiveRecord::Base
  has_many :users
end
		
class User < ActiveRecord::Base
  belongs_to :group
end

validation

Having all validation in the model is very powerful as a correct model guarantees complete correctness within the database. Rails contains a number of built-in validators. For example, if the user model requires that a username and email is given, all fields have a maximum length, the email field contains a valid format of email address, the password is confirmed in a separate field and the username is unique:

class User < ActiveRecord::Base
  validates_presence_of :username
  validates_presence_of :email
		
  validates_length_of :username, :maximum => 12, :allow_nil => true
  validates_length_of :email, :maximum => 80, :allow_nil => true
		
  validates_format_of :email, :with => /^([^@\s]+)@((?:[-_a-z0-9]+\.)+[a-z]{2,})$/i, :allow_nil => true

  validates_confirmation_of :password

  validates_uniqueness_of :username
end

Something of interest in the above example is the :allow_nil => true on some of the validation. We have said that the email address is compulsory, get when validating the format we allow it to be blank. Surely this is a contradiction? It’s not and the reason is to avoid multiple error messages if a compulsory field is left blank. If the email address is not given, the validates_presence_of will fail validation and trigger an error message. However, the validates_format_of would also generate an error as blank is not a valid address. Thus, tell the validation of the format to ignore a blank string.

Whilst the built in validators in Rails are very useful, any specific validation required which is not covered by them can be added simply by putting the code in a method called validate. This will be automatically called during validation. For example, a product with an effective date and an expiry date should validate that the expiry date is not before the effective date.

class Product < ActiveRecord::Base
  def validate
    errors.add(:expiry_date, 'Expiry date must not be before effective date') if !expiry_date.nil? && expiry_date < effective_date
  end
end

As we will see later, validation logic is also useful within the view as compulsory fields and error fields can be automatically marked for the convenience of the user simply by consulting the model.

attributes

Anyone used to a Java/Hibernate type environment will be familiar with the excessive repetition required to define a simple class and attributes - within the DDL to build the database table, the ORM mapping file, the Java class and finally the required getter and setter methods. Put a column in a table and by default Rails will do the rest for nothing. If the product table contains a column description, then the Product class will implicitly contain a getter method, description and a setter method, description=(description). This is exceedingly useful and saves loads of development time, but of course, can be expanded upon and overwritten if a particular application demands it.

For example, suppose the product name should be kept in the database in uppercase only. This can be achieved by simply overwritting the setter method.

class Product < ActiveRecord::Base
  def product=(product)
    self[:product] = product.upcase
  end
end

filters

Like the validate method, filters can be defined to be called at specific points in processing the model. For example, if a parameters table always has exactly one row, it will be added during installation and the model must not permit insertion or deletion of rows.

class Parameters < ActiveRecord::Base
  before_create   :filter_create
  before_destroy  :filter_delete
		
  def filter_create
    if !Parameter.count.zero?
      raise 'Insertion of multiple parameter records is not permitted.'
    end
  end
		
  def filter_delete
    raise 'Deletion of parameter records is not permitted.'
  end
end

leave a comment

previously

archives

categories

search