我真的很难弄清楚如何说出这个问题,但实质上,我想这样做:
model = MyModel.new
model.title = "foo bar"
model.title.to_id #=> "foo_bar"
我有一个MyModel的ActiveRecord类
class MyModel < ActiveRecord::Base
def to_id(str)
str.downcase.gsub(" ", "_")
end
end
但是,当然,它正在寻找String上的to_id方法,我不想覆盖字符串,因为我不需要在每个字符串上都有这种行为。只是与MyModel相关联的字符串。我可以保持简单并做类似的事情:
model.to_id(model.title)
但那不是Ruby。
我知道我已经看过以前实施的这种方法的例子,我只是无法追踪它们。
哄谁?
答案 0 :(得分:2)
您可以使用模块使用方法扩展特定对象实例。
module ToId
def to_id
self.downcase.gsub " ", "_"
end
end
class MyClass
def title=(value)
value.extend ToId
@title = value
end
def title
@title
end
end
m = MyClass.new
m.title = "foo bar"
puts m.title #=> foo bar
puts m.title.to_id #=> foo_bar
因为传递给.title =方法的值是一个字符串,当我们使用ToId模块扩展字符串字符串时,模块方法中的“self”是一个字符串。因此,我们可以直接访问传递给.title =方法的字符串,我们可以直接操作它。
这一切都是在不必直接修改String类的情况下完成的。我们只是扩展代表标题的特定实例。
答案 1 :(得分:2)
我相信真正的Ruby解决方案基于元编程。如果你有兴趣,我强烈推荐你这本书http://pragprog.com/titles/ppmetr/metaprogramming-ruby(20美元)。
顺便说一下 - 上面提出的解决方案可能不会起作用,因为重写列访问器并不那么简单。
所以我建议你创建一个你在模型定义中使用的类方法,如下所示:
class MyModel < ActiveRecord::Base
# adds to_id to the following attributes
ideize :name, :title
end
嗯,这是一个简单的部分,现在变得更加困难 - 模块本身:
#
# extends the ActiveRecord with the class method ideize that
# adds to_id method to selected attributes
#
module Ideizer
module ClassMethods
def ideize(*args)
# generates accessors
args.each do |name|
define_method("#{name}") do
# read the original value
value = read_attribute(name)
# if value does not contain to_id method then add it
unless value.respond_to?(:to_id)
# use eigen class for the value
class << value
def to_id
self.downcase.gsub " ", "_"
end
end
end
# return the original value
value
end
end
end
end
def self.included(base)
base.extend(ClassMethods)
end
end
# extend the active record to include ideize method
ActiveRecord::Base.send(:include, Ideizer)
我不得不承认我没有在我的记忆中编写上面的解决方案,所以我准备了一些我在这里分享的测试:
require 'spec_helper'
describe MyModel do
before :each do
@mod = MyModel.new(:name => "Foo Bar",
:title => "Bar Bar",
:untouched => "Dont touch me")
end
it "should have to_id on name" do
@mod.name.respond_to?(:to_id).should be_true
@mod.name.to_id.should eql "foo_bar"
end
it "should have to_id on title" do
@mod.title.respond_to?(:to_id).should be_true
@mod.title.to_id.should eql "bar_bar"
end
it "should NOT have to_id on untouched" do
@mod.untouched.respond_to?(:to_id).should be_false
end
it "should work with real model" do
@mod.save!
@mod.name.to_id.should eql "foo_bar"
# reload from the database
@mod.reload
@mod.name.to_id.should eql "foo_bar"
end
end
Ruby规则!
答案 2 :(得分:0)
您应该查看ActiveSupport::CoreExtensions::String::Inflections中的函数,特别是在您的情况下,我将使用(扩展的)String类中存在的下划线方法。
然后要覆盖访问者,您可以执行以下操作:
def title_id
read_attribute(:title).underscore
end
我认为这就是你想要的。