deau
deau

Reputation: 1223

self = Descendant in Ruby?

I have a text log from a game with (for example) two types of entries viz. Chat and Event. For the most part they are very similar so I have a LogEntry class defined as so;

class LogEntry < Array
  def initialize(str)
    super str.split
  end

  def parse
    LogEntry.parse self
  end

  def LogEntry.parse(entry)
    # Processes the elements that are in any Entry
    # Figure out whether it's a Chat entry or an Event entry
    # Returns an object of type LogChat or LogEvent
  end
end

LogChat and LogEvent both extend LogEntry and do further processing relevant to their domain. Everything works as expected;

chat = LogEntry.new("some chat")
event = LogEntry.new("some event")

chat.parse.class   # => LogChat
event.parse.class  # => LogEvent

Question: The class method LogEntry.parse essentially returns a parsed entry of the appropriate class. In this context, the parsed entry is the important bit. But we could rename the instance method 'parse' to 'what_type_should_i_be?'. I want the object to act on that information and 'self.become LogEntry.parse(self)'

Right now, to parse an entry, i have to do this;

  entry = entry.parse

I want to push this further so that i get the same result with;

  entry.parse

I've tried the obvious;

class LogEntry
  def parse
    self = LogEntry.parse(self)
  end
end

Yet I get the error Can't change the value of self. Does anyone know how I should go about achieving this?

Edit: I have changed my examples because many answers were focusing on the iteration over many entries. Chuck's answer elegantly shows that this situation isn't a problem.

In case this arouses anyone's interest, i've stumbled across Evil Ruby which let's you meddle with `self.class'. There's a nice Orielly article about it called Ruby code that will swallow your soul! I'm looking into it to see if it offers any answers. (Edit: evil.rb is well named! Something that low level doesn't 'seem' suitable for stable/long term distribution.)

Upvotes: 1

Views: 938

Answers (4)

pilcrow
pilcrow

Reputation: 58651

If you can break out the functionality into different modules, you can mutateextend() self as you like:

class LogEntry
  ...
  def parse!       # This mutates self!
    case LogEntry.parse!
    when :chat
      self.extend MyApp::LogChat
    when :event
      self.extend MyApp::LogEvent
    else
      raise MyApp::Exception, "waaah"
    end
  end
end

You don't have to do a clunky case statement with repeated calls to self.extend(), of course, but you get the idea.

Upvotes: 2

Chuck
Chuck

Reputation: 237080

I think the fundamental problem is that each is the wrong method here. Either have parse change the object's internal state rather than the object itself, or use map! to replace the objects in the collection with the new versions.

entries.map! {|entry| entry.parse}

will update the objects in the array with the result of calling parse on them, so there's no reason to do weird stuff to self in the parse method.

Upvotes: 2

glenn mcdonald
glenn mcdonald

Reputation: 15488

You've got some string/array/LogEntry confusion here, but assuming you get that worked out, and at the end you still want to have an Array subclass replacing its own contents, you need to use replace:

self.replace(LogEntry.parse(self))

Upvotes: 0

cgr
cgr

Reputation: 1121

for starters, your comments say that LogEntry.parse returns an LogChat or LogEvent object. So you are asking the object to change itself to a different type of object.

It also looks like class methods and instance methods are being confused a little I am guessing a little but why couldn't you do :

entries.each do |entry|
  some_type_log = entry.parse
  some_type_of_log.save!
end

EDIT: sorry, wanted to clarify something. Since you are parsing data that is part of LogEntry, and you want an entry to parse itself, there is no need to pass in any parameters. just keep the parse method parameter-less.

If you know what type of log something is, you can skip a step and parse it on the way in. chat_entry = LogChat.new(:log => LogEntry)

then make a method called log which is your parser that explicityly handles chat related items.

Upvotes: 0

Related Questions