Create a model¶
Paths on this page are relative to your plugin directory, for example plugins/easyproject/easy_plugins/modification_acme/.
Model¶
Model files live in app/models. A model inherits from ApplicationRecord and has a singular name that describes the object it represents.
Prefix new entities with Easy (or with your plugin name, see Prefixes) to avoid clashes with Redmine names. See ADR 0001.
The namespace reflects the file path: EasyModule::MyModel is in app/models/easy_module/my_model.rb.
Model structure¶
Start the model with the concerns it includes, such as Redmine::SafeAttributes, followed by model properties such as table_name.
Then define associations: belongs_to first, then has_many and has_one. Give each association inverse_of, and class_name when the association name differs from the class name. belongs_to is required by default; add optional: true to change that. Give every has_many a dependent option.
belongs_to :author, class_name: "::User", inverse_of: :my_models
has_many :items, class_name: "::EasyModule::MyItem", dependent: :destroy, inverse_of: :my_model
has_one :address, class_name: "::EasyModule::MyAddress", dependent: :destroy, inverse_of: :my_model
After associations come callbacks and validations. Callbacks may use on: to limit the action they run on. validate takes the name of a custom method; validates takes built-in validators such as presence, uniqueness and length. Put all validators of one attribute on one line, and attributes that share validators on one line as well.
before_validation :set_author, on: :create
validates :name, presence: true, uniqueness: true, length: { maximum: 255 }
validates :description, :text, presence: true, length: { maximum: 1000 }
validate :custom_my_model_validation
Next come scopes and enums. A scope has a name and a lambda; common ones are visible, like and active. An enum takes the attribute name and a hash of values, and defines scopes, getters and setters. Use prefix: or suffix: to avoid collisions with existing methods.
scope :visible, -> { all }
scope :like, ->(q) { where(arel_table[:name].matches("%#{q}%")) }
enum :status, { active: 0, disabled: 1 }, prefix: true
Before the methods, call the class methods of included concerns: safe_attributes (the allow list of attributes assignable from user input) and the acts_as_* declarations that add model behaviour.
safe_attributes "name", "description", "status"
acts_as_searchable columns: %W[#{table_name}.name #{table_name}.description],
date_column: "#{table_name}.created_at",
permission: :view_my_models,
project_key: :project_id
The methods come last. Public methods describe an instance or implement model logic; private methods mostly implement callbacks and validations. Common public methods:
def to_s
name # or some other instance meaningful representation
end
def visible?(user = User.current)
# implementation
end
def editable?(user = User.current)
# implementation
end
def deletable?(user = User.current)
# implementation
end
Migrations¶
Every model needs a table. See Database and data migrations.
Tests¶
Model specs live in test/spec/models and are named model_name_spec.rb. Cover methods, scopes, validations and callbacks.
Factories¶
Factories prepare the instances your specs work with. Define one factory per model in test/factories/model_name.rb.
| test/factories/my_model.rb | |
|---|---|
| example with trait | |
|---|---|