Pergunta

I'm trying to implement a funky version of method chaining. Returning the instance of the class after each function call is easy, you just do

def chainable_method
    some_code()
    self
end

My idea is that the methods you can call depend on the previous method call. I'm trying to achieve this by returning an object belonging to the containing object. The contained object will have a few special methods, and then implement method_missing to return the containing object's instance.

Edit: The child object has some state associated with it that should be in itself, and not the parent. It might not have been clear previously as to why I need a whole instance for just method calls.

super is irrelevant in this case because the contained object doesn't inherit from the containing object, and I wouldn't want to call the containing object's methods on the contained object anyway - I want to call the containing object's methods on the containing object itself. I want the containing object, not the containing object class.

Not sure if this is possible.

Edit: reworded everything to use "containing/contained object" instead of the completely incorrect parent/child object. Also, I'm using 1.9.3, if that matters. Version isn't important, I can change if needed.

My explanation was probably unclear. Here's the code:

class AliasableString
    def initialize(string)
        @string = string
    end

    def as(aka)
        @aka = aka
    end

    def has_aka?
        !@aka.nil?
    end

    # alias is a reserved word
    def aka
        @aka
    end

    def to_s
        @string + (self.has_aka? ? (" as " + @aka) : "")
    end
end

class Query
    def initialize
        @select_statements = Array.new
    end

    def select(statement)
        select_statement = AliasableString.new(statement)
        @select_statements.push(select_statement)
        select_statement
    end

    def print
        if @select_statements.size != 0
            puts "select"
            @select_statements.each_with_index {| select, i|
                puts select
            }
        end
    end
end

# Example usage

q0 = Query.new

q0.select("This is a select statement")
    .select("Here's another one")
        .as("But this one has an alias")
    .select("This should be passed on to the parent!")

q0.print

I haven't yet fully implemented print. AliasableString needs to have @string and @aka separate so I can pull them apart later.

Foi útil?

Solução

First of all, it doesn't matter what class of object is contained within a Query instance. All of the syntax shown on your 'example usage' section is appropriately defined in Query. The only requirement of the objects contained within a query instance is that they respond to as (or some similar method). What you have here is something like a state machine, but the only state that really matters is that some object occupies the last position in the select_statements array. Here's how I would build this (again, based mostly on your example at the end, I'm afraid I can't quite follow your initial explanation):

class Query
  # ... initialize, etc.

  def select(statement, statement_class = AliasableString)
    select_statements << statement_class.new(statement)
    self
  end

  def as(aka)
    # this will only ever be used on the most recent statement added
    statement_to_alias = select_statements.last

    # throw an error if select_statements is empty (i.e., :last returns nil)
    raise 'You must add a statement first' unless statement_to_alias

    # forward the message on to the statement
    statement_to_alias.as(aka)

    # return the query object again to permit further chaining
    self
  end
end

AliasableString doesn't need to know a thing about Query; all it needs to do is respond appropriately to as.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top