Push single file to Zeitwerk loader - ruby

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.

Related

RSPEC: Is it possible to run other spec file inside a spec file

I know best practice is to make tests run independently.
I am using Rspec to run Selenium Webdriver tests. If I create and teardown users-groups-other models for every specific test case, the test run time goes up significantly (30 mins or so).
So I separated the spec files into create_..., delete_..., and would like to call these spec files inside one spec file. Is this possible and is it a good or bad way of setting up the tests?
Something like:
create_users_spec.rb
create_user_groups_spec.rb
create_dashboard_spec.rb
==> run some tests....
delete_dashboard_spec.rb
delete_user_groups_spec.rb
delete_users_spec.rb
run_all_spec.rb => run all of the spec files above, so you can see that
it needs to run in a certain sequence
You really want to be able to run tests independently. Having to run all tests, even "just" all tests in a given file, seems to me like an unacceptable delay in the development process. It also makes in hard to use tools like Guard that run tests for you based on a file watcher.
If there is really something that needs to run before, for example, every request spec, you can do something like:
# spec_helper.rb
RSpec.configure do |config|
config.before :all, type: :request do
# do stuff
end
end
But also look at what you are trying to do. If you are just setting up model/database data for the tests, then you want fixtures, or better yet, factories. Look into FactoryGirl.
Perhaps organizing your tests with context and using before(:each) and before(:all) judiciously would yield a performance boost. Simplifying the database, or using a library like FactoryGirl can also help.

PRY or IRB - reload class and forget deleted functionality

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.

How to separate core logic and logging code in a Ruby application?

I am looking for a general way to maintain a separation of core logic and logging/debugging/terminal output code in a Ruby application's codebase. Ideally, I would like to have a separate "tracer" codebase parallel to that of my application core (the 'lib' directory in a typical Ruby project). The tracer code would live in a special directory (perhaps called "trace") in the same way that unit tests often live in a parallel structure in the test/spec directory. Tracer files would extend target classes with wrappers for various methods. These wrappers would do things like writing to a log, setting a breakpoint (e.g. with pry's' binding.pry) or incrementing a progress bar whenever the method returned. The loading of the tracer code could then be controlled by a single switch.
I have done some research and come up mostly blank. I've found some pieces of the functionality I'm after; for example, the standard library's Tracer class and the method_decorators gem. But I am wondering there is a more complete solution out there, something analogous to rspec for testing. Does such a thing exist? Or are there perhaps other ways of dealing with this problem?
If you, for instance, are looking at enabling this code only in development, then you can add an initializer in rails: /config/initializers
if( Rails.env.development?)
require "logging_wrappers"
end
In your lib/ folder, you can add your extensions into lib/logging_wrappers.rb
To override the find_by_id function on your User model, simply add the following:
class User
alias_method :unlogged__find_by_id, :find_by_id
def find_by_id(id)
Rails.logger.info "About to find by id"
x = unlogged__find_by_id(id)
if (x.blank?)
Rails.logger.info "We didn't find any users"
end
end
For more information on this technique, several good examples can be found at: http://yehudakatz.com/2009/01/18/other-ways-to-wrap-a-method/

Location of a class in rails app

I want to extend the functionality of Array, add a method that checks if a key exists in array and that the array is not empty, where to write the class and how to make sure it's loaded?
You can either put it into lib/ and make sure that it is autoloaded as outlined in the answer by shioyama; or you could just put it into an initializer. I like the initializer approach a bit better, since it is easier (you get autoloading for free).
I usually create a core_ext subdirectory of the initializers directory and put my core class extensions in there. I always try to put the name of the class that is being extended and a description of what I add into the filename, so in you case I would create a file RAILS_ROOT/config/initializers/core_ext/array_my_function containing:
module MyFunctionForArray
def my_function(arg1, arg2)
# ...
end
end
Array.send :include, MyFunctionForArray
I always try to not reopen the class and extend it directly but to put my extensions into a module and then including this module into the class to extend.
Standard way to do it is to put the code in lib/ and make sure it's autoloaded by rails by adding a line to config/application.rb:
config.autoload_paths += Dir["#{config.root}/lib/**/"]
Then in your code, just make sure you require it wherever you use it. If you want to apply it everywhere, create an initializer in config/initializers with a line:
require 'my_array'
Where my_array.rb is the name of the file in lib where you have the file. That will make it available in your models, controllers, views, etc.
See also: Best way to load module/class from lib folder in Rails 3?
Also, beware of one pitfall of autoloading a directory structure in ruby (not just rails), explained in this answer: Best way to load module/class from lib folder in Rails 3?

what is a controller in sinatra?

I was asked why "I was creating complex Ruby variables in my view.
Shouldn't have these variables been declared by my controller?"
Is my sinatra controller my .rb file? I have one .rb file and view views.
You can setup the idea of controllers by doing (in 1.9.2) this at the top of your main .rb file
Dir.glob("controllers/*.rb").each { |r| require_relative r }
This will require_relative each .rb file in a folder called controllers/
From there you can implement normal routing like you would've previously done in the main .rb file. Please have a look at rstat.us on Github.
Edit: Rstat.us has gone rails3 and while still helpful you may have to go back numerous commits on the master branch to find how it was used.
Each Sinatra route can be considered its own controller in a typical MVC setup. For your example:
require 'sinatra'
require 'json'
get "/foo" do
# This might take many lines of excellent code to form your data
#data = some_complex_array_hash_combo
haml :foo
end
And then in foo.haml:
:javascript
var data = #{#data.to_json};
Sinatra out of the box does not have a standard MVC framework. So while you don't want to leave everything in the main view file, you also don't technically have a "controller" to put this in. Splitting up your application into different functionality would probably be the best approach to keep it simple. Pull large areas of functionality out into separate classes and small things into helper libraries.
Looking at how others do this might help out, this post should have some good examples for you to study: https://stackoverflow.com/questions/2075758/real-life-examples-of-sinatra-applications
If an MVC framework becomes something you really think you need, take a look at Padrino (http://padrinorb.com)
Slightly related post:
https://softwareengineering.stackexchange.com/questions/14293/ruby-sinatra-best-practices-for-project-structure
#CaleyWoods : thank you for the reference to rstat.us
For those who are looking for the Sinatra version, here is a link to a Sinatra commit:
https://github.com/hotsh/rstat.us/tree/00b27505681d80b3943fd9b9e9791f664a04cf39
(so you don't have to trawl through the commit history ;-) )
This is just for inheritance later if you have controllers that inherit from ApplicationController. Good luck!
If your using a config.ru file for your app then this may help.
require 'active_support'
require 'sinatra/base'
APP_ROOT = Pathname.new(File.expand_path('../', __FILE__))
# We have to do this in case we have controllers that inherit from each other.
Dir[APP_ROOT.join('app', 'controllers', '*.rb')].each do |controller_file|
filename = File.basename(controller_file).gsub('.rb', '')
autoload ActiveSupport::Inflector.camelize(filename), controller_file
end
This assumes you put that code into your config.ru but you could put in your application file also and be sure to adjust for directory structure.

Resources