Reputation: 1223
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
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
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
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
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