Rails Memoization of Helper method - ruby-on-rails

I have a helper method that does expensive calculations and returns a Hash, and this Hash is constant during my entire application lifespan (meaning it can only change after a re-deploy) and it also doesn't take any arguments.
For performance, I wish I could 'cache' the resulting Hash.
I don't want to use Rails cache for this, since I want to avoid the extra trip to memcached and I don't want the overhead of de-serializing the string into a hash.
My first idea was to assign the resulting hash to a Constant and calling .freeze on it. But the helper is an instance method, the constant lives on the class, and I had to do this ultra hacky solution:
module FooHelper
def expensive_calculation_method
resulting_hash
end
EXPENSIVE_CALCULATION_CONSTANT = Class.new.extend(self).expensive_calculation_method.freeze
This is due to the helper method being an instance method, the helper being a Module (which leads to the fake Class extend so I can call the instance method) and I also must declare the constant AFTER the instance method (if I declare it right after module FooHelper, I get an undefined method 'expensive_calculation_method'.
The second idea was to use memoization, but at least for Rails Controllers memoization is the persistance of a variable over the lifecycle of a single request, so it's only valuable if you reuse a variable many times from within a single request, which is not my case, but at the same time Helpers are modules, not Classes to be instanciated, and by this point I don't know what to do.
How would I cache that Hash, or memoize it in a way that persists over requests?

Per your comments, this will only change at application boot, so placing it in an initializer would do the trick.
# config/initializers/expensive_thing.rb
$EXENSIVE_THING_GLOBAL = expensive_calculation
# or
EXPENSIVE_THING_CONSTANT = expensive_calculation
# or
Rails.application.config.expensive_thing = expensive_calcualatioin

If you want to cache the result of some painful operation at launch time:
module MyExpensiveOperation
COMPUTED_RESULT = OtherModule.expensive_operation
def self.cached
COMPUTED_RESULT
end
end
Just make sure that module's loaded somehow or it won't initiate. You can always force-require that module if necessary in environment.rb or as a config/initializer type file.
If you want to lazy load the basic principle is the same:
module MyExpensiveOperation
def self.cached
return #cached if (defined?(#cached))
#cached = OtherModule.expensive_operation
end
end
That will handle operations that, for whatever reason, return nil or false. It will run once, and once only, unless you have multiple threads triggering it at the same time. If that's the case there's ways of making your module concurrency aware with automatic locks.

Related

Rails NoMethodError when calling a method from a constant

I'm trying to call a method inside a class constant. It returns a NoMethodError. Here's a sample code:
class TestingA
CONSTANT_HERE = { details: get_details('example.json') }
def get_details(file)
# retrieve details here
end
end
The error that appears is:
NoMethodError (undefined method `get_details' for TestingA:Class)
Any ideas on why?
Generally, dynamic constant assignment is discouraged in Ruby. Why would you want to define a constant that can possibly change within the life-cycle of an object? We don't know exactly what get_details is doing, but what is the use case of creating an instance method that is called from a constant as well as exposing the method? We can only assume return value is dynamic at this stage. Rubocop is also going arrest you for not freezing the constants, which is bad as linters are a good tool to abide by.
Constants can be changed and there is no way to avoid this as variables in Ruby are not containers: they point towards an object. However, it is your duty to make your code readable. If you see a constant that you cannot easily discern the value of, would you think that is readable?
We should talk about how Ruby loads constants and, more generally, files. Every Ruby application has its entry point. The interpreter will need the file to load and execute the commands of the application. The Ruby interpreter will increment over each statement inside your file and execute them following a specific set of rules, until it parses the entire file. What happens when the interpreter iterates to a constant? What other types of constants are there? Hint: CONSTANT_HERE is not the only constant you are defining.
The class keyword is processed first and the interpreter creates a constant 'TestingA' and stores in that constant a class object. The name of the class instance is "TestingA", a string, named after the constant. Yes, classes and modules are constants. Each class has a constant table, which is where "TestingA" will be stored. After this, the body of the class is interpreted and a new entry is created in the constant table for "CONSTANT_HERE" and the value is set and stored. This is happening before your definition of get_details has been defined: the interpreter wants to set the value, to store it, before "get_details" has been interpreted, which is why you are experiencing the error you are.
Now we know this, and wanting to provide an example of how constants are evaluated in code, you would need to mimic the below in order to have a method defined in a constant:
def get_details(file)
"stub_return"
end
class TestingA
CONSTANT_HERE = { details: get_details('example.json') }
end
In my opinion, the above is not good practise as it is an example mis-use of constant assignment. If you want to get details from a file, define a class/method and call the API/method instead. It's neater, assigns role of responsibility and is less ambiguous.

Why isn't the args parameter used in ActionController::Instrumentation::render?

I am new to Ruby and to Rails, and am trying to understand fully what I'm reading.
I am looking at some of the Rails source code, in this case action_controller/metal/instrumentation.rb.
def render(*args)
render_output = nil
self.view_runtime = cleanup_view_runtime do
Benchmark.ms { render_output = super }
end
render_output
end
I understand that *args is using the splat operator to collect the arguments together into an array. But after that, it stops making much sense to me.
I can't fathom why render_output is set to nil before being reassigned to equal super and then called with no arguments. I gather that some speedtest is being done, but coming from other languages I'd expect this to just be something more like Benchmark.ms(render_output) or perhaps Benchmark.start followed by render_output followed by Benchmark.end. I'm having a hard time following the way it works here.
But more importantly, I don't really follow why args isn't used again. Why bother defining a param that isn't used? And I mean, clearly it is getting used-- I just don't see how. There's some hidden mechanism here that I haven't learned about yet.
In this context, it is important to note how super works, because in some cases it passes implicitly arguments and you might not expect that.
When you have method like
def method(argument)
super
end
then super is calling the overridden implementation of method implicitly with the exact same arguments as the current method was called. That means in this example super will actually call super(argument).
Of course, you can still define a method call that explicitly sends other arguments to the original implementation, like in this example:
def method(argument)
super(argument + 1)
end
Another important edge-case is when you want to explicitly call super without any arguments although the current method was called with arguments then you need to be very explicit like this
def method(argument)
super() # note the empty parentheses
end
Let me try to describe you what I think this code does.
*args*
is using the splat operator to collect the arguments together into an array
that is totally correct, however they don't use it, and if you will go to master branch, they just changed it to *. Asking why it is defined and not used, I think that's question about bad design. They should have called it _args or at least like it is now just single splat *.
render_output is set to nil because of scopes, it has to be explicitly defined out block, lambda, proc in order to store value in it, otherwise its visibility will be locked only to those lambda, proc, block execution. Refer to this article
Benchmark.start. Blocks are great ruby construction. You are totally correct that speedtest is done, we can see it is just decorator for benchmark library.
source.
You are wondering why we cannot just pass it as Benchmark.ms(render_output), that's because what will be given to benchmark ms function? It will be given result, like <div> my html </div. And how we can measure this string result - no how. That's why we calling super right in this block, we want to access parent class function and wrap it inside block, so we are not calling it, we just construct it, and it will be called inside benchmark lib, and measured execution like
class Benchmark
...
def realtime # :yield:
r0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
yield
Process.clock_gettime(Process::CLOCK_MONOTONIC) - r0
end
...
end
So here we can count realtime of function execution, this is the code from original library

Best practice for a big array manipulation with values that never change and will be used in more than one view

What would be the best and more efficient way in Rails if I want to use a hash of about 300-500 integers (but it will never be modified) and use it in more than one view in the application?
Should I save the data in the database?, create the hash in each action that is used? (this is what I do now, but the code looks ugly and inefficient), or is there another option?
Why don't you put it in a constant? You said it will never change, so it fits either configuration or constant.
Using the cache has the downside that it can be dropped out of cache, triggering a reload, which seems quite useless in this case.
The overhead of having it always in memory is none, 500 integers are 4KB or something like that at most, you are safe.
You can write the hash manually or load a YAML file (or whatever) if you prefer, your choice.
My suggestion is create a file app/models/whatever.rb and:
module Whatever
MY_HASH = {
1 => 241
}.freeze
end
This will be preloaded by rails on startup (in production) and kept in memory all the time.
You can access those valus in view with Whatever::MY_HASH[1], or you can write a wrapper method like
module Whatever
MY_HASH = {
1 => 241
}.freeze
def self.get(id)
MY_HASH.fetch(id)
end
end
And use that Whatever.get(1)
If the data will never be changed, why not just calculate the values before hand and write them directly into the view?
Another option would be to put the values into a singleton and cache them there.
require 'singleton'
class MyHashValues
include Singleton
def initialize
#
#results = calculation
end
def result_key_1
#results[:result_key_1]
end
def calculation
Hash.new
end
end
MyHashValues.instance.result_key_1
Cache it, it'll do exactly what you want and it's a standard Rails component. If you're not caching yet, check out the Rails docs on caching. If you use the memory store, your data will essentially be in RAM.
You will then be able to do this sort of thing
# The block contains the value to cache, if there's a miss
# Setting the value is done initially and after the cache
# expires or is cleared.
# put this in application controller and make it a helper method
def integer_hash
cache.fetch('integer_hash') { ... }
end
helper_method :integer_hash

changing the value of a global variable in rails

I'm prototyping an app and want to have a global variable that I can hit an endpoint that toggles the global variable $current_status. I have:
def toggle_status
$current_status=false if $current_status.nil?
$current_status=!$current_status
r={}
r[:current_status]=$current_status
render json:r.to_json
end
and in application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
$current_status
end
but hitting /toggle_status always returns false. Why isn't assigning a bool to what it isn't changing this value? I'm aware something like this should be in db but just prototyping
edit 1
I just created this in lib/
class Jt
#cur
def self.cur
#cur
end
def self.cur=val
#cur=val
end
end
and updated the controller to:
def toggle_status
Jt.cur=!Jt.cur
r={}
r[:current_status]=Jt.cur
render json:r.to_json
end
Your toggle code doesn't actually toggle anything. It appears you expect this line to "toggle" the contents of the $current_status variable.
$current_status!=$current_status
However, the != operator doesn't assign anything but it is a comparison operator. In your case, it returns always false based on your query whether $current_status is equal to not $current_status.
What you want to use instead is probably
$current_status = !$current_status
As for your software design, global variables are generally frowned upon in Ruby (and Rails) as are all other kinds of globally mutable state. Use proper classes and objects instead to encapsulate your state and behaviour into more manageable structures. Using global variables, you will shoot yourself in the foot some day and you will have a very hard time to find out what is actually happening. You should try to avoid this :)
You can't use a global variable in this way in such app and there are several reasons. I'll give you just one: depending on the webserver you use, the server may start different processes to handle the incoming web requests, and global variables can't be shared between these processes.
And in fact, it's not even a good idea to use a global variable at all.
If you want to persist a state, use a proper storage. Depending on how long the value should be persisted and who should be able to access it, you have plenty of choices:
database
file system
memory
cookie
Your first snipper does not work because != is a comparison operator, not assignment
Second may not work due to code reloading (Jt class instance is not guaranteed to be the same for other request unless cache_classes is on, but in development you usually always want it off, because otherwise any code changes require server restart to take effect), simple way to have a non-reloaded class - put it in a initializer
For prototyping you also may try thread-local storage for this task: Thread.current[:foo] = 1234

remote code execution in ruby with constantize

I'm trying to wrap my head around remote code execution vulnerabilities in ruby/rails when contantize is used.
I understand that being able to provide any class name to the server could be potentially dangerous, but I'm wondering if this by itself is dangerous.
for example, if a rails controller code looks something like this (i.e. executes a hardcoded method on the instantiated object):
klass = params[:class].classify.constantize
klass.do_something_with_id(params[:id]) if klass.respond_to?('do_something_with_id')
Is this code vulnerable? Or only in combination with being able to also specify the method to be called on the class?
Turning a string into a constant isn't dangerous in itself, but how that constant is used is potentially dangerous (i.e. the method that is then called).
If you really need to do this, then it's probably best to provide a list of classes that are allowed. E.g.
klass = params[:class].classify
if %w(Class1 Class2 Class3).include? klass
klass.constantize.do_something_with_id(params[:id])
else
raise 'Forbidden'
end
However it's done, it helps you to sleep at night to know that the input is considerably limited.
Update
Another way of controlling the creation, which is more explicit but also more verbose, is to use a case statement:
def create_klass(option)
case option
when "option1"
Class1
when "option2"
Class2
when "option3"
Class3
else
raise "Unknown option"
end
end
This way, you don't need to expose the internals of your system to the client. If there are many options, then you could use a hash with options mapping to classes.

Resources