オブジェクト向けRails
38287 ワード
Primitive Obsession
次のコードを見たことがありますか.# == Schema Information
#
# Table name: sellers
#
# id :integer not null, primary key
# name :string
# role :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Seller < ApplicationRecord
def role_name
if role == :admin
"관리자"
elsif role == :normal
"일반 판매자"
else
"뉴비"
end
end
def role_description
if role == :admin
"관리해요"
elsif role == :normal
"물건을 팔아요"
else
"새로 들어왔어요"
end
end
def work?
if role == :admin
true
elsif role == :normal
false
else
false
end
end
end
すべてのコードがそうではないかもしれませんが、そのコードを見たことがあるかもしれません.以上のコードは冗長で読みにくい.また、新しい機能を追加する場合は、多くのif~else間を切り替える必要があります.どうやって直せるの?メソッドの名前role_name
とrole_description
にヒントがあります.
対象に向かって考える
Role
というPORO(Plain Old Ruby Object)
を抽出します.以下の手順で作成します.# app/models/seller/role.rb
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [
new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
]
end
def of(code)
if code.is_a?(String)
code = code.to_sym
end
all.find { |role| role.code == code }
end
protected_methods :new
end
def initialize(code, name, description)
@code = code
@name = name
@description = description
end
end
さらに、Seller
では、以下の変更が発生します.class Seller < ApplicationRecord
def role
@role_object ||= Seller::Role.of(super)
end
delegate :description, :name, to: :role, prefix: true
# TODO
def work?
if role == :admin
true
elsif role == :normal
false
else
false
end
end
end
delegate
を使用すると、同じインタフェースを提供しながら、より凝集力のあるコードを作成できます.今、work?
を再包装します.クラスは少し小さいもので実現できます.
より小さく割る
前の実施では、もう少し場所を分けてもらえますか?# app/models/seller/role.rb
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [
new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
]
end
end
# ... 생략
end
次の部分をRole
のサブクラスにしてwork?
を実現すればよい.new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
叶えてよ# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
現在、Seller::Role
では、次のような変化が発生します.class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
# ...
end
# ...
end
次いで、Seller
からブランチを除去することができる.class Seller < ApplicationRecord
def role
@role_object ||= Seller::Role.of(super)
end
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
同じAPIを提供すると同時に、内部実装のみを変更できます.
ActiveRecordでのシーケンス化
Validationをよりスムーズにするために、ActiveRecord#serialize
を使用します.数式図を参照して、次のインタフェースを提供します.class Rot13JSON
def self.rot13(string)
string.tr("a-zA-Z", "n-za-mN-ZA-M")
end
# returns serialized string that will be stored in the database
def self.dump(object)
ActiveSupport::JSON.encode(object).rot13
end
# reverses the above, turning the serialized string from the database
# back into its original value
def self.load(string)
ActiveSupport::JSON.decode(string.rot13)
end
end
私たちのSeller::Role
類は以下のように整理されています.class Seller::Role
attr_reader :code, :name, :description
class << self
# ...
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
# ...
end
現在のSeller
類は以下の通りです.class Seller < ApplicationRecord
serialize :role, Role
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Validationを追加するには、次のようにします.
最終結果
Seller
class Seller < ApplicationRecord
serialize :role, Role
validates :role, inclusion: { in: Seller::Role.all }
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Seller::Role
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
def of(code)
if code.is_a? String
code = code.to_sym
end
all.find { |role| role.code == code }
end
protected_methods :new
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
def initialize(code, name, description)
@code = code
@name = name
@description = description
end
end
Seller::Role::{Admin, Normal, Newbie}
# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
新しいroleを追加するのは簡単です.そしていくつかの凝集度の高い小さなクラスが得られた.元のクラスに比べてずっと小さくて、理解するのは負担がなくて、しかも私の変更の影響の地方はとても明確で、修正しやすいです.
また、ActiveRecord
ではなく、簡単なrubyobjectなので、テストはずっと簡単で、速度もずっと速いです.
参考資料
# == Schema Information
#
# Table name: sellers
#
# id :integer not null, primary key
# name :string
# role :string
# created_at :datetime not null
# updated_at :datetime not null
#
class Seller < ApplicationRecord
def role_name
if role == :admin
"관리자"
elsif role == :normal
"일반 판매자"
else
"뉴비"
end
end
def role_description
if role == :admin
"관리해요"
elsif role == :normal
"물건을 팔아요"
else
"새로 들어왔어요"
end
end
def work?
if role == :admin
true
elsif role == :normal
false
else
false
end
end
end
Role
というPORO(Plain Old Ruby Object)
を抽出します.以下の手順で作成します.# app/models/seller/role.rb
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [
new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
]
end
def of(code)
if code.is_a?(String)
code = code.to_sym
end
all.find { |role| role.code == code }
end
protected_methods :new
end
def initialize(code, name, description)
@code = code
@name = name
@description = description
end
end
さらに、Seller
では、以下の変更が発生します.class Seller < ApplicationRecord
def role
@role_object ||= Seller::Role.of(super)
end
delegate :description, :name, to: :role, prefix: true
# TODO
def work?
if role == :admin
true
elsif role == :normal
false
else
false
end
end
end
delegate
を使用すると、同じインタフェースを提供しながら、より凝集力のあるコードを作成できます.今、work?
を再包装します.クラスは少し小さいもので実現できます.より小さく割る
前の実施では、もう少し場所を分けてもらえますか?# app/models/seller/role.rb
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [
new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
]
end
end
# ... 생략
end
次の部分をRole
のサブクラスにしてwork?
を実現すればよい.new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
叶えてよ# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
現在、Seller::Role
では、次のような変化が発生します.class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
# ...
end
# ...
end
次いで、Seller
からブランチを除去することができる.class Seller < ApplicationRecord
def role
@role_object ||= Seller::Role.of(super)
end
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
同じAPIを提供すると同時に、内部実装のみを変更できます.
ActiveRecordでのシーケンス化
Validationをよりスムーズにするために、ActiveRecord#serialize
を使用します.数式図を参照して、次のインタフェースを提供します.class Rot13JSON
def self.rot13(string)
string.tr("a-zA-Z", "n-za-mN-ZA-M")
end
# returns serialized string that will be stored in the database
def self.dump(object)
ActiveSupport::JSON.encode(object).rot13
end
# reverses the above, turning the serialized string from the database
# back into its original value
def self.load(string)
ActiveSupport::JSON.decode(string.rot13)
end
end
私たちのSeller::Role
類は以下のように整理されています.class Seller::Role
attr_reader :code, :name, :description
class << self
# ...
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
# ...
end
現在のSeller
類は以下の通りです.class Seller < ApplicationRecord
serialize :role, Role
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Validationを追加するには、次のようにします.
最終結果
Seller
class Seller < ApplicationRecord
serialize :role, Role
validates :role, inclusion: { in: Seller::Role.all }
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Seller::Role
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
def of(code)
if code.is_a? String
code = code.to_sym
end
all.find { |role| role.code == code }
end
protected_methods :new
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
def initialize(code, name, description)
@code = code
@name = name
@description = description
end
end
Seller::Role::{Admin, Normal, Newbie}
# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
新しいroleを追加するのは簡単です.そしていくつかの凝集度の高い小さなクラスが得られた.元のクラスに比べてずっと小さくて、理解するのは負担がなくて、しかも私の変更の影響の地方はとても明確で、修正しやすいです.
また、ActiveRecord
ではなく、簡単なrubyobjectなので、テストはずっと簡単で、速度もずっと速いです.
参考資料
# app/models/seller/role.rb
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [
new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
]
end
end
# ... 생략
end
new(:admin, "관리자", "관리해요"),
new(:normal, "일반 판매자", "물건을 팔아요"),
new(:newbie, "뉴비", "새로 들어왔어요")
# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
# ...
end
# ...
end
class Seller < ApplicationRecord
def role
@role_object ||= Seller::Role.of(super)
end
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Validationをよりスムーズにするために、
ActiveRecord#serialize
を使用します.数式図を参照して、次のインタフェースを提供します.class Rot13JSON
def self.rot13(string)
string.tr("a-zA-Z", "n-za-mN-ZA-M")
end
# returns serialized string that will be stored in the database
def self.dump(object)
ActiveSupport::JSON.encode(object).rot13
end
# reverses the above, turning the serialized string from the database
# back into its original value
def self.load(string)
ActiveSupport::JSON.decode(string.rot13)
end
end
私たちのSeller::Role
類は以下のように整理されています.class Seller::Role
attr_reader :code, :name, :description
class << self
# ...
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
# ...
end
現在のSeller
類は以下の通りです.class Seller < ApplicationRecord
serialize :role, Role
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Validationを追加するには、次のようにします.最終結果
Seller
class Seller < ApplicationRecord
serialize :role, Role
validates :role, inclusion: { in: Seller::Role.all }
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
Seller::Role
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
def of(code)
if code.is_a? String
code = code.to_sym
end
all.find { |role| role.code == code }
end
protected_methods :new
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
def initialize(code, name, description)
@code = code
@name = name
@description = description
end
end
Seller::Role::{Admin, Normal, Newbie}
# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
新しいroleを追加するのは簡単です.そしていくつかの凝集度の高い小さなクラスが得られた.元のクラスに比べてずっと小さくて、理解するのは負担がなくて、しかも私の変更の影響の地方はとても明確で、修正しやすいです.
また、ActiveRecord
ではなく、簡単なrubyobjectなので、テストはずっと簡単で、速度もずっと速いです.
参考資料
class Seller < ApplicationRecord
serialize :role, Role
validates :role, inclusion: { in: Seller::Role.all }
delegate :description, :name, to: :role, prefix: true
delegate :work?, to: :role
end
class Seller::Role
attr_reader :code, :name, :description
class << self
def all
@all ||= [Seller::Role::Admin.new, Seller::Role::Normal.new, Seller::Role::Newbie.new]
end
def of(code)
if code.is_a? String
code = code.to_sym
end
all.find { |role| role.code == code }
end
protected_methods :new
def load(code)
Seller::Role.of(code)
end
def dump(role)
role.code.to_s
end
end
def initialize(code, name, description)
@code = code
@name = name
@description = description
end
end
# models/seller/role/admin.rb
class Seller::Role::Admin < Seller::Role
def initialize
super(:admin, "관리자", "관리해요")
end
def work?
true
end
end
# models/seller/role/newbie.rb
class Seller::Role::Newbie < Seller::Role
def initialize
super(:newbie, "뉴비", "처음이세요")
end
def work?
false
end
end
# models/seller/role/normal.rb
class Seller::Role::Normal < Seller::Role
def initialize
super(:normal, "일반 판매자", "물건을 팔아요")
end
def work?
true
end
end
Reference
この問題について(オブジェクト向けRails), 我々は、より多くの情報をここで見つけました https://velog.io/@heka1024/더-객체지향적인-Railsテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol