访问 ruby 个对象内对象的属性
Access attributes on objects inside ruby objects
我正在构建一个 LineItemGenerator
对象,其目的是在给定所需属性的情况下生成属性值数组。
问题是给定的对象有对象作为属性。所以给出的 "attributes" 实际上是 "nested attributes".
目标是从 item
访问请求的嵌套属性,在本例中是 item.name
和 item.style.name
通过创建一些输入数据结构并使用一些算法。
目前,我将 "nested attributes" 输入数据结构表示为数组数组,nested_attributes
我执行繁重工作的算法称为 #generate
。
它采用原始 item
和 nested_attributes
。接下来,它映射 nested_attributes
,通过在每次迭代中将消息 send
发送到原始 item
,将每个 nested_attribute
减少为 "attribute"。
class Style
attr_reader :name
def initialize name:
@name = name
end
end
class Item
attr_reader :name, :style
def initialize name:, style:
@name = name
@style = style
end
end
class LineItemGenerator
def generate item:, nested_attributes:
nested_attributes.map do |nested_attribute|
nested_attribute.reduce(item) do |obj, attribute| # <-- algorithm using #reduce to burrow in
obj.send(attribute)
end
end
end
end
require 'minitest/autorun'
class SomeTest < Minitest::Test
def test_it_returns_the_right_line_item
style = Style.new name: 'cool'
item = Item.new name: 'pants', style: style
# input data structure is array or arrays
nested_attributes = [[:name], [:style, :name]]
input = { item: item, nested_attributes: nested_attributes}
output = LineItemGenerator.new.generate input
assert_equal ['pants', 'cool'], output
end
end
我对实现更具声明性和表现力的输入数据结构和算法的新方法感到好奇。上面的评论中提到了两个感兴趣的部分。
使用 #inject
感觉很奇怪,因为我实际上只是想将可变数量的 send
调用链接在一起。例如:
item = Item.new name: 'pants', style: Style.new(name: 'cool')
p item.send(:style).send(:name) #=> "cool"
在这种情况下,是否有一些 Enumerable 方法是更好的选择?我的输入数据结构有更好的选择吗?
这对我来说更像是一个软件设计问题,所以这就是我从设计角度处理它的方式。
- 一次从一个组件的角度推理。
- 将我们试图传达的内容与其实施方式分开
The LineItemGenerator
's job is to generate an array of attribute values for an item given the desired attributes.
基于此,LineItemGenerator
:
- 获取具有属性的项目
- 实施
generate_attribute_values
给定所需属性列表
这可能看起来像:
LineItemGenerator.new(@item).generate_attribute_values(:name, :style)
我会删除 generate
,因为它在这里似乎不是正确的词。我们只是检索和过滤现有值,而不是创建新的属性值对象。
LineItemGenerator.new(@item).attribute_values(:name, :style)
在这一点上,我考虑 Item
应该向我们的 LineItemGenerator
公开什么。
- 项目有属性
- 属性有值,这意味着它们也应该有名称。
鉴于这种理解,我可以将 LineItemGenerator
实现为:
class LineItemGenerator
def initialize(item)
@item = item
end
def attribute_values(*attribute_names)
@item.attributes.select { |attribute| attribute_names.include?(attribute.name) }.map(&:value)
end
end
此时,有两个合约需要完成:
- 项目需要实施
#attributes
item.attributes
需要 return 一组响应 #name
和 #value
的对象
现在,让我们从一个项目的角度来思考。
- 一个项目有很多属性(例如名称和样式)。
- 相关的属性值可以在Item
对象上定义或委托给其他对象。
合同 1 很容易实现:
class Item
attr_reader :attributes
end
合同 2 更灵活一些,因为它可以在项目或单个属性 classes 上实现。如果某个属性不是应用程序中的第一个 class 关注点,我会在 Item
上实施它。
class Item
attr_reader :attributes
Attribute = Struct.new(:name, :value)
def initialize(name:, style:)
@attributes = [
Attribute.new(name: :name, value: name),
Attribute.new(name: :style, value: style)
]
end
end
如果系统的某些其他部分需要与属性交互作为第一个 class 问题:
# TODO: DRY up using inheritance or modules
class Style
attr_reader :value
def initialize value:
@value = value
end
def name
:style
end
end
class ItemName
attr_reader :value
def initialize value:
@value = value
end
def name
:name
end
end
class Item
attr_reader :name, :style, :attributes
def initialize item_name:, style:
@name = item_name
@style = style
@attributes = [@name, @style]
end
end
我正在构建一个 LineItemGenerator
对象,其目的是在给定所需属性的情况下生成属性值数组。
问题是给定的对象有对象作为属性。所以给出的 "attributes" 实际上是 "nested attributes".
目标是从 item
访问请求的嵌套属性,在本例中是 item.name
和 item.style.name
通过创建一些输入数据结构并使用一些算法。
目前,我将 "nested attributes" 输入数据结构表示为数组数组,nested_attributes
我执行繁重工作的算法称为 #generate
。
它采用原始 item
和 nested_attributes
。接下来,它映射 nested_attributes
,通过在每次迭代中将消息 send
发送到原始 item
,将每个 nested_attribute
减少为 "attribute"。
class Style
attr_reader :name
def initialize name:
@name = name
end
end
class Item
attr_reader :name, :style
def initialize name:, style:
@name = name
@style = style
end
end
class LineItemGenerator
def generate item:, nested_attributes:
nested_attributes.map do |nested_attribute|
nested_attribute.reduce(item) do |obj, attribute| # <-- algorithm using #reduce to burrow in
obj.send(attribute)
end
end
end
end
require 'minitest/autorun'
class SomeTest < Minitest::Test
def test_it_returns_the_right_line_item
style = Style.new name: 'cool'
item = Item.new name: 'pants', style: style
# input data structure is array or arrays
nested_attributes = [[:name], [:style, :name]]
input = { item: item, nested_attributes: nested_attributes}
output = LineItemGenerator.new.generate input
assert_equal ['pants', 'cool'], output
end
end
我对实现更具声明性和表现力的输入数据结构和算法的新方法感到好奇。上面的评论中提到了两个感兴趣的部分。
使用 #inject
感觉很奇怪,因为我实际上只是想将可变数量的 send
调用链接在一起。例如:
item = Item.new name: 'pants', style: Style.new(name: 'cool')
p item.send(:style).send(:name) #=> "cool"
在这种情况下,是否有一些 Enumerable 方法是更好的选择?我的输入数据结构有更好的选择吗?
这对我来说更像是一个软件设计问题,所以这就是我从设计角度处理它的方式。
- 一次从一个组件的角度推理。
- 将我们试图传达的内容与其实施方式分开
The
LineItemGenerator
's job is to generate an array of attribute values for an item given the desired attributes.
基于此,LineItemGenerator
:
- 获取具有属性的项目
- 实施
generate_attribute_values
给定所需属性列表
这可能看起来像:
LineItemGenerator.new(@item).generate_attribute_values(:name, :style)
我会删除 generate
,因为它在这里似乎不是正确的词。我们只是检索和过滤现有值,而不是创建新的属性值对象。
LineItemGenerator.new(@item).attribute_values(:name, :style)
在这一点上,我考虑 Item
应该向我们的 LineItemGenerator
公开什么。
- 项目有属性
- 属性有值,这意味着它们也应该有名称。
鉴于这种理解,我可以将 LineItemGenerator
实现为:
class LineItemGenerator
def initialize(item)
@item = item
end
def attribute_values(*attribute_names)
@item.attributes.select { |attribute| attribute_names.include?(attribute.name) }.map(&:value)
end
end
此时,有两个合约需要完成:
- 项目需要实施
#attributes
item.attributes
需要 return 一组响应#name
和#value
的对象
现在,让我们从一个项目的角度来思考。
- 一个项目有很多属性(例如名称和样式)。
- 相关的属性值可以在Item
对象上定义或委托给其他对象。
合同 1 很容易实现:
class Item
attr_reader :attributes
end
合同 2 更灵活一些,因为它可以在项目或单个属性 classes 上实现。如果某个属性不是应用程序中的第一个 class 关注点,我会在 Item
上实施它。
class Item
attr_reader :attributes
Attribute = Struct.new(:name, :value)
def initialize(name:, style:)
@attributes = [
Attribute.new(name: :name, value: name),
Attribute.new(name: :style, value: style)
]
end
end
如果系统的某些其他部分需要与属性交互作为第一个 class 问题:
# TODO: DRY up using inheritance or modules
class Style
attr_reader :value
def initialize value:
@value = value
end
def name
:style
end
end
class ItemName
attr_reader :value
def initialize value:
@value = value
end
def name
:name
end
end
class Item
attr_reader :name, :style, :attributes
def initialize item_name:, style:
@name = item_name
@style = style
@attributes = [@name, @style]
end
end