I tried to create a value object because of Domain Driven Design, but it was quite troublesome, so I created a parent class. (Since it is made for personal development to speed up development, we do not guarantee that it can be used as it is in the product)
https://qiita.com/kichion/items/151c6747f2f1a14305cc It is an object with the characteristics described in.
If you write it in Ruby, it will be an object like ↓. This example is a value object that contains the user's name. (If you want to make a legitimate definition, it would be better to define something like ʻequal`, but I don't use it so much so I don't define it now.)
domain/user/name.rb
module Domain::User
class Name
attr_reader :first_name, :last_name
def initialize(first_name, last_name)
@first_name = first_name
@last_name = last_name
end
def update_first_name(first_name)
self.new(first_name, @last_name)
end
def update_last_name(last_name)
self.new(@first_name, last_name)
end
def fullname
"#{@last_name} #{@first_name}"
end
end
end
It's convenient to use, but it's a hassle to write a few lines of getters and setters in this routine field. So, I created a parent class that defines these arbitrarily.
domain/base/value_object.rb
module Domain::Base
class ValueObject
attr_reader :changed_fields
FIELDS = []
class << self
#Define the following method new_date = date.update_month(11)
def attr_updater(*attrs)
attrs.map(&:to_sym).each do |defining_attr|
define_method("update_#{defining_attr}") do |defining_value|
values = attrs.map { |attribute| attribute == defining_attr ? defining_value : instance_variable_get("@#{attribute}")}
changed_fields = @changed_fields.include?(defining_attr) ? @changed_fields : @changed_fields + [defining_attr]
self.class.new(*values, changed_fields: changed_fields)
end
end
end
end
# NOTE
# -Basically, only FIELDS and accessors are defined on the value object side.
# -initialize lets the parent use something of this class.
# -It is possible to implement your own initialize. However, in that case, only initialize of this parent class is used from the value object side, and the value object user is not allowed to use initialize of this class directly.
# - changed_Changes such as fields are made only internally
# -Care to keep the initialization arguments of the value object itself as simple as possible
def initialize(*field_values, fields: self.class::FIELDS, changed_fields: [])
define_fields(fields, field_values)
@changed_fields = changed_fields
end
private
def define_fields(fields, field_values)
fields.zip(field_values).each do |field, field_value|
instance_variable_set("@#{field}", field_value)
end
end
end
end
With this, you can provide the original method with the following writing style.
domain/user/name.rb
module Domain::User
class Name < ::Domain::Base::ValueObject
FIELDS = %I(first_name last_name)
attr_reader *FIELDS
attr_updater*FIELDS
def fullname
"#{@last_name} #{@first_name}"
end
end
end
Remarks
――If you use too much metaprogramming black magic, you will not be able to follow it (whether grep or visually), so we have not defined complicated methods.
――You can automate ʻattr_reader, but ʻattr_reader
does not put a burden on the reader, so I follow the basic Ruby writing method.
――This way of writing implicitly indicates that the arguments should be entered in the order specified by FILED at the time of ʻinitialize, and I have not specified it, but I know the problem myself, so now It is avoided by the operation. --
changed_fields` was created as you like because I wanted to update only the fields that were updated when the DB was updated.
Recommended Posts