Is it ok to pass large objects in method parameters? - ruby-on-rails

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.

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.

Rails - How do Controllers pass instance variables to Views...can it be stopped?

I understand and appreciate that by putting # in front of a variable name in a Controller that it becomes available in whatever View is loaded. This is wonderfully useful, but I would like to understand the magic. How does it happen, and can it be stopped?
I am trying to DRY my CRUDdy resource controllers using inheritance, placing most of the logic in ApplicationController. The superclass should refer to the abstract variables #resource (for a single resource), #resources (for a collection of resources), and #parent_resource (for the parent resource when #resource is nested), but ideally the view would get more concrete names, for example;#customer, #customers, and #sales_territory respectively. Can this be done without sending duplicates of all objects (once in the abstract name, and once in the concrete name) to the view?
As I write this, the possibilities that come to mind are;
protected instance variables...does Ruby have such a thing, and
if so does the Controller magic hand them to the View?
setting the generic named variables to nil before render/redirect
using a protected empty method defined in the subclass to instead of
abstract named instance variables
What is the right choice in how to implement this?
What I'm assuming is happening here, is that there's a bunch of controllers in your app that literally just do the same thing and so you are wanting to make use of inheritance to DRY it up.
That being said, I'm not entirely sure the ApplicationController is the right place to dump all of this functionality as in the future if you had new controllers, they would also inherit all of this functionality without necessarily needing it.
I would do something like this:
Assuming you have controllers like this:
lions_controller.rb
tigers_controller.rb
hippos_controller.rb
and they pretty much have similar functionality... I would create a "Base" controller and then setup inheritance on child controllers. I would then also make an action that sets the "logical" defaults of child controllers, something like this.
AnimalsController.rb
class AnimalsController < ApplicationController
class_attribute :resource_class, :parent_resource_class
protected
def self.set_resource_attributes(options={})
self.resource_class = options[:resource_class]
self.parent_resource_class = options[:parent_resource_class]
end
end
LionsController.rb
class LionsController < AnimalsController
#call methods in AnimalsController here, start with setting the resource name
set_resource_attributes :resource_class => Lion, :parent_resource_class => Animal
end
and so on and so forth... the other thing that may be useful is to make use of the methods "instance_variable_set" so that you can set instance variable names in the view that actually make sense... You can make use of the class variables you just set to do this... so for example, lets re-open the AnimalsController.rb class:
class AnimalsController < ApplicationController
def show
instance_variable_set("##{self.resource_class.name.underscore}".to_sym, self.resource_class.find(params[:id]))
#... all the regular show stuff
end
end
This way, when you go to the lions#show path, what you will get in your view is access to a variable named #lion which will be set and contain an instance of the Lion class found through ActiveRecord.
Of course this pseudo code I threw in here can be cleaned up and DRY'd a bit more, but hopefully you get where I'm going with it. Hopefully this helps.

How can I keep an instance variable DRY in a controller helper with Rails?

I am writing this widget that is to be included with a single page of the site, so I don't see a reason to declare what I am doing in the ApplicationController. However, this new widget does have a fair amount of complexity to it, so I want to keep it separate from the controller that normally handles this page since that controller already has quite the bit of complexity in it. I originally figured in my design I could just make a helper module for the controller and keep most of the widget logic there.
I am trying to write this helper module, but then I realized that there is a particular array I need to be able to initialize from a single location, but multiple methods need access to it and it needs to be insured that it is initialized before any other method makes calls to this array. Is there a way to do something similar to a before_filter in such a module where I can ensure this variable is initialized before use or is there a better overall way of approaching this that is the "Rails way"?
I think you can create a new class and instantiate it in the before_filter or your controller.
Not sure what you want to achieve but an example could be :
class MyArrayBuilder
def initialize(some, param)
#something
end
def get_array
return my_array
end
end
class SomethingsController < ApplicationController
before_filter :build_array
def build_array
#array = MyArrayBuilder.new(some, param).get_array
end
end
Then you pass the #array variable to your helper.
I'm not sure there is a "Rails way" of doing what you want : Rails as a lot of convention but when you start to care a lot about design and DRY, I believe it's better to think in term of good practice for Oriented Object Programming than Rails convention.
You may in need of a good presenter. Look for it in ruby toolbox.
The presenter is a middle ground between the controller and the view. It can access data, and handle complex logic, which is related to presentation, and not to data manipulation. It can do lot of things clean and simple. Check it out, maybe it is good for your problem.
Tutorial for draper.

Access session variable or controller variable from module

Using: Rails 3.0.3
I have this feature of my website (count calculate . com) which consists of plenty different calculations. The user can set his own variable for decimals (significant numbers) through a form. First I set this variable as a configatron variable which turned out to be a bad idea (a change that some user did was passed on to all users).
So, I decided to make it a session variable, which makes sense since a user should select his own decimal value.
Now, in the controller I call a module function that in turn calls 100 different module functions, depending on id.
My problem:
I want to access this session variable in that module which turns out to be impossible. Passing it already from the controller (through the model) is a bit of a nightmare since it needs to be passed for each and every function.
So:
How do I access session variables from a module in the lib-catalog?
How could I access a controller method from a module? If so I could call that method and fetch the session variable.
Do you mean a ruby module? As in a mixin? If you're including it in your controller, it already has access to everything the controller has access to. Just call session, request, params etc and all will just work.
If you really need to get the session from some arbitrary part of the system, you can obtain the request with:
ActionDispatch::Request.new(ENV)
Then you can get the session from the request. (Note that request is actually a singleton in disguise... it won't build a new request if one is already built).
Here is one way to do it, mix your calculation module into the controller
# application_controller contains a method that wraps your session value
class ApplicationController < ActionController::Base
protect_from_forgery
def user_value
return 5 # really a session value
end
end
The module that contains your calculation methods needs access to the session, since you are mixing it into the controller you can call the user_value method with self
module CalculationModule
def super_cool_calculation
return self.user_value * 420
end
# more calculation methods
end
note the self.user_value call, that method is not defined in the module but you will need to define it in the class you mixin to
class FooController < ApplicationController
include CalculationModule
def show
#calculation = super_cool_calculation # call calculation module method
end
# etc...
if any of your model objects also define a user_value method you could then mixin the calculation module to those if needed, they would probably not be getting the value from session
Something else to consider, just store the user defined value in the database instead of the session, you will have easy access to the value from the model or controller
it sounds like your Model is including the module code. You really really do not want to use or access the session directly from there. It breaks the purpose for MVC, Rails is doing its best to prevent you from trying something like that.
when you say " I call a module function that in turn calls 100 different module functions, depending on id." I would like to see that chunk of code.
Your original idea is the correct one...the value needs to be passed over to the model...it just sounds like the code needs to be DRY'd up a little so that it isn't such a headache.
It is hard to give recommendations without seeing the code. I was stuck in a similar situation and used dynamic methods (overriding method_missing) to get around the problem.

Resources