encapsulation problem
andrzejsliwa opened this issue · comments
Hi
I see small problem with encapsulation, original idea was based on passing only locals,
but you are converting them on the fly to instance variables so there is couple issues with it:
- I have global scope, which is breaking encapsulation (I can call @var from child component), which is breaking the original concept of component isolation
- I can overwrite instance variables passed from controller, by passing them with same name, on any level to any component
If i got it correctly you did it to have ability of defining helpers methods with same name in component module.
I would go back to using locals, and wrap them not with modules but with instances of class (similar to decorators/presenters). You can keep properties helper on class level, which will lets you generate default constructor with kwargs (with base object + properties, then you can make properties to be instance variables in such auto-constructor).
What do you think about it?
can you also explain the use case for "render_#{component_name}" ?
custom_method = :"render_#{component_name}"
if context.respond_to?(custom_method)
context.public_send(custom_method, locals, &capture_block)
else
context.render("components/#{component}/#{parts.last}", &capture_block)
end
Hi,
I have global scope, which is breaking encapsulation (I can call
@var
from child component), which is breaking the original concept of component isolation
Actually you can't use the @var
that was defined in a parent component because the view_context
is always taken directly from the controller so component instance variables are not inherited in child components.
I can overwrite instance variables passed from controller, by passing them with same name, on any level to any component
Since the context is local, you can overwrite instance variables passed from the controller but it won't affect other views or components.
But indeed the fact the controller instance variables are accessible is a problem regarding isolation. We tried to remove them but we cannot remove all the instance variables because Rails defines some. So we have a few possibilities:
- hard code the instance variables to keep, but we must maintain the list when Rails changes
- add a
before_action
or equivalent that records which instance variables were defined before the controller action was called - keep controller instance variables as is, like we did
- switch to local variables
- maybe others?
If i got it correctly you did it to have ability of defining helpers methods with same name in component module.
The main reason to use instance variables was to avoid checking local_assigns
or defined?
to determine whether a parameter was passed or not to the component (you can just do if @foo
instead of if local_assigns[:foo]
or if defined? foo
).
Another possibility would be to say that if you don't want to use local_assigns
or defined?
you can define a property, which will initialize it to nil if you don't specify a default value. It would be an incentive to list all the component parameters as properties, which is probably a good thing.
A secondary reason was to make it easier to distinguish between helper/local variables and component parameters. But it's minor and subjective.
I would go back to using locals
I have a slight preference for instance variables but I'm not against switching back to local variables.
and wrap them not with modules but with instances of class (similar to decorators/presenters).
I tried to use classes for component code but I could not find a way to add the instance methods to the view context so that they work like local helpers. It would require multiple inheritance which is not possible.
can you also explain the use case for "render_#{component_name}" ?
The use case is when you want to render a different partial depending on the parameters, or when you want to manipulate some data before rendering.
I'm not sure it's useful though, because this can probably be done just as well in the main partial or a helper.
Actually you can't use the @var that was defined in a parent component because the view_context is always taken directly from the controller so component instance variables are not inherited in child components.
yep, you are right. I was wrong, in my example this was instance variable from controller. Thx!
Your question seems resolved, I'm closing. Feel free to reopen.