4

I've been trying to make sense of the class of self in the present method that Ryan Bates used in his Presenters RailsCast (#287 Presenters from Scratch (pro) - RailsCasts). In the video, Ryan says, 'Self is the template object which has all the helper methods we want to access', but I wanted to know the class for this object. After reading a range of blog posts, SO threads, and Ruby docs, I'm starting to think that self is a kind of Struct, but I don't know how to confirm this notion.

My questions are: 1) In the present method below, is self a Struct?, and 2) How does one verify that something is a Struct?

module ApplicationHelper
  def present(object, klass = nil)
    klass ||= "#{object.class}Presenter".constantize
    presenter = klass.new(object, self)
    yield presenter if block_given?
    presenter
  end
end

I'm asking this because I don't have much experience working with Struct classes and when I stick binding.pry in the middle of the method above and try to get the name of the class for self, I end up with more questions.

  • When I enter self.class, I get, #<Class:0x007fb64f696268> I wondered if getting Class here might indicate that I have a Struct, but I couldn't find any documentation that confirmed this
  • When I enter self.class.class, I get Class
  • When I enter self, I get an extensive object that starts with the lines of code of code listed below

    @ line 16 ApplicationHelper#present:
    
    14: def present(object, klass = nil)
    15:   klass ||= "#{object.class}Presenter".constantize
    16:   binding.pry
    17:   presenter = klass.new(object, self)
    18:   yield presenter if block_given?
    19: end
    
    [1] pry(#<#<Class:0x007fb64f696268>>)> self
    => #<#<Class:0x007fb64f696268>:0x007fb64f6948f0
    @_assigns={"marked_for_same_origin_verification"=>true},
    @_config={},
    @_controller=
      #<PostsController:0x007fb64f6762d8
       @_action_has_layout=true,
       @_action_name="show",
       @_config={},
       @_db_runtime=0,
       @_lookup_context=
        #<ActionView::LookupContext:0x007fb64f6760d0
          @cache=true,
          @details=
            {:locale=>[:en],
            :formats=>[:html],
            :variants=>[],
            :handlers=>[:raw, :erb, :html, :builder, :ruby]},
          @details_key=#<Concurrent::Map:0x007fb64f697938 entries=0 default_proc=nil>,
          @prefixes=["posts", "application"],
          @rendered_format=:html,
          @view_paths=
            #<ActionView::PathSet:0x007fb64f675fe0
    

This post was helpful in explaining how a Struct works, but doesn't explain how one can confirm that they have a Struct.

Initially, when I started dissecting the present method, I found this answer to be helpful. However, I was thrown off by the comment, saying that the "ModelPresenter is initialized by passing the model, and the ApplicationHelper class", since ApplicationHelper is a module.

Ginnie Hench
  • 277
  • 2
  • 11
  • Keep in mind that you're touching on Rails internals here and the Rails internals are not exactly simple, straightforward, or well documented. It is very confusing and takes a long time to cut through all the magic and get your head around it. – mu is too short May 28 '18 at 16:10
  • 1
    Indeed! I don't want to get lost in the weeds, but I also want to spend some some time challenging my own wrong assumptions, so that I can cut through the magic eventually. – Ginnie Hench May 28 '18 at 16:55

1 Answers1

2

Summary

Use is_a?(Struct)

Explanation

A struct is a constructor for an anonymous class:

struct_class = Struct.new(:foo)
# => #<Class:0x007fa7e006ea98>

You can check that an instance of the anonymous class is a struct like so:

inst = struct_class.new
inst.class.superclass
# => Struct

However Object#is_a? checks the parent class as well as superclasses:

inst.is_a?(Struct)
# => true

You can see the same behavior in the following arbitrary example:

# inherits from String
anon_class = Class.new(String) 

inst = anon_class.new
# => ""

inst.class == String
# => false

inst.is_a?(String)
# => true
Joshua Pinter
  • 45,245
  • 23
  • 243
  • 245
max pleaner
  • 26,189
  • 9
  • 66
  • 118
  • 1
    The thing is, when I submit `self.is_a?(Struct)`, I get `false`. – Ginnie Hench May 28 '18 at 01:13
  • But, `self.class.superclass` returns `ActionView::Base`... Finally, that gets me to closer to what I was looking for. Thank you, @max-pleaner – Ginnie Hench May 28 '18 at 01:16
  • 1
    @GinnieHench That suggests that `Class.new(ActionView::Base) do ... end` was used to dynamically create a subclass of `ActionView::Base` with some extra methods and what not specific to the view in question. – mu is too short May 28 '18 at 01:39
  • 1
    Hmm, I haven't called any methods like that within my app, but I am calling the `present` method within erb in a view. By running `self.class.ancestors` in pry, I see a bunch of ActionView::Helpers listed, along with other Rails modules, so I can see how `self` "has all the helper methods we want to access" that Bates mentions, but I'm still not 100% sure what to call self.... Given that `self.is_a?(Object)` returns true and `self.class.is_a?(Class)` returns true, I have to settle on accepting that `self` is an object from the `Class` class. – Ginnie Hench May 28 '18 at 01:53
  • 1
    Rails will often create anonymous classes when it needs an isolated container for a bunch of stuff (such as a view). You're not doing it, Rails is. And then Rails will mash a bunch of modules (such as view helpers) into the anonymous class and copy instance variables from the controller to the view instance. – mu is too short May 28 '18 at 05:07
  • 1
    Little tidbit: Using `case object` and `when Struct` also works. – Joshua Pinter May 10 '23 at 22:57