Nov/07 27 7:50 pm
models
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