How do I temporarily monkey with a global module constant? - ruby-on-rails

Greetings,
I want to tinker with the global memcache object, and I found the following problems.
Cache is a constant
Cache is a module
I only want to modify the behavior of Cache globally for a small section of code for a possible major performance gain.
Since Cache is a module, I can't re-assign it, or encapsulate it.
I Would Like To Do This:
Deep in a controller method...
code code code...
old_cache = Cache
Cache = MyCache.new
code code code...
Cache = old_cache
code code code...
However, since Cache is a constant I'm forbidden to change it. Threading is not an issue at the moment. :)
Would it be "good manners" for me to just alias_method the special code I need
just for a small section of code and then later unalias it again? That doesn't
pass the smell test IMHO.
Does anyone have any ideas?
TIA,
-daniel

But you can overwrite constants in Ruby (regardless of whether it's a module or class or simple other object):
MyConst = 1
# do stuff...
old_my_const = MyConst
MyConst = 5
puts "MyConst is temporarily #{MyConst}"
MyConst = old_my_const
puts "MyConst is back to #{MyConst}"
Output:
a.rb:6: warning: already initialized constant MyConst
MyConst is temporarily 5
a.rb:8: warning: already initialized constant MyConst
MyConst is back to 1
The warnings are simply that: warnings. Your code will continue to run the same.
Okay, maybe the warnings are unacceptable in your situation for some reason. Use this suppress_all_warnings method I've written. Example includes reassigning a module.
def suppress_all_warnings
old_verbose = $VERBOSE
begin
$VERBOSE = nil
yield if block_given?
ensure
# always re-set to old value, even if block raises an exception
$VERBOSE = old_verbose
end
end
module OriginalModule
MyConst = 1
end
module OtherModule
MyConst = 5
end
def print_const
puts OriginalModule::MyConst
end
print_const
suppress_all_warnings do
old_module = OriginalModule
OriginalModule = OtherModule
print_const
OriginalModule = old_module
end
print_const
Now you get the correct output, but without the warnings:
1
5
1

Related

Rails: thread_mattr_accessor randomly becoming `nil` in development

In Rails 5.2.2.4, I'm getting this inexplicable behavior where a thread_mattr_accessor is becoming nil randomly.
Given this module:
module Test
thread_mattr_accessor :max_installments
self.max_installments = 12
end
Out of 6 pager refreshes (using Puma in development), I get a comparison of Integer with nil failed when comparing an integer with that variable.
On the Rails red exception page, I can use the bottom controller to confirm it's actually nil!
However, simply refreshing the page some times, I will eventually get a normal page load with that variable being set.
What might be setting that to nil? There's no code at all that sets that variable anywhere else other than the self.max_installments= in the class definition above.
Module Test
module Test
thread_mattr_accessor :max_installments
self.max_installments = 12
end
Normal use
puts "max_installments is: #{Test.max_installments.inspect}"
# max_installments is: 12
In Thread use
Thread.new { puts "max_installments in thread is #{Test.max_installments.inspect}" }
# max_installments in thread is nil
Set value inside the thread
Thread.new do
Test.max_installments = 99
puts "max_installments in thread now is #{Test.max_installments.inspect}"
end
# max_installments in thread now is 99
Thread.new { puts "max_installments in another thread is #{Test.max_installments.inspect}" }
# max_installments in another thread is nil
puts "max_installments is: #{Test.max_installments}"
# max_installments is: 12
ASAP this commit must be released and we can set a default value to use in threads like this
module Test
thread_mattr_accessor :max_installments, default: 12
self.max_installments = 12
end
For now, if you go use inside threads, you need set value before to avoid get nil.

Ruby code memory leak in loop

Below code is having memory leak. It's running under ruby 2.1.1. I am not able to find the actual leak.
q = Queue.new("test")
while true do
m = q.dequeue
body = JSON.parse(m.body)
user_id = body["Records"][0]
user = V2::User.find(user_id)
post = V2::Post.find(post_id)
end
After few hours of run I added GC.start but its not solving the problem
q = Queue.new("test")
while true do
m = q.dequeue
body = JSON.parse(m.body)
user_id = body["Records"][0]
user = V2::User.find(user_id)
post = V2::Post.find(post_id)
GC.start
end
I don't know how to find the actual memory leak.
Try removing the lines from the bottom up, and seeing if the memory leak persists. It's possible that the Memory leak is coming from the find method, or possibly the JSON.parse (extremely unlikely), or the custom Queue data structure. If the memory leak is still there after removing all of the lines, it is likely coming from the worker itself and/or the program running the workers.
q = Queue.new("test")
while true do
m = q.dequeue # Finally remove this and stub the while true with a sleep or something
body = JSON.parse(m.body) # Then remove these two lines
user_id = body["Records"][0]
user = V2::User.find(user_id) # Remove the bottom two lines first
post = V2::Post.find(post_id)
end
I bet the problem is with introduced local variables (sic!). Get rid of user_id and post_id and it’ll likely stop leaking:
# user_id = body["Records"][0]
# user = V2::User.find(user_id)
user = V2::User.find(body["Records"][0]) # sic!
The reason is how Ruby stores objects in RValues.

How can I make this method more concise?

I get a warning when running reek on a Rails project:
[36]:ArborReloaded::UserStoryService#destroy_stories has approx 8 statements (TooManyStatements)
Here's the method:
def destroy_stories(project_id, user_stories)
errors = []
#project = Project.find(project_id)
user_stories.each do |current_user_story_id|
unless #project.user_stories.find(current_user_story_id).destroy
errors.push("Error destroying user_story: #{current_user_story_id}")
end
end
if errors.compact.length == 0
#common_response.success = true
else
#common_response.success = false
#common_response.errors = errors
end
#common_response
end
How can this method be minimized?
First, I find that class and method size are useful for finding code that might need refactoring, but sometimes you really do need a long class or method. And there is always a way to make your code shorter to get around such limits, but that might make it less readable. So I disable that type of inspection when using static analysis tools.
Also, it's unclear to me why you'd expect to have an error when deleting a story, or who benefits from an error message that just includes the ID and nothing about what error occurred.
That said, I'd write that method like this, to reduce the explicit local state and to better separate concerns:
def destroy_stories(project_id, story_ids)
project = Project.find(project_id) # I don't see a need for an instance variable
errors = story_ids.
select { |story_id| !project.user_stories.find(story_id).destroy }.
map { |story_id| "Error destroying user_story: #{story_id}" }
respond errors
end
# Lots of services probably need to do this, so it can go in a superclass.
# Even better, move it to #common_response's class.
def respond(errors)
# It would be best to move this behavior to #common_response.
#common_response.success = errors.any?
# Hopefully this works even when errors == []. If not, fix your framework.
#common_response.errors = errors
#common_response
end
You can see how taking some care in your framework can save a lot of noise in your components.

Recursive method not returning to previous line position in caller

I have been trying to find a solution to this for some time. I have found questions and answers on recursion but nothing that seemed to fit this particular situation.
I have written a class which should go through the given folder and all subfolders and rename files and folders if a particular search pattern is found.
Everything works as expected the replaceAllInDir gets called, it replaces files and folders if needed. The next step then is to do the same for all subfolders within the given folder.
So a subfolder gets identified and replaceAllInDir gets called from within itself. Let's assum the particular subfolder called does not contain any subfolders. I would then expect that we return to the parent folder and continue looking for other subfolders. But instead control is not returned to the parent calling method and the program ends.
I am aware of other ways of solving the actual use case, but I cannot explain the behaviour of ruby.
class MultiFileAndFolderRename
attr_accessor :rootDir, :searchPattern, :replacePattern
def initialize(rootDir, searchPattern, replacePattern)
#rootDir = rootDir
#searchPattern = searchPattern
#replacePattern = replacePattern
end
def execute
replaceAllInDir(#rootDir)
end
def getValidDirEntries(dir)
dirList = Dir.entries(dir)
dirList.delete('.')
dirList.delete('..')
dirList
end
def replaceAllInDir(currentDir)
Dir.chdir(currentDir)
puts "Processing directory: " + Dir.pwd
dirList = getValidDirEntries(currentDir)
dirList.each { |dirEntry|
attemptRename(dirEntry)
}
dirList = getValidDirEntries(currentDir)
dirList.each { |dirEntry|
if File.directory?(dirEntry)
newDir = currentDir + '\\' + dirEntry
rntemp = MultiFileAndFolderRename.new(newDir, 'searchString', 'replaceString')
rntemp.replaceAllInDir(newDir)
end
}
end
def attemptRename(dirEntry)
if dirEntry.match(#searchPattern)
newname = dirEntry.to_s.sub(#searchPattern, #replacePattern)
FileUtils.mv(dirEntry.to_s, newname)
end
end
end
You have a bug. The first line of replaceAllInDir() is Dir.chdir(). chdir() changes the directory of the current process on a global scale. It's not call-stack dependent. So later when you move into a subdirectory and change into that, the change becomes permanent even if you return from the recursion.
You need to change back to the correct directory after any call to replaceAllInDir(). For example:
...
dirList.each { |dirEntry|
if File.directory?(dirEntry)
....
rntemp.replaceAllInDir(newDir)
Dir.chdir(currentDir) # <- Restore us back to the correct directory
end
}
I have tried your code, and I have found numerous errors in it. Perhaps if you fix them, your idea is working.
You should include in a library like that a part at the end that allows to call it from the shell: MultiFileAndFolderRename.new(ARGV[0], ARGV[1], ARGV[2]).execute if __FILE__ == $0 This ensures when you call the ruby code from the shell by ruby rename.rb test old new, your class will be instantiated, and the search and replace pattern will be set accordingly.
You shouldn't set the current directory, because that ensures that the line getValidDirEntries(currentDir) will not work. If you eg. call it for the directory test, and then change your current directory to test, inside the directory, getValidDirEntries('test') will not work like expected.
You should use only forward slashes instead of the double backward ones. So your code will work on Linux and Mac OS X as well.
When you instantiate the new instance of MultiFileAndFolderRename (which is not necessary), the arguments to the initializer are the wrong ones. Instead, you should use your current instance and just call self.replaceAllInDir(newDir) instead of rntemp = MultiFileAndFolderRename.new(newDir, 'searchString', 'replaceString');rntemp.replaceAllInDir(newDir).
I think the wrong instantiation is the major reason why it works not as expected, but the others should be fixed as well.

Disable rails class_caching mechanism for Time.now?

I'm currently fighting with the rails class_caching mechanism as I need to return a file path that changes discrete over time. It is used to constantly change a log file path after the amount of GRAIN seconds and returns a fully working timestamp:
GRAIN = 30
def self.file_path
timestamp = (Time.now.to_i / GRAIN) * GRAIN
return FILE_DIR + "tracking_#{timestamp.call}.csv"
end
This works really great if the class_caching of rails is set to false. But of course the app is to run with enabled class caching. And as soon as I enable it, either the timestamp variable is cached or the Time.now expression.
I tried to solve this with a proc block, but no success:
def self.file_path
timestamp = Proc.new { (Time.now.to_i / GRAIN) * GRAIN }
return FILE_DIR + "tracking_#{timestamp.call}.csv"
end
Is there anything like a cache disabled scope I could use or something like skip_class_caching :file_path? Or any other solutions?
Thank you for your help!
It's not entirely clear where your code is located, but ActiveRecord has an uncached method that suspends the cache for whatever is inside its block.
I found the problem. Not the Time.now was beeing cached but a logger instance. It was assigned in another method calling the file_path.
As long as the class caching was disabled the environment forgot about the class variable between the requests. But as soon as it was enabled the class variable stayed the same - and desired value - but never changed.
So I had to add a simple condition that checks if the file_path changed since the last request. If so, the class variable is reassigned, otherwise it keeps the same desired value.
I changed from:
def self.tracker
file_path = Tracking.file_path
##my_tracker ||= Logger.new(file_path)
end
to:
def self.tracker
file_path = Tracking.file_path
##my_tracker = Logger.new(file_path) if ##my_tracker.nil? or Tracking.shift_log?(file_path)
##my_tracker
end
Thank you for your help anyways!

Resources