views:

48

answers:

2

I understand my question is a bit vague but I don't know how else to describe it. I've asked in numerous places and no one seems to understand why I want to do this. But please bear with me, and I'll explain why I want something like this.

I'm using Liquid Templates to allow users to make some dynamic pages on my site. And for those that don't know, Liquid uses a class of theirs called LiquidDrop to expose certain items to the user. Any method in the drop can be called by the Liquid template.

class PageDrop < Liquid::Drop
  def initialize(page)
    @page = page
  end

  def name
    @page.name
  end

  def children
    PagesDrop.new(@page.children)
  end
end
class PagesDrop < Liquid::Drop
  def initialize(pages)
    @pages = pages
  end

  def group_by
    GroupByDrop.new(@pages)
  end

  def all
    @pages.all
  end

  def size
    @pages.size
  end
end

For example, I want to be able to do this:

@page_drop = PageDrop.new(@page)
@page_drop.children # to get an array of children

instead of

@page_drop.children.all

Why do I have a pages drop?

Because I want to be able to cleanly split up the methods I can do to an array of pages, and methods I can do to a single page. This allows me to group pages like so:

@page_drop.children.group_by.some_method_here_that_the_group_drop_contains

To make it simpler for my users, I don't want them to have to think about adding "all" or not to a drop instance to get the "default" object/s that it contains. To reiterate:

@pages_drop = PagesDrop.new(Page.all)

@pages_drop == @pages_drop.pages #I want this to be true, as well as
@pages_drop == @pages_drop.all

Where did I get this idea?

In Rails, a scope (association object) (@person.friends) seems to return the array when you do certain things to it: @person.friends.each, for person in @person.friends

A: 

This isn't really possible. When you write @instance you aren't really calling an instance as you describe, you're getting a reference to the object that @instance refers to.

The reason it seems to work with the collections for Rails' associations is that the the association objects are instances of Array that have had some of their methods overridden.

I would consider removing PagesDrop and using the group_by(&:method) syntax if you want a concise way to express groupings. If you do want to keep it then you can get some way towards what you want by implementing each and [] on PagesDrop and having them delegate to @pages. That will let you use @page_drop.children in for loops, for instance.

mikej
Thanks mikej. Unforunately, the group_by stuff I'm applying to the array are a bit more complicated than that, and I'm not sure how I can put it in that syntax. I think the users will just have to trade a nice chainable syntax for putting .all at the end of sets of objects (like @pages_drop).
Ramon Tayag
A: 

It looks like you want to implement has_many outside of rails. Will the following work?

class PageDrop < Liquid::Drop
  attr_accessor :children
  def initialize(page)
    @page = page
    @children = []
  end

  def name
    @page.name
  end
end

This allows you to do the following:

@page_drop = PageDrop.new(@page)
@page_drop.children.size # => 0
@page_drop.children # => []

This also gives you all the standard array functions (group_by, size, each, etc). If you want to add your own methods, create a class that inherits from Array and add your methods there.

class PageArray < Array
   def my_method
     self.each{|a| puts a}
   end
end

class PageDrop < Liquid::Drop
  attr_accessor :children
  def initialize(page)
    @page = page
    @children = PageArray.new
  end
  [...]
end

@page_drop = PageDrop.new(@page)
@page_drop.children.size # => 0
@page_drop.children # => []
@page_drop.children.my_method # Prints all the children

Then any functions you don't define in PageArray fall through to the Ruby Array methods.

Jason Noble
Ah... I see what you're trying to do. I'll try it out and see if this will work with Liquid, then I'll get back to you. Thanks!
Ramon Tayag
Thanks! I tried it out, and this would have been perfect, if I didn't have named_scopes "exposed" to via the PagesDrop. PagesDrop has a published method that calls @pages.published. This requires @pages to be an association object. It looks like I'll need to require users to put an "all" at the end of every PagesDrop instance, like so: @page_drop.children.all, @page_drop.children.published.all
Ramon Tayag