I tried to make a parent class of a value object in Ruby

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)

What is a value object?

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.

Parent class of value object

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

I tried to make a parent class of a value object in Ruby
I tried to make a client of RESAS-API in Java
I tried to make full use of the CPU core in Ruby
I tried to make a login function in Java
Mapping to a class with a value object in How to MyBatis
I tried a calendar problem in Ruby
I tried to write code like a type declaration in Ruby
I tried to make Numeron which is not good in Ruby
I want to change the value of Attribute in Selenium of Ruby
I wanted to make (a == 1 && a == 2 && a == 3) true in Java
I tried to make a talk application in Java using AI "A3RT"
I want to get the value in Ruby
I tried to make a sample program using the problem of database specialist in Domain Driven Design
Call a method of the parent class by explicitly specifying the name in Ruby
I want to call a method of another class
I tried to create a Clova skill in Java
[Ruby / Rails] Set a unique (unique) value in the class
[Ruby] I want to make a program that displays today's day of the week!
I tried to make a message function of Rails Tutorial extension (Part 1): Create a model
I tried to make an application in 3 months from inexperienced
Determine that the value is a multiple of 〇 in Ruby
I want to make a specific model of ActiveRecord ReadOnly
I just wanted to make a Reactive Property in Java
I tried using Hotwire to make Rails 6.1 scaffold a SPA
I tried to summarize the basic grammar of Ruby briefly
I tried to convert a string to a LocalDate type in Java
I want to create a Parquet file even in Ruby
I tried to implement a buggy web application in Kotlin
I tried to summarize object orientation in my own way.
I tried to make a reply function of Rails Tutorial extension (Part 3): Corrected a misunderstanding of specifications
I tried to make the sample application into a microservice according to the idea of the book "Microservice Architecture".
I tried to make a message function of Rails Tutorial extension (Part 2): Create a screen to display
[VBA] I tried to make a tool to convert the primitive type of Entity class generated by Hibernate Tools to the corresponding reference type.
I tried to solve the problem of "multi-stage selection" with Ruby
I tried to create a simple map app in Android Studio
I tried to implement Ajax processing of like function in Rails
I tried to illuminate the Christmas tree in a life game
[Ruby] I want to put an array in a variable. I want to convert to an array
[Unity] I tried to make a native plug-in UniNWPathMonitor using NWPathMonitor
I tried to deepen my understanding of object orientation by n%
[Java] I tried to make a maze by the digging method ♪
I tried to make a group function (bulletin board) with Rails
[Rails] Implementation of multi-layer category function using ancestry "I tried to make a window with Bootstrap 3"
I tried to express the result of before and after of Date class with a number line
I tried to make the "Select File" button of the sample application created in the Rails tutorial cool
I tried to make a machine learning application with Dash (+ Docker) part2 ~ Basic way of writing Dash ~
I tried to create an API to get data from a spreadsheet in Ruby (with service account)
Make a note of Ruby keyword arguments
I tried to build Ruby 3.0.0 from source
I tried embedding a formula in Javadoc
I tried to make a simple face recognition Android application using OpenCV
[Rails] I want to send data of different models in a form
Procedure to make the value of the property file visible in Spring Boot
[iOS] I tried to make a processing application like Instagram with Swift
I tried to make a Web API that connects to DB with Quarkus
[Ruby] I want to extract only the value of the hash and only the key
I tried to build a Firebase application development environment with Docker in 2020
I made a virtual currency arbitrage bot and tried to make money
I tried to solve the tribonacci sequence problem in Ruby, with recursion.
[Ruby] I want to display posted items in order of newest date
How to change the value of a variable at a breakpoint in intelliJ