If you change a file then re-load it in pry or irb, it seems to pick up any NEW functionality you've added to that class, but doesn't forget about OLD functionality you've deleted from that class.
Steps to reproduce:
Create a class with a single method - eg. say_hello.
Open PRY or IRB, and load 'my_class.rb'
Edit your class - delete the existing method, and add a new one with a different name - eg. say_goodbye
reload your class - load 'my_class.rb'
BOTH your methods will now be available. I see why this happens - because ruby allows you to re-open classes for modification, re-loading your file basically just re-opens the existing version of the class you'd already loaded, rather than wiping the memory of that class and defining the class again from scratch.
My question is, how do you work around this, apart from quitting and re-starting PRY or IRB? How do you just say "forget my previous class completely and re-load this file from scratch"?
Thanks!
You can use remove_const to remove the class from its parent, either from the Module it is in:
My::Module.send(:remove_const, :MyClass)
or from Object if it was not declared inside a Module:
Object.send(:remove_const, :MyClass)
If you don't need to selectively reload specific modules, classes, etc., and want to preserve your local variables, just:
reload!
While you are in pry, you can use reset and that will reset the environment.
To reset IRB you can see this answer which is to say exec($0)
According to reset is exec 'pry' (+ some Pry's things). $0 in the IRB seems to be "irb" and in the pry, $0 it is "pry".
$0 is a global variable that means 'the running program' and so that is not surprising.
Look at the source code in Pry for the reset command though, I am somewhat surprised that they refer to pry by name, rather than this well known variable.
Here is the code from Pry that gives the functionality of reset, and why it increases memory use.
class Command::Reset < Pry::ClassCommand
match 'reset'
group 'Context'
description 'Reset the REPL to a clean state.'
banner <<-'BANNER'
Reset the REPL to a clean state.
BANNER
def process
output.puts 'Pry reset.'
exec 'pry'
end
end
The third line from the last in this listing being the one.
The cleaner way is actually doing the housekeeping yourself, as answered by Uri.
Related
For a Roda app, I'd like to include a single file for autoloading by Zeitwerk while ignoring all the others in that directory. The simplified layout looks like this:
config
application.rb
puma.rb
(...)
lib
my_class.rb
(...)
To setup Zeitwerk, this would be perfect but not possible:
loader = Zeitwerk::Loader.new
loader.push_dir('lib')
loader.push_file('config/application.rb') # <--- no such thing as push_file
if ENV['RACK_ENV'] == 'development'
loader.enable_reloading
loader.setup
run ->(env) do
loader.reload # <--- full reload on every request
Application.call(env)
end
else
Zeitwerk::Loader.eager_load_all
loader.setup
run Application.freeze.app
end
Excluding all but application.rb using loader.ignore won't cut it neither, Ruby glob patterns don't implement negation it appears.
I could of course move application.rb into lib/ (and I might have to), but I'd rather have it in config/. Any way to make this fly?
First, to answer the question, there is no way to push one single file. I might consider adding that feature, but need to think about it.
Then, in Roda, you could argue that the application class is not configuration. That class dispatches and has application logic. I'd consider moving that file out of the config directory for conceptual reasons, regardless.
If you totally want to keep it in that directory, you can "reload" manually in development (untested, written on the spot):
loader.reload
load "#{__dir__}/config/application.rb"
Application.call(env)
Point is that load, unlike require, executes always the file.
I am not familiar with Roda and it could be the case that running the file multiple times messes with internal state. If that was the case, you could remove the constant before the load call:
Object.send(:remove_const, :Application) if defined?(Application)
I've seen Roda has an inherited hook, but does not seem to cache subclasses in internal state.
Additionally, for production, you normally want to run loader.setup before eager_load_all, so that your application is eager loaded too.
I'm building a gem that allows for a much more convenient and configurable 'console' for ruby gem development than the current options (ie: 'bundle console').
As such, one of if not the most important aspects of the entire gem is that it does in fact open a console session, which I currently have set up in a start method:
class MyConsole
def start
Pry.start(self)
end
end
I am trying to test this functionality, but it's difficult because their's not a lot of good resources for this. It's also really annoying because every time I run this method in rspec, pry opens, and I have to exit it before finishing the rest of the tests.
I have three main questions:
How can I run a method in rspec that starts pry without actually starting pry?
How can I specifically test that that method does in fact start pry?
Assuming 1 and 2 are possible, how can I test what context the method starts pry in? (notice how the start method calls Pry.start(self), meaning the pry session should open in the context of a MyConsole instance.)
The best way to do this would probably be using an RSpec spy.
Your test would probably look something like this:
describe MyConsole do
describe '#start' do
it 'calls Pry.start' do
described_class.start
expect(Pry).to have_received(:start).with('your_args_here')
end
end
end
This is a really good explanation of RSpec stubbing options, IMO, where you can learn more: https://about.futurelearn.com/blog/stubs-mocks-spies-rspec
class Carnivore
def roar=(v)
#roar = v
end
def roar
#roar
end
end
trex = Carnivore.new
trex.roar = "GRRRxxz"
puts trex.roar
OS Used: Windows 10.
I am using IRB and playing around with some of the getter/setter methods. If I delete the setter method roar=(v), the reader method still works and will print out "GRRRxxz". However, if I quit IRB and launch another session, IRB will give me an error. Note that I am loading the files each time using the load keyword i.e.: load "test.rb".
What's even more unusual is that if I update the puts after deleting the setter method, it will update the puts to whatever (i.e.: set GRRRxxz to YOOO, it will change to YOOO).
Can someone explain to me why IRB is operating in this manner? It seems to be saving the setter method for some inexplicable reason. It also does the same thing if you delete the reader method (will still work if you delete writer method, but an exiting and rebooting of IRB makes it fail).
Any help would be greatly appreciated!
IRB has nothing to do with this. It's a feature of ruby called "open classes"
So when you first load your Carnivore class, it is loaded in its entirety, because it didn't exist before. But if you then remove a method in a file and reload it, what happens is ruby opens your class and adds a getter method. Which it already had, so it's overwritten. Which explains why changes to method bodies (your puts there) are reflected.
This technique is what powers so-called "monkey-patching". You can patch any class in ruby, even a system one.
class String
def pirate
self + ', arrrr!'
end
end
'hello'.pirate # => "hello, arrrr!"
You see, it "patches" an existing class. If this class definition replaced/shadowed the one from the system, your app would be pretty broken.
currently I am loading all YAML files inside Cucumber Hooks like this before- the intention is to ensure we dont code individually for loading YAML files.
#all_Yaml_Files = Dir.entries(#projectData_Path).select {|f| !File.directory? f};
#all_Yaml_Files.each do |file_Name|
file_Full_Path = #projectData_Path + '/' + file_Name
instance_variable_set("#" + file_Name.split('.')[0], YAML.load_file(file_Full_Path))
end
However this code is inside a Before Cucumber Hook. So When the instance variables are loaded they are not available for next scenario. How can I make them run only once and then be available for all scenarios and all features - ie globally. Currently its loading everytime for a scenario.
Before do
....
end
Note: By doing above , we can directly access a YAML_FileName by #YAML_FileName variable without writing code. Thats my intention. Same happens with all excel files, json files etc etc. They are all called from inside Hooks.
EDIT: added the "quick" solution
Quick solution
You are using class variables (#var) now; these are subject to some Cucumber magic so they are cleaned out after each scenario, as you noticed. This makes them rather useful as well, giving you a "clean slate" while allowing you to transfer some state from one step to the next.
This is all just Ruby, so you can of course use global ruby variables ($var) instead. This still requires you to change your existing code.
If you want to keep your class variables #var and still have them to stick around, you could do hooks to save and restore them in globals, i.e.:
Around do |scenario, block|
#var = $var
begin
block.call
ensure
$var = #var
end
end
You'll want to make sure this hook is encountered early enough so it fires before any other hooks you might have.
I don't like to use globals in any language, so I try not to use either #var or $var in Cucumber. Instead, I prefer to do it the OOP way... hence:
"Clean" solution (PORO)
I use a singleton module for such things:
# support/000_cucu_globals.rb
class CucuGlobals
include Singleton
def initialize
#somevalue = 'hello world'
...
end
attr_reader :somevalue
end
Elsewhere:
puts CucuGlobals.instance.somevalue + "!"
Adjust/extend/get creative, as necessary. If you want to avoid typing so much, feel free to use a shorter class name (like G for global). You can also make your accessors class methods instead to even skip the instance in between. This is all just Ruby (not Cucumber) at this point, so there is not Cucumber magic involved anymore.
Hint: you want to load this module pretty early. I'm sure there is a good way to do that "cleanly", but I opted to just name it in such a way that is found first, by Cucumber (000_...).
I have the following model in a Rails app set up:
# app/models/event_list/peak.rb
class EventList::Peak < AR
# ...
end
in a gem I use for import (activerecord-import) the following line triggers a NameError exception:
Module.const_get(class_name)
# class_name evals to :'EventList::Peak'
So, I fire up the Rails console and try to manually get the right symbol I need, by doing:
Module.const_get(EventList::Peak.to_s.to_sym)
but I get the same error. When I simply type EventList::Peak in the console I get the correct class object.
Am I missing something here?
Module::const_get does not support arbitrary depth; it only gets children of the calling module. So in your case EventList.const_get(Peak.to_s.to_sym) should work.
You can use ActiveSupport's qualified_const_get to do what you are asking, e.g.:
require 'active_support/core_ext'
Module.qualified_const_get(EventList::Peak.to_s.to_sym)
If you don't want to use ActiveSupport, you could also do:
Module.const_get(EventList.to_s.to_sym).const_get(Peak.to_s.to_sym).
It would also be easy to create a wrapper around this using inject to support arbitrary depth and duplicate the qualified_const_get functionality.