Accessing view_context in Model - ruby-on-rails

Within a model I have the following method:
def some_method
some_obj.new(view_c: view_context).create_some_links
end
An exception is thrown with the following message:
undefined local variable or method `view_context'
I am fully aware that it is not good practice to call view-related methods from your model, but nonetheless: Is it possible to access view_context from the model so that I can pass it along to a Plain Old Ruby Object (PORO) which creates some links?
Update: From the code I have above, one suggestion might be to simply create and call the PORO directly in the view. However: pretend that the code requires it to go through the model in order to create the right PORO.
view_context doc

Unless you pass the view_context to the method it is impossible:
model layer has nothing to do with view layer.
model itself can not possibly know anything about view context.
model has no access to view context.
Here's how you'd pass a view to the method (while being in the view):
#model_instance.some_method(self) # self is the view itself
Now, slight method change does the trick:
def some_method(view_context)
some_obj.new(view_c: view_context).create_some_links
end

Related

Rails instance variables not shared between helpers in ApplicationController and app/helpers

I have an instance variable that I set in a controller.
#cost=[1,2,3]
Eventually a view gets called that calls a partial that in turn calls a helper in app/helpers.
In the debugger I can see that #cost is still [1,2,3] when I enter that helper method.
Inside the helper method, I set #cost to [4,5,6] and then call a helper method set_cost that is defined in application_controller.rb.
class ApplicationController < ActionController::Base
helper_method :set_cost
def set_cost
#cost=[7,8,9]
end
end
When I enter set_cost, I can see in the debugger that #cost has reverted to [1,2,3].
When I return back to the helper method in app/helpers, I can see in the debugger that #cost is no longer [7,8,9], but has reverted to [4,5,6].
If I call set_cost directly from the controller, #cost is [7,8,9] when control returns back to the controller.
Is there a way that I can access and manipulate the instance variable across the controller, view, partial, helper method in app/helpers, and helper method in application_controller.rb such that the reference / values stay consistent? It's as though it's changing the scope and making local versions of the variable.
I realize there may be design and practical benefits to passing #cost to set_cost (my actual function is more complex than what I showed), updating it, and then returning it to the original function for assignment there, but even if that accomplishes what I want, I'd like to understand the Rails design better. I thought an instance variable was akin to a global variable, but apparently not.
Thank you for your help.
An instance variable is not a global variable - it is tied to a particular instance (hence the name).
Rails allows you to access controller instance variables from a view. This works by copying the controller instance variables: the view_assigns methods in AbstractController::Rendering creates a hash of all the instance variables. Later on, the view object uses that hash to recreate the instance variables. When you say that it's as if there are local copies of the variable being made, that's pretty much exactly what is happening.
The values are shared, i.e. initially in both the view and the controller #cost.object_id will be the same. If your helper were to do something like #cost.replace(...) then you would see that change everywhere, however when you reassign #cost that assignment does not affect the controller (and vice-versa - the copy of controller instance variables to the view is a once-off operation).
"Normal" helpers end up being instance methods of the view context, however ones created by helper_method are controller instance methods - rails basically defines a view helper that looks like
def set_cost
controller.set_cost
end
You can probably dodge these issues by mutating #cost rather than reassigning it, but I find the idea that a view helper might mutate state like this surprising.

Does this method that changes an instance variable belong in my Rails controller or model?

I have a basic "best practice" question about controllers and instance variables.
Say you have an instance variable in anew or update action in a controller, is it ok to modify that instance variable via a private method in the controller? Or should the method exist in the model?
e.g. in this example below, I need to loop through the attributes of an instance variable, and add or remove something. For example, if I am using nested attributes 3 layers deep and have to remove certain attributes, change them and then add them back in. I know this may seem strange, but assume it is necessary.
def new
#some_thing = SomeThing.new(:some_params)
do_something_to_inst_var # method call
#some_thing.save
end
private
def do_something_to_inst_var
#some_thing.addresses.each do |address|
# modify it in some way
end
end
Or is this bad practice? Should this be a method in the model and should be called like:
#some_thing.do_something_to_inst_var
OR
should we explicitly pass the instance variable to the method like:
def new
#some_thing = SomeThing.new(:some_params)
do_something_to_inst_var(#some_thing) # method call
#some_thing.save
end
private
def do_something_to_inst_var(some_thing)
some_thing.addresses.each do |addresses|
# modify it in some way
end
end
I'm looking for some clarity here, with an example if possible. I'm still learning and trying to improve and I didn't find an answer by searching.
Rails applications should have "thin controllers" and "fat models" for a couple of reasons:
Each object should handle only its own responsibilities. A controller should just be about connecting the web, the the model and the view, which thanks to Rails doesn't take much code. If a controller method refers repeatedly to methods of the same model, it's incorrectly taking on model responsibilities; we say that it's not cohesive or that it has "Feature Envy". It is more likely that if the model changes the controller will have to change in parallel.
It's easier to test models than to test controllers.
Fix it by writing a method in the model that does the model-specific work and call it in the controller (your second option). (Eventually your model will get too fat and you'll have to break it up too, but that's another story.) For example:
class SomeThingsController
def new
#some_thing = SomeThing.new(:some_params)
#some_thing.do_something # method call
#some_thing.save
end
end
class SomeThing
def do_something
addresses.each do |address|
# modify it in some way
end
end
end
Regarding instance variables.
Define them only if necessary. Presumably the one in your example is needed for the view.
Assuming an instance variable is justified at all, there's no reason not to refer to it in private methods of the class that contains it. That's what they're for. So your first option (referring directly to the instance variable) is a bit better than your third option (passing it in). But, as discussed above, extracting a model method is better than both of the other two options.
In my opinion Modifying #instance_vars from private method is okay if your controller is just 100 lines long.
Imagine a scenario where there are 500 LOC in your controller and after a struggle of a couple of hours you found out that the #intance_var is being modified by some private method.
Helpful tips:
create small private methods with single responsibility
put ! at the end of method_name! indicating that it modifies something. Specially this is helpful when you see my_private_method!, ! makes you realize that its modifying something.
lets not put code in controller that do not belong here.
There is one more option:
In Controller:
def new
#some_thing = SomeThing.new(:some_params)
#some_thing_modified = #some_thing.modify_somehow(params)
#some_thing_modified.save
end
In SomeThing Model:
def modify_somehow(params)
result = self.clone
# ... modify result ...
return result
end
Because modify_somehow is now pure function (assuming you don't do anything in ... modify result ... part, that makes it impure), what you gain here is Referential transparency. Main benefit of referential transparency is that you can determine what function/method invocation will do, only by looking at its arguments, and get result of its work only via return value, and not via side effects. This makes your code more predictable, which in turn makes it easier to understand and debug.
There are of course disadvantages: Because you create new object this option can be less performant, it's also more verbose than its alternatives.
Functional programming concepts, like referential transparency, are not very popular in Rails community (probably because of how OO-centric Ruby is). But referential transparency is there if you want it, with its pros and cons.

DRY Form Processing on Different Controllers in Rails 4

I have two forms/views which share similar input parameters; but each view is supposed to be processed by a separate controller because additional logic unique to each view is required. Each controller has plenty of input parameters associated with it. What is the best practice?
I know this question may sound like the "Sharing variables between controllers" thing; But if i use the before_filter method; I have to declare every input parameter from the form again. If I define a new method in the parent application controller or create a helper method for input parameter extraction; other methods in the child controller cannot access the variables... Is there a more elegant solution?
I often find cases where I need some reusable business logic outside of a controller. In those cases I'll define a new object in my /lib folder so I can use it multiple places. For example,
# lib/my_business_logic/do_stuff.rb
module MyBusinessLogic
class DoStuff
def self.dostuff(params)
# do stuff
end
end
end
Then I can call:
result = MyBusinessLogic::DoStuff.dostuff(params)

What's the difference between sending :include to class and directly defining method in second class definition?

Recently I had to add a method to Redmine's core class. I was unable to use inheritance, so I've done something like this:
require_dependency 'time_entry_query'
class TimeEntryQuery < Query
def my_new_method(foo, bar)
end
end
and it works perfectly - my method is added to all new objects. However, I've seen someone declaring the new method in their own module instead and then sending :include to class, so it become a mixin. Here's an example:
module Patches
module SomeClassPatch
def my_new_method
end
end
and somewhere in app's initialization:
SomeClass.send(:include, Patches::SomeClassPatch) unless SomeClass.include? (Patches::SomeClassPatch)
What's difference between these two methods and which one should I use?
There are two differences:
When you use a mixin, there is a clear place where your "patch" methods can live. If I wonder "Hmm, where's this my_new_method" coming from, and I look at, say, TimeEntryQuery.ancestors or TimeEntryQuery.instance_method(:my_new_method).owner, that will return Patches::SomeClassPatch. So I know I have to look for a file named lib/patches/some_class_patch.rb somewhere to find where it is probably defined. (I could try source_location as well, but that is not always reliable.)
Mixing in a module into a class makes the module the superclass of the class it is being mixed into. So, if there already is a my_new_method defined in TimeEntryQuery, your first option will overwrite it, whereas in your second option, your method will become the super method of that method. IOW: with your second option, your new method won't be called unless the already existing method calls super.

Is it ok to pass large objects in method parameters?

I'm doing a Rails application where people can take quizzes. I have a model BrowserGame that's taking care of the controller logic (sessions, redirecting etc.). Currently, this is my #initialize method:
class BrowserGame
def initialize(controller)
#controller = controller
end
end
And in the controller I have a method
class GamesController < ApplicationController
# actions
private
def browser_game
BrowserGame.new(self)
end
end
As you can see, I'm passing the whole controller to BrowserGame#initialize (so I can manipulate with sessions and others). Is this a good idea? Are there any side effects, since the controller instance is a large object?
Yes, it is fine to pass large objects as method parameters. You're not placing the object on the stack, just a pointer to it. As far as side-effects -- anything you do to #controller from within BrowserGame is seen through any other reference to the controller, but that's probably what you already expect.
There is no problem with passing a large object.
As Darshan says, it is only a pointer.
It would be better to only pass serializable objects if you are forking the process/thread or otherwise trying to create a delayed job to run in the background.

Resources