ar2dto 0.0.0 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 95a346915889755102f7d55c39719a0b9448072537b4b5f5eb0fa2236be8e94b
4
- data.tar.gz: 80a22c29ae6e5cc0544ff5b8bfc8592e22aead54188168fffd59edd7cc851424
3
+ metadata.gz: c868cca39990a602ad330973d246059d32672f0c7db8bf1ef20a211b666bb851
4
+ data.tar.gz: 4cf8f1a39fbfb81655ab8b848502694102f1b72a14f5c5201d47cf6cad55c081
5
5
  SHA512:
6
- metadata.gz: 5fb6538cecb77a3b787249405692c9664d177e276a2ba51f942afd2546a0bdd1137731e58cb220bd094265e991e7d1d73061e63c9996125c56b7ecd2d6b5527d
7
- data.tar.gz: 73be4eecaedcc6ab2a2904284f0251eb4e7259d590336eb0cf8ae6214617aba52fb5ed1465b5026b4212f2a6b9450ba5ca3c7e7cbcdb3071357c48e267d638d0
6
+ metadata.gz: 6090a77b929d7e07c5c03c256b4a29c138d977e0f97b225a76434db04daa18be4735713a39dae663d3130097ff3c6b496e7673747be3cb02062e072bc949d2a5
7
+ data.tar.gz: 48be3084e1c4840b6bda5dccb572b021f0593bf83f62a70f6b48618784c127891fa66d91749ea6ff9967dda1b8197d48545447c807a61a70028ccfdbb06113db
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # AR2DTO ![AR2DTO](docs/images/logo.png)
2
2
 
3
- AR2DTO (ActiveRecord to DTO, pronounced R2-D2 or Artoo-Detoo) is a gem that lets you create DTOs (data transfer objects) from your ActiveRecord models. It is a simple and small gem with the goal of encouraging the usage of simpler objects across an app rather than ActiveRecord models, to help with coupling issues in large Rails apps.
3
+ AR2DTO (ActiveRecord to DTO, pronounced R2-D2 or Artoo-Detoo) is a gem that lets you create [DTOs](https://martinfowler.com/eaaCatalog/dataTransferObject.html) (data transfer objects) from your ActiveRecord models. It is a simple and small gem to encourage the usage of simpler objects across an app rather than ActiveRecord models, to help with coupling issues in large Rails apps.
4
4
 
5
5
  ![CI](https://github.com/santib/ar2dto/workflows/CI/badge.svg)
6
6
 
@@ -10,17 +10,27 @@ AR2DTO (ActiveRecord to DTO, pronounced R2-D2 or Artoo-Detoo) is a gem that lets
10
10
  - [Why AR2DTO?](#why-ar2dto)
11
11
  - [Installation](#installation)
12
12
  - [Usage](#usage)
13
+ - [Global configuration](#global-configuration)
13
14
  - [Setting up your models](#setting-up-your-models)
15
+ - [DTO class](#dto-class)
16
+ - [#to_dto](#to_dto)
17
+ - [.to_dto](#to_dto-1)
14
18
  - [Development](#development)
15
19
  - [Contributing](#contributing)
16
20
  - [License](#license)
17
21
  - [Code of Conduct](#code-of-conduct)
18
22
 
19
23
  ## Motivation
20
- TBD
24
+
25
+ ActiveRecord is a very powerful tool and extensively used in most Rails apps. When working on large Rails apps, having such powerful objects all over the place can have a negative impact on the maintainability of the app. This is even more clear when trying to create domain boundaries within a Rails monolith. When there is communication between different components you probably don't want to share an ActiveRecord model with other components, if you do that, you'll be giving direct access to your component's tables from anywhere. For that reason, we want to create POROs that look like ActiveRecord models but that are much simpler and only carry data. This could be done by hand, but with this gem, we are trying to help you avoid having to write all the boilerplate to create these objects. As a corollary, by using this gem you are standardizing how things are done, and how your data-only objects look like.
21
26
 
22
27
  ### Why AR2DTO?
23
- TBD
28
+
29
+ - It lets you work with objects that are similar to ActiveRecord models but without DB access or business logic.
30
+ - It impedes ActiveRecord models leaking through its methods.
31
+ - It helps you reduce boilerplate.
32
+ - It provides a standard way to work with data-only objects.
33
+ - It is a very small gem focused on solving one specific problem.
24
34
 
25
35
  ## Installation
26
36
 
@@ -39,10 +49,115 @@ Or install it yourself as:
39
49
  $ gem install ar2dto
40
50
 
41
51
  ## Usage
42
- TBD
52
+
53
+ In the following sections, we explain the API provided by the gem and some basic usage.
54
+
55
+ Many aspects of AR2DTO are [configurable for individual models](#setting-up-your-models); typically this is achieved by passing options to the has_dto method within a given model. Some aspects of AR2DTO are [configured globally](#global-configuration) for all models.
56
+
57
+ ### Global Configuration
58
+ Global configuration options affect all threads and models where has_dto has been defined. A common place to put these settings is in a Rails initializer file such as `config/initializers/ar2dto.rb`.
59
+ These settings are assigned directly on the `AR2DTO.configure` object.
60
+
61
+ Configuration options are:
62
+ - `active_model_compliace`: DTO objects behaves like `ActiveModel` objects to play well with other parts of Rails and its ecosystem. Defaults to `true`.
63
+ - `except`: array of attributes to exclude from the DTO. Defaults to `[]`.
64
+ - `delete_suffix`: suffix to be delete from the model name. Defaults to `nil`.
65
+ - `add_suffix`: suffix to be added to the model name. Defaults to `"DTO"`.
66
+
67
+ Syntax examples:
68
+
69
+ ```ruby
70
+ # config/initializers/ar2dto.rb
71
+
72
+ AR2DTO.configure do |config|
73
+ config.active_model_compliance = true
74
+ config.except = [:updated_at]
75
+ config.delete_suffix = nil
76
+ config.add_suffix = "DTO"
77
+ end
78
+ ```
79
+
80
+ OR
81
+
82
+ ```ruby
83
+ # config/initializers/ar2dto.rb
84
+
85
+ AR2DTO.configure.active_model_compliance = true
86
+ AR2DTO.configure.except = [:updated_at]
87
+ AR2DTO.configure.delete_suffix = nil
88
+ AR2DTO.configure.add_suffix = "DTO"
89
+ ```
90
+
91
+ These options are intended to be set only once, during app initialization.
43
92
 
44
93
  ### Setting up your models
45
- TBD
94
+
95
+ To use the gem, you need to add `has_dto` to your ActiveRecord models. For example:
96
+ ```ruby
97
+ class User < ApplicationRecord
98
+ has_dto
99
+ end
100
+ ```
101
+
102
+ This will dynamically create a class called `UserDTO` and will add two methods to your ActiveRecord model: [#to_dto](#to_dto) and [.to_dto](#to_dto-1).
103
+
104
+ ### DTO class
105
+
106
+ This class is dynamically created based on your models that declare `has_dto`. The goal of these classes is to be data-only. They don't have access to the DB, business logic, or calculate things on the fly. They just store the data and provide them in a read-only fashion through plain simple methods.
107
+
108
+ In addition to that, and optionally, you can have these objects be compliant with the [ActiveModel API](https://github.com/rails/rails/blob/main/activemodel/lib/active_model/lint.rb), you can do that by configuring it globally with:
109
+
110
+ ```ruby
111
+ # config/initializers/ar2dto.rb
112
+
113
+ AR2DTO.configure do |config|
114
+ config.active_model_compliance = true
115
+ end
116
+ ```
117
+
118
+ With this, it'll be even easier to interchange ActiveRecord models for DTOs, because other parts of Rails and other gems will continue to work (e.g. Rails route helpers).
119
+
120
+ ### #to_dto
121
+
122
+ When calling `#to_dto` on an ActiveRecord model, a DTO object will be initialized with the model attributes. As an example:
123
+
124
+ ```ruby
125
+ user = User.create!(name: 'John', email: 'john@example.com')
126
+ user_dto = user.to_dto
127
+ # #<UserDTO:0x00007fab8ce66a10 @name="John" @email="john@example.com">
128
+ ```
129
+
130
+ `user_dto` will be an instance of `UserDTO` and, by default, it will be initialized with the same attributes as the model, that is: `id`, `name`, `email`, `created_at`, and `updated_at`.
131
+
132
+ You can then use `user_dto` across your app, and even share it with other components, without having to worry about others making queries, modifying data, or even running business logic, where they shouldn't.
133
+
134
+ This method accepts the same options as `ActiveRecord`'s `#as_json`, they are `except`, `only`, `methods`, and `include`.
135
+
136
+ `except`
137
+ Excludes attributes from the model when creating the DTO.
138
+
139
+ `only`
140
+ Selects the only attributes that should be included when creating the DTO.
141
+
142
+ `methods`
143
+ Run methods defined in the model and stores the values into the DTO as attributes.
144
+
145
+ `include`
146
+ Includes an association into the DTO. The association is also converted into a DTO in case of a `has_one`/`belongs_to` association, or into an `Array` of DTOs in case of a `has_many` association. The associations accepts the same options explained above.
147
+
148
+ ### .to_dto
149
+
150
+ This method is similar to `#to_dto` but meant for `ActiveRecord::Relation`. So that running:
151
+
152
+ ```ruby
153
+ User.last(10).to_dto
154
+ ```
155
+
156
+ will return an `Array` consisting of 10 `UserDTO`. With this you are forcing the executing of the query, having collections of simple data objects, and avoiding other parts of the app from modifying the query.
157
+
158
+ It accepts the same options as `#to_dto` and uses them create each DTO.
159
+
160
+ :warning: **Warning!** Given that this method executes the query and brings records into memory, you have to be careful when and how to use it. You may not want to bring all records from a large table into memory. Consider combining it with things such as pagination or batch processing.
46
161
 
47
162
  ## Development
48
163
 
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AR2DTO
4
+ module ActiveModel
5
+ def self.included(base)
6
+ base.include ::ActiveModel::Conversion
7
+ base.extend ::ActiveModel::Naming
8
+ base.extend ::ActiveModel::Translation
9
+ base.extend ::AR2DTO::ActiveModel::ClassMethods
10
+
11
+ base.class_eval do
12
+ def persisted?
13
+ id.present?
14
+ end
15
+
16
+ def errors
17
+ @errors ||= ::ActiveModel::Errors.new(self.class.original_model)
18
+ end
19
+
20
+ def to_partial_path
21
+ self.class.original_model._to_partial_path
22
+ end
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def model_name
28
+ ::ActiveModel::Name.new(original_model)
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AR2DTO
4
+ class Config
5
+ include Singleton
6
+
7
+ def self.reset!
8
+ instance.active_model_compliance = true
9
+ instance.except = []
10
+ instance.delete_suffix = nil
11
+ instance.add_suffix = "DTO"
12
+ end
13
+
14
+ attr_accessor :active_model_compliance, :except, :delete_suffix, :add_suffix
15
+
16
+ def initialize
17
+ @active_model_compliance = true
18
+ @except = []
19
+ @delete_suffix = nil
20
+ @add_suffix = "DTO"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AR2DTO
4
+ class Converter
5
+ attr_reader :model, :options
6
+
7
+ def initialize(model, options)
8
+ @model = model
9
+ @options = apply_configs(options)
10
+ end
11
+
12
+ def serializable_hash
13
+ hash = model.serializable_hash(options&.except(:methods, :include))
14
+ hash = add_methods(hash)
15
+ add_associations(hash)
16
+ end
17
+
18
+ private
19
+
20
+ def apply_configs(options)
21
+ options[:except] = Array(model.class.ar2dto.except) | Array(options[:except])
22
+ options
23
+ end
24
+
25
+ def add_methods(hash)
26
+ options&.dig(:methods)&.each do |method|
27
+ result = model.send(method)
28
+ result = if result.respond_to?(:to_dto)
29
+ result.to_dto
30
+ else
31
+ result.as_json
32
+ end
33
+ hash[method.to_s] = result
34
+ end
35
+ hash
36
+ end
37
+
38
+ def add_associations(hash)
39
+ includes.each do |association, opts|
40
+ records = model.send(association)
41
+ hash[association.to_s] = records ? records_dto(records, opts) : nil
42
+ end
43
+ hash
44
+ end
45
+
46
+ def includes
47
+ includes = options&.dig(:include)
48
+ return includes if includes.is_a?(Hash)
49
+
50
+ Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }.to_h
51
+ end
52
+
53
+ def records_dto(records, opts)
54
+ if records.respond_to?(:to_ary)
55
+ records.to_ary.map { |a| a.to_dto(opts) }
56
+ else
57
+ records.to_dto(opts)
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/ar2dto/dto.rb ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AR2DTO
4
+ class DTO
5
+ def self.[](original_model)
6
+ Class.new(self) do
7
+ include ::AR2DTO::ActiveModel if original_model.ar2dto.active_model_compliance
8
+ define_singleton_method(:original_model) { original_model }
9
+ end
10
+ end
11
+
12
+ def initialize(attributes = {})
13
+ attributes.each { |key, value| define_singleton_method(key) { value } }
14
+ super()
15
+ end
16
+
17
+ def as_json(options = nil)
18
+ attribute_names = self.class.original_model.attribute_names
19
+ attribute_names.map { |name| [name.to_sym, send(name)] }.to_h.as_json(options)
20
+ end
21
+
22
+ def ==(other)
23
+ if other.instance_of?(self.class)
24
+ as_json == other.as_json
25
+ else
26
+ super
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AR2DTO
4
+ # Extensions to `ActiveRecord::Base`.
5
+ module HasDTO
6
+ def self.included(base)
7
+ base.extend ::AR2DTO::HasDTO::ClassMethods
8
+ end
9
+
10
+ module ClassMethods
11
+ # Declare this in your model to expose the DTO helpers.
12
+ #
13
+ # @api public
14
+ def has_dto(options = {})
15
+ include ::AR2DTO::HasDTO::InstanceMethods
16
+ ar2dto.setup_config(options)
17
+
18
+ begin
19
+ ar2dto.namespaced_class_name.constantize
20
+ rescue NameError
21
+ ar2dto.namespace.const_set(ar2dto.class_name, AR2DTO::DTO[self])
22
+ end
23
+ end
24
+
25
+ # @api public
26
+ def to_dto(options = {})
27
+ all.map { |record| record.to_dto(options) }
28
+ end
29
+
30
+ # @api public
31
+ def ar2dto
32
+ @ar2dto ||= AR2DTO::ModelConfig.new(self)
33
+ end
34
+ end
35
+
36
+ # Wrap the following methods in a module so we can include them only in the
37
+ # ActiveRecord models that declare `has_dto`.
38
+ module InstanceMethods
39
+ # @api public
40
+ def to_dto(options = {})
41
+ self.class.ar2dto.namespaced_class_name.constantize.new(
42
+ AR2DTO::Converter.new(self, options).serializable_hash
43
+ )
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AR2DTO
4
+ class ModelConfig
5
+ attr_reader :model, :model_config
6
+
7
+ def initialize(model)
8
+ @model = model
9
+ end
10
+
11
+ def setup_config(model_config)
12
+ @model_config ||= model_config
13
+ end
14
+
15
+ def active_model_compliance
16
+ @active_model_compliance ||= global_config.active_model_compliance
17
+ end
18
+
19
+ def except
20
+ @except ||= Array(global_config.except) | Array(model_config[:except])
21
+ end
22
+
23
+ def class_name
24
+ @class_name ||= namespaced_class_name.split("::").last
25
+ end
26
+
27
+ def namespace
28
+ @namespace ||= namespaced_class_name.deconstantize.presence&.constantize || Object
29
+ end
30
+
31
+ def namespaced_class_name
32
+ @namespaced_class_name ||= model_config[:class_name] || model_name_replaced_suffix
33
+ end
34
+
35
+ private
36
+
37
+ def model_name_replaced_suffix
38
+ "#{model.name.delete_suffix(global_config.delete_suffix.to_s)}#{global_config.add_suffix}"
39
+ end
40
+
41
+ def global_config
42
+ AR2DTO::Config.instance
43
+ end
44
+ end
45
+ end
data/lib/ar2dto.rb CHANGED
@@ -1,8 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_record"
4
+ require "active_model"
5
+ require "singleton"
6
+
7
+ require_relative "ar2dto/active_model"
8
+ require_relative "ar2dto/dto"
9
+ require_relative "ar2dto/config"
10
+ require_relative "ar2dto/converter"
11
+ require_relative "ar2dto/model_config"
3
12
  require_relative "ar2dto/version"
13
+ require_relative "ar2dto/has_dto"
14
+
15
+ ActiveRecord::Base.include ::AR2DTO::HasDTO
4
16
 
5
17
  module AR2DTO
6
18
  class Error < StandardError; end
7
- # Your code goes here...
19
+
20
+ def self.configure
21
+ @config ||= AR2DTO::Config.instance
22
+ yield @config if block_given?
23
+ @config
24
+ end
8
25
  end
metadata CHANGED
@@ -1,15 +1,31 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ar2dto
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.0
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Santiago Bartesaghi
8
+ - Martín Jaime Morón
9
+ - Sebastian Herrera
8
10
  autorequire:
9
11
  bindir: bin
10
12
  cert_chain: []
11
- date: 2022-06-05 00:00:00.000000000 Z
13
+ date: 2022-07-07 00:00:00.000000000 Z
12
14
  dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activemodel
17
+ requirement: !ruby/object:Gem::Requirement
18
+ requirements:
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: '5.2'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ version: '5.2'
13
29
  - !ruby/object:Gem::Dependency
14
30
  name: activerecord
15
31
  requirement: !ruby/object:Gem::Requirement
@@ -24,9 +40,95 @@ dependencies:
24
40
  - - ">="
25
41
  - !ruby/object:Gem::Version
26
42
  version: '5.2'
43
+ - !ruby/object:Gem::Dependency
44
+ name: database_cleaner-active_record
45
+ requirement: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - "~>"
48
+ - !ruby/object:Gem::Version
49
+ version: 1.8.0
50
+ type: :development
51
+ prerelease: false
52
+ version_requirements: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - "~>"
55
+ - !ruby/object:Gem::Version
56
+ version: 1.8.0
57
+ - !ruby/object:Gem::Dependency
58
+ name: rake
59
+ requirement: !ruby/object:Gem::Requirement
60
+ requirements:
61
+ - - "~>"
62
+ - !ruby/object:Gem::Version
63
+ version: 13.0.1
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: 13.0.1
71
+ - !ruby/object:Gem::Dependency
72
+ name: rspec
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: 3.9.0
78
+ type: :development
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: 3.9.0
85
+ - !ruby/object:Gem::Dependency
86
+ name: rubocop
87
+ requirement: !ruby/object:Gem::Requirement
88
+ requirements:
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: 1.28.2
92
+ type: :development
93
+ prerelease: false
94
+ version_requirements: !ruby/object:Gem::Requirement
95
+ requirements:
96
+ - - "~>"
97
+ - !ruby/object:Gem::Version
98
+ version: 1.28.2
99
+ - !ruby/object:Gem::Dependency
100
+ name: sqlite3
101
+ requirement: !ruby/object:Gem::Requirement
102
+ requirements:
103
+ - - "~>"
104
+ - !ruby/object:Gem::Version
105
+ version: 1.4.2
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ requirements:
110
+ - - "~>"
111
+ - !ruby/object:Gem::Version
112
+ version: 1.4.2
113
+ - !ruby/object:Gem::Dependency
114
+ name: zeitwerk
115
+ requirement: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - "~>"
118
+ - !ruby/object:Gem::Version
119
+ version: 2.6.0
120
+ type: :development
121
+ prerelease: false
122
+ version_requirements: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - "~>"
125
+ - !ruby/object:Gem::Version
126
+ version: 2.6.0
27
127
  description:
28
128
  email:
29
129
  - santib@hey.com
130
+ - martinmoron7@gmail.com
131
+ - sebaherrera93@gmail.com
30
132
  executables: []
31
133
  extensions: []
32
134
  extra_rdoc_files: []
@@ -34,7 +136,12 @@ files:
34
136
  - LICENSE.txt
35
137
  - README.md
36
138
  - lib/ar2dto.rb
37
- - lib/ar2dto/base.rb
139
+ - lib/ar2dto/active_model.rb
140
+ - lib/ar2dto/config.rb
141
+ - lib/ar2dto/converter.rb
142
+ - lib/ar2dto/dto.rb
143
+ - lib/ar2dto/has_dto.rb
144
+ - lib/ar2dto/model_config.rb
38
145
  - lib/ar2dto/version.rb
39
146
  homepage: https://github.com/santib/ar2dto
40
147
  licenses:
@@ -44,6 +151,7 @@ metadata:
44
151
  source_code_uri: https://github.com/santib/ar2dto
45
152
  bug_tracker_uri: https://github.com/santib/ar2dto/issues
46
153
  changelog_uri: https://github.com/santib/ar2dto/releases
154
+ rubygems_mfa_required: 'true'
47
155
  post_install_message:
48
156
  rdoc_options: []
49
157
  require_paths:
@@ -59,7 +167,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
59
167
  - !ruby/object:Gem::Version
60
168
  version: '0'
61
169
  requirements: []
62
- rubygems_version: 3.1.6
170
+ rubygems_version: 3.3.7
63
171
  signing_key:
64
172
  specification_version: 4
65
173
  summary: Easing the creation of DTOs from your ActiveRecord models.
data/lib/ar2dto/base.rb DELETED
@@ -1,26 +0,0 @@
1
- module AR2DTO
2
- module Base
3
- def self.included(model)
4
- namespace = model.name.deconstantize.presence&.constantize || Object
5
-
6
- namespace.const_set("#{model.name.split('::').last}DTO", Class.new do
7
- include ::ActiveModel::Model
8
- attr_reader :attributes
9
- attr_accessor *model.column_names
10
-
11
- def initialize(attributes)
12
- @attributes = attributes
13
- super
14
- end
15
-
16
- def ==(other)
17
- attributes == other.attributes
18
- end
19
- end)
20
- end
21
-
22
- def to_dto
23
- "#{self.class.name}DTO".constantize.new(attributes)
24
- end
25
- end
26
- end