Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails: capture method

The following code is from a Ryan Bates' RailsCasts in which he turns the front page of a blog into a calendar, so that articles show up as links on days. The following helper module creates the Calendar. I have two questions about this code

  1. In the day_cell method, he uses a method called capture. I found some docs on it but I still can't figure out how capture is working in this context. Also, what is the &callback that's passed as an argument to capture? Would it be the same :callback that's passed to Struct.new? If so, how does it get into capture? What is the :callback that's passed to Struct?

    def day_cell(day)
      content_tag :td, view.capture(day, &callback), class: day_classes(day)
    end
    

source code

module CalendarHelper
  def calendar(date = Date.today, &block)
    binding.pry
    Calendar.new(self, date, block).table

  end

  class Calendar < Struct.new(:view, :date, :callback)
    HEADER = %w[Sunday Monday Tuesday Wednesday Thursday Friday Saturday]
    START_DAY = :sunday

    delegate :content_tag, to: :view

    def table
      content_tag :table, class: "calendar" do
        header + week_rows
      end
    end

    def header
      content_tag :tr do
        HEADER.map { |day| content_tag :th, day }.join.html_safe
      end
    end

    def week_rows
      weeks.map do |week|
        content_tag :tr do
          week.map { |day| day_cell(day) }.join.html_safe
        end
      end.join.html_safe
    end

    def day_cell(day)
      content_tag :td, view.capture(day, &callback), class: day_classes(day)
    end

    def day_classes(day)
      classes = []
      classes << "today" if day == Date.today
      classes << "notmonth" if day.month != date.month
      classes.empty? ? nil : classes.join(" ")
    end

    def weeks
      first = date.beginning_of_month.beginning_of_week(START_DAY)
      last = date.end_of_month.end_of_week(START_DAY)
      (first..last).to_a.in_groups_of(7)
    end
  end
end
like image 484
BrainLikeADullPencil Avatar asked Oct 11 '12 21:10

BrainLikeADullPencil


1 Answers

I have done my research, and I've finally unraveled the mystery.

So, a couple of things to start with; as usual the documentation isn't very clear, the capture(*args) method is supposed to grab a piece of template into a variable, but it doesn't dig deeper into explaining that you may pass variables to the grabbed piece of template, that of course comes in the form of a block

source code from Ryan Bate's Calendar Screen-cast:

<div id="articles">
  <h2 id="month">
    <%= link_to "<", date: @date.prev_month %>
    <%= @date.strftime("%B %Y") %>
    <%= link_to ">", date: @date.next_month %>
  </h2>
  <%= calendar @date do |date| %>
    <%= date.day %>
    <% if @articles_by_date[date] %>
      <ul>
        <% @articles_by_date[date].each do |article| %>
          <li><%= link_to article.name, article %></li>
        <% end %>
      </ul>
    <% end %>
  <% end %>
</div>

In the code above, the block would be exclusively this part:

do |date| %>
  <%= date.day %>
  <% if @articles_by_date[date] %>
    <ul>
      <% @articles_by_date[date].each do |article| %>
        <li><%= link_to article.name, article %></li>
      <% end %>
    </ul>
  <% end %>
<% end %>

So, When he makes this call:

content_tag :td, view.capture(day, &callback), class: day_classes(day)

particularly:

view.capture(day, &callback)

What's happening here is that he's passing the day argument to the Block above as the |date| parameter (in the block).

What needs to be understood here, is that in the context of the Problem (making a 30-day Calendar); each day of the Month is passed to the capture method, along with the piece of template (&callback), doing so.. in consequence renders the block above for each day of a Given Month. The final step, of course is.. Placing that rendered content (for each day) as the content for the content_tag :td

A final note; Ryan is calling the capture method on a view variable, it isn't stated in the documentation either, but he does mention during the ScreenCast that he needs this view as a "proxy" to access the view, and of course the view is the only one that has access to ViewHelper methods.

So, in summary, it's very beautiful code, but it's only beautiful once you understand what it does, So, I agree is very confusing at first sight.

Hope this helps, it's the best explanation I could come up with. :)

like image 134
jlstr Avatar answered Nov 16 '22 04:11

jlstr