lib/rom/model/attributes.rb
require 'virtus'
require 'active_model' # can't cherry-pick conversion :(
module ROM
module Model
# Mixin for validatable and coercible parameters
#
# @example
#
# class UserAttributes
# include ROM::Model::Attributes
#
# attribute :email, String
# attribute :age, Integer
#
# validates :email, :age, presence: true
# end
#
# user_attrs = UserAttributes.new(email: '', age: '18')
#
# user_attrs.email # => ''
# user_attrs.age # => 18
#
# user_attrs.valid? # => false
# user_attrs.errors # => #<ActiveModel::Errors:0x007fd2423fadb0 ...>
#
# @api public
module Attributes
VirtusModel = Virtus.model(nullify_blank: true)
# Inclusion hook used to extend a class with required interfaces
#
# @api private
def self.included(base)
base.class_eval do
include VirtusModel
include ActiveModel::Conversion
end
base.extend(ClassMethods)
end
# Return model name for the attributes class
#
# The model name object is configurable using `set_model_name` macro
#
# @see ClassMethods#set_model_name
#
# @return [ActiveModel::Name]
#
# @api public
def model_name
self.class.model_name
end
# Class extensions for an attributes class
#
# @api public
module ClassMethods
# Default timestamp attribute names used by `timestamps` method
DEFAULT_TIMESTAMPS = [:created_at, :updated_at].freeze
# Process input and return attributes instance
#
# @example
# class UserAttributes
# include ROM::Model::Attributes
#
# attribute :name, String
# end
#
# UserAttributes[name: 'Jane']
#
# @param [Hash,#to_hash] input The input params
#
# @return [Attributes]
#
# @api public
def [](input)
input.is_a?(self) ? input : new(input)
end
# Macro for defining ActiveModel::Name object on the attributes class
#
# This is essential for rails helpers to work properly when generating
# form input names etc.
#
# @example
# class UserAttributes
# include ROM::Model::Attributes
#
# set_model_name 'User'
# end
#
# @return [undefined]
#
# @api public
def set_model_name(name)
class_eval <<-RUBY
def self.model_name
@model_name ||= ActiveModel::Name.new(self, nil, #{name.inspect})
end
RUBY
end
# Shortcut for defining timestamp attributes like created_at etc.
#
# @example
# class NewPostAttributes
# include ROM::Model::Attributes
#
# # provide name(s) explicitly
# timestamps :published_at
#
# # defaults to :created_at, :updated_at without args
# timestamps
# end
#
# @api public
def timestamps(*attrs)
if attrs.empty?
DEFAULT_TIMESTAMPS.each do |t|
attribute t, DateTime, default: proc { DateTime.now }
end
else
attrs.each do |attr|
attribute attr, DateTime, default: proc { DateTime.now }
end
end
end
end
end
end
end