开发者

Creating new Ruby string helper without argument params

开发者 https://www.devze.com 2023-01-17 11:18 出处:网络
I really had a hard time figu开发者_StackOverflowring out how to word this question, but in essence, I want to do this:

I really had a hard time figu开发者_StackOverflowring out how to word this question, but in essence, I want to do this:

model = MyModel.new
model.title = "foo bar"
model.title.to_id #=> "foo_bar"

I have an ActiveRecord class for MyModel

class MyModel < ActiveRecord::Base
  def to_id(str)
    str.downcase.gsub(" ", "_")
  end
end

but, of course, it's looking for the to_id method on String, and I don't want to override string, because I don't require this behaviour on every string. Just strings associated with MyModel. I could keep it simple and do something like:

model.to_id(model.title)

But that's not very Ruby.

I know I've seen examples of this sort of method implemented before, I just can't track them down.

Halp anyone?


you can extend a specific object instance with a method, using modules.

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

since the value passed into the .title= method is a string, when we extend the string string with the ToId module, "self" in the module's methods is a string. therefore, we have direct access to the string that was passed into the .title= method, and we can manipulate it directly.

this is all done without having to modify the String class directly. we are only extending the specific instance that represents the title.


I believe that true Ruby solution is based on meta-programming. I'd strongly recommend you this book http://pragprog.com/titles/ppmetr/metaprogramming-ruby ($20) if you are interested.

By the way - the solution proposed above probably will not work as overriding column accessors is not that simple.

So I would recommend to create a class method that you use in your model definition like this:

class MyModel < ActiveRecord::Base
  # adds to_id to the following attributes
  ideize :name, :title
end

Well, that was an easy part, now comes the tougher one - the module itself:

# 
# 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)

I have to admit that I did not write the solution above just from my memory so I've prepared some tests that I'm sharing here:

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 rules!


You should take a look at the functions within ActiveSupport::CoreExtensions::String::Inflections, specifically in your case I would use the underscore method that exist in the (expanded) String class.

Then to override the accessor you could do something like:

def title_id
  read_attribute(:title).underscore
end

I think that's what you want.

0

精彩评论

暂无评论...
验证码 换一张
取 消