needs and view parameter assignment

27 views
Skip to first unread message

Viktor Trón

unread,
Jul 18, 2011, 3:03:09 PM7/18/11
to erector
Alex, here is my 2cents about view parameter assignment in erector

1)

given a widget W that declares needs params without a default
given a controller that assigns instance variables corresponding to
those needs

Scenario A:
you render W directly from the controller without passing assignments
to constructor (via widget renderer or template handler)

then
* a is correctly set in the W widget instance and erector does not
complain for missing parameters
* even if @b is set in the controller, @b will be nil if W does not
declare it as needed (by default erector only cares about relevant
instance vars to be passed on to the widget scope).

so far so good, this is nice, but

Scenario B: you render W indirectly from another widget P (also
without passing assignments to constructor)
* erector now complains about the missing parameter in W
* if you declare var with a default, that default value is not
overwritten by the value set in the controller
* this is the case even if the proxy widget P declares var, so it is
not inherited

I think the behaviour in scenario 2 is counterintuitive and confusing.
For any declared need, a corresponding controller assignment should
* satisfy the need
* overwrite any default
* irrespective of how deeply embedded the widget.

is there any reason this should not be so?

2)
* even if @b is set in the controller, @b will be nil if W does not
declare it as needed.
This means that unset/undeclared params can go unnoticed (just like in
erb etc).
If needs does define attr_accessors (like it used to i think) and you
get into the habit of using that instead of instance vars in your
widget, such mistakes are easier to catch

3)
from experience most of the time that a view parameter has a default,
that default is not a constant but needs controller or template
instance scope, say current_user or controller.resource etc.
in order to be able to specify such defaults, needs assign values
should take a proc with arity one for the template

needs :user => proc { |t| t.current_user }

pretty much like validate :if params etc. in the controller

The way I implemented these three features is by subclassing
Erector::Widget and redefine the content method wrapping the 'real'
content method I called 'widget_content' that i define in
my own widgets.

best
vik

Alex Chaffee

unread,
Jul 23, 2011, 8:10:28 PM7/23/11
to vikto...@gmail.com, erector
Viktor -

One of Erector's core principles is encapsulation, and that objects work better when initialized explicitly. 

This is diametrically opposed to one of Rails' core principles, which is "Woo-hoo! Let's pass data around like a hot potato!" :-)

So it's tough for me to consider what the Rails Way would be in cases like multiply-nested partials called from leaky controllers, since the Rails Way usually makes me a little seasick.

That said, I think some of what you're requesting is covered in rails3.rb, especially the ignore_extra_controller_assigns and controller_assigns_propagate_to_partials flags. Check https://github.com/pivotal/erector/blob/master/lib/erector/rails3.rb and all the nice documentation that J-Fire wrote that rdoc seems to have missed.

> needs :user => proc { |t| t.current_user }

If you do this, you could also skip "needs" altogether and assign @user in the constructor. Every RailsWidget has a pointer to its controller (called "helpers") that's set during to_html, so...

class LoginBox < Erector::Widget
  def user
    helpers.current_user
  end

  def content
    div :class => "login_box" do
      if user.nil?
        login_form
      else
        text "Welcome, #{user.name}!"
      end
    end
  end
  ...

> The way I implemented these three features is by subclassing
> Erector::Widget and redefine the content method wrapping the 'real'
> content method I called 'widget_content' that i define in
> my own widgets.

This is a good pattern. It would be even nicer if Erector (or Ruby itself) had an "around" idiom, so your subclass could still call its method "content" even though some code got called before and/or after it.

Viktor Trón

unread,
Jul 25, 2011, 11:21:15 AM7/25/11
to erector
Hi Alex, thanks for your reply

On Jul 24, 1:10 am, Alex Chaffee <a...@stinky.com> wrote:
> Viktor -
>
> One of Erector's core principles is encapsulation, and that objects work
> better when initialized explicitly.
>
> This is diametrically opposed to one of Rails' core principles, which is
> "Woo-hoo! Let's pass data around like a hot potato!" :-)

totally agree

> So it's tough for me to consider what the Rails Way would be in cases like
> multiply-nested partials called from leaky controllers, since the Rails Way
> usually makes me a little seasick.

sorry i made your head spin, now i see a bit more clearly and see
where to locate the confusion.
The behaviour is as expected if embedding is done with
render :template/action/partial within a template/widget
however incorrect or unexpected if done with the widget method.

The widget instance method is defined in abstract_widget and non-rails
specific, bypasses the controller assignments
But i needed to use this method at some point cos rendering via
template/partial
1) only handles widgets defined under view
2) can only finds widgets as paths not as class names

Now that I understand this better, i would like to make the following
suggestions:

1) the doc should mention that widget is no good substitute for render
and that it bypasses assignments from controller
2) there should be a feature that allows widget rendering by class
from a view and does not bypass assignments from controller

for 2 I suggest adding lib/erector/rails/
action_view_widget_renderer.rb that redifines
ActionView::Base#render

#lib/erector/rails/action_view_widget_renderer.rb
ActionView::Base.class_eval do
def render_with_erector_widget(options = {}, locals = {}, &block)
if widget = options.delete(:widget)
options[:text] = Erector::Rails.render(widget, self, locals,
options)
end
render_without_erector_widget(options, locals, &block)
end
alias_method_chain :render, :erector_widget
end

> That said, I think some of what you're requesting is covered in rails3.rb,
> especially the ignore_extra_controller_assigns and
> controller_assigns_propagate_to_partials flags. Checkhttps://github.com/pivotal/erector/blob/master/lib/erector/rails3.rband all
> the nice documentation that J-Fire wrote that rdoc seems to have missed.

ignore_extra_controller_assigns = false just replicates rails 'leaky'
ways by making ALL assigments available to the widget
controller_assigns_propagate_to_partial=true is only relevant for
rendering via render :partial and makes it behave like
render :template with regard to variable assignments
so these options are not what i needed

> > needs :user => proc { |t| t.current_user }
>
> If you do this, you could also skip "needs" altogether and assign @user in
> the constructor. Every RailsWidget has a pointer to its controller (called
> "helpers") that's set during to_html, so...

currently the way assignments work: initiliser via new > controller
instance var > needs default
now if I want to fall back to assignment with a controller instance
method unless it is assigned locally or in controller, I need to do
this

class Views::Students::StudentSideBar < Erector::Widget
needs :student => :special_value

def content
self.student = controller.current_user if student
== :special_value
...
end
end

instead of the more elegant
class Views::Students::StudentSideBar < Erector::Widget
needs :student => proc { |t| t.current_user }

def content
...
end
end



hope I am being constructive :)
Vik

Alex Chaffee

unread,
Jul 25, 2011, 9:40:40 PM7/25/11
to Viktor Trón, erector
On Mon, Jul 25, 2011 at 8:21 AM, Viktor Trón <vikto...@gmail.com> wrote:
> however incorrect or unexpected if done with the widget method.
>
> The widget instance method is defined in abstract_widget and non-rails
> specific, bypasses the controller assignments

Sounds to me like we could call this a bug.

> 2) there should be a feature that allows widget rendering by class
> from a view and does not bypass assignments from controller

I think
render :widget => TestWidget
is what you're after.

It works from inside a controller, and should work inside a template
view (e.g. erb) too. Though maybe it doesn't work either :-)

see https://github.com/pivotal/erector/blob/master/spec/rails_root/spec/render_spec.rb

> now if I want to fall back to assignment with a controller instance
method unless it is assigned locally or in controller, I need to do
this

How about...

class Views::Students::StudentSideBar < Erector::Widget

needs :student => nil

def student
@student || helpers.current_user
end

def content
text student.name
end
end

Yes, you'd have to wait until the controller is set before calling the
controller (helpers).

I like the idea of adding procs to the "needs" system, but I'm not
sure why you'd pass in "helpers" (the controller) to it... maybe you'd
pass in the widget itself...

Viktor Trón

unread,
Jul 26, 2011, 7:39:25 AM7/26/11
to ere...@googlegroups.com, Viktor Trón


On Tuesday, July 26, 2011 2:40:40 AM UTC+1, Alex Chaffee wrote:
On Mon, Jul 25, 2011 at 8:21 AM, Viktor Trón <vikto...@gmail.com> wrote:
> however incorrect or unexpected if done with the widget method.
>
> The widget instance method is defined in abstract_widget and non-rails
> specific, bypasses the controller assignments

Sounds to me like we could call this a bug.

> 2) there should be a feature that allows widget rendering by class
> from a view and does not bypass assignments from controller

I think
render :widget => TestWidget
is what you're after.

It works from inside a controller, and should work inside a template
view (e.g. erb) too. Though maybe it doesn't work either :-)

see https://github.com/pivotal/erector/blob/master/spec/rails_root/spec/render_spec.rb


sure it does not work, the render you call in the controller is defined in http://apidock.com/rails/AbstractController/Rendering/ 
to allow widgets

the one you call from view is in http://apidock.com/rails/ActionView/Rendering/
which is what i redefined

> now if I want to fall back to assignment with a controller instance
method unless it is assigned locally or in controller, I need to do
this

How about...

class Views::Students::StudentSideBar < Erector::Widget
 needs :student => nil

 def student
    @student || helpers.current_user
 end

  def content
    text student.name
  end
end

Yes, you'd have to wait until the controller is set before calling the
controller (helpers).

I like the idea of adding procs to the "needs" system, but I'm not
sure why you'd pass in "helpers" (the controller) to it... maybe you'd
pass in the widget itself...

sure, if you prefer. in fact the block could just be instance evaled in widget scope
by the initializer.

i shall send a pull request
v

Alex Chaffee

unread,
Jul 26, 2011, 11:59:52 AM7/26/11
to vikto...@gmail.com, ere...@googlegroups.com
On Tue, Jul 26, 2011 at 4:39 AM, Viktor Trón <vikto...@gmail.com> wrote:
sure, if you prefer. in fact the block could just be instance evaled in widget scope
by the initializer.

instance_eval?!??

"Those who do not know Markaby are doomed to repeat it."
 - me
Reply all
Reply to author
Forward
0 new messages