views:

287

answers:

4

Hi there, 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.)

A: 

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.

cgr
Thanks for your time m8, but I'm not confusing instance/class methods; The class method `LogEntry.parse` causes a cascade along gradually more specific `EntryType.parse` methods. The result is a fully parsed and categorized (by class) entry object. The instance method parse calls the class method in LogEntry, triggering a new cascade, so that you might recompute already parsed entries. Your solution is fine and similar to what I have in place already (see question!)
deau
A: 

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))
glenn mcdonald
sadly replace will only copy the contents of the array. Subclasses of LogEntry have instance variables (and a class ofcourse!) that aren't covered by the replace.
deau
+2  A: 

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.

Chuck
+1 for starting an interesting train of thought. I'm going to see if I can implement this within the `parse` method. I'd like the object to look at itself and say "actually, i'm on of those types of objects."
deau
Do you think I am doing weird stuff to self? Theoretically speaking, is it dangerous to have an object that controls itself in this way?
deau
Well, it's technically impossible to set `self` without some really low-level hacking, which is what I meant by "doing weird stuff to self". An object can't change its own identity (even if you could set `self` in a method, it wouldn't change the object's identity outside of the method — variables are just references). An object can only control its internal state, which is why I recommended representing the thing that can change that way.
Chuck
So the issue is that an object's class isn't part of it's state. I need to take a closer look at `some_fixnum += 1` to understand. The way `Fixnum` can change into `Bignum` got me inspired. Maybe `x += y` is just sugar for `x = x + y`.
deau
The Fixnum object does not change, the variable is set to a new object. The x += y in Ruby is just syntactic sugar for x = x + y — you're setting the variable x to point to the result of adding the value of x to the value of y. The Fixnum object itself doesn't change (in fact, it can't — there's only one Fixnum object for any given number). To prove this, try this: `a = [1,2,3]; a.each {|i| i += 1}`. You're merely rebinding the variable `i`, not changing any object.
Chuck
+2  A: 

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.

pilcrow
Quite right. I could simulate the whole thing by using `self.@type` instead of `self.class`. Mixins could add the relevant functionality and instance variables. I'll need to think about it some more though.
deau