Rails 4 reloading code in lib folder - ruby-on-rails

I know, this question was asked at least milion times, but I still can't find the right solution. I mean clean and easy, like it is supposed to be in Rails applications.
Let's imagine I am developer of an application that needs some code in the lib folder. My app's name is Xy. I will create a file xy.rb in the lib folder with this content:
require 'xy/version'
module Xy
end
After that I will create next file in lib/xy/ named version.rb:
module Xy
module VERSION
def self.to_s
"1.0.0"
end
end
end
So far, so good. Then I will create some controller and I will edit routes:
class IndexController < ApplicationController
def index
render text: Xy::VERSION
end
end
root 'index#index'
I also need require my code, in application.rb I will add require 'xy'
Then I will run my server rails s and I will wisit the app in the browser http://localhost:3000 - seems good, I see 1.0.0.
Then I will update the version string in Xy::VERSION to 1.0.1 and I'll refresh the browser - here it comes, I will no see the change. To see it I will have to restart the server. And this is quite anoying.
So the question is: What is the right way to force RoR 4+ to reload changes in the lib folder in development environment?
Thank you in advance.

Add this to application.rb:
config.autoload_paths += %W(#{config.root}/lib)
I'm using this in my app now, you never need to even require' the lib files anywhere and don't have to restart the server. Just make sure you name your files correctly. E.g
/lib/foo/rb
class Foo
/lib/foo/bar.rb
class Foo::Bar

Related

I moved my folder from lib to /app in a rails 6, I can't seem to instantiate an object still because of load path issue

I have a new rails 6 application and in the lib folder I had this:
/lib/some_app_name/stripe/subscription/subscription_service.rb
module Someappname # Someappname is also in my application.rb
module Stripe
class SubscriptionService
def initialize(a)
#a = a
end
end
end
end
I then moved the 'some_app_name' folder to:
/app/some_app_name/stripe/subscription_service.rb
I read that anything inside of /app will be autoloaded and reloaded so I moved in here. It wasn't working in /lib also.
In my home_controller.rb I tried this:
ss = Someappname::Stripe::SubscriptionService.new("a")
I get an error saying:
uninitialized constant Someappname::Stripe::SubscriptionService
What am I doing wrong here?
I suspect it's spring, try this
bin/spring stop
And then start rails console, stopping Spring will force Rails to load your app fresh
Also,
if the name of your module is Someappname, then the directory name should be app/someappname and not some_app_name
Hope that helps!
Rails does auto-load everything under /app, but there's a caveat. First level of directories is not becoming a part of module name. That's why you can have class User in /app/models/user.rb (and not class Models::User).
Solution: place your code into some folder under /app. I usually call it /app/lib or /app/custom or something like that.
/app/custom/some_app_name/stripe/subscription/subscription_service.rb
(and yes, make sure that names in your filepath correctly represent names in your module path. You can't have directory some_app_name for module Someappname, but you can for SomeAppName)

RoR Mailboxer Monkey Patch works only a few times

I am adding a method to Mailboxer::Conversation to retrieve a link using mailboxer's emails to reply (i.e. reply_link).
I've decided to monkey patch mailboxer within my application. What I've done exactly is the following:
Created the folder structure lib/mailboxer/extensions.
Added files lib/mailboxer/extensions/conversation.rb, lib/mailboxer/extensions.rb, lib/mailboxer.rb.
The following is the content of the files:
# lib/mailboxer/extensions/conversation.rb
module Mailboxer
module Extensions
module Conversation
def reply_link
"/mail?notif_id=#{id}"
end
end
end
end
# lib/mailboxer/extensions.rb
require 'mailboxer/extensions/conversation'
# lib/mailboxer.rb
require 'mailboxer/extensions'
My config/application.rb has the following:
config.autoload_paths += %W(#{config.root}/lib)
Which gives me access to my lib folder.
Then what I do is include Mailboxer::Extensions::Conversation to Mailboxer::Conversation within the mailboxer initializer file initalizers/mailboxer.rb:
Mailboxer.setup do |config|
# ...
end
Mailboxer::Conversation.include Mailboxer::Extensions::Conversation
In my rails console, the code always works. However in the website, the reply_link method works at first, then becomes undefined randomly.
Couple of attempts later....
and it stops working until I restart the server...
Whenever I get an unrelated exception (i.e. typo, refactoring, etc.) the reply_link method becomes undefined. Could this be a development thing?
I could fork mailboxer, make my changes then go on. But the method is so custom to my application that I'd rather just patch.
If anyone has any suggestions, advice or questions I truly appreciate the advice.
First, I am still convinced that this is a development issue only. Whenever I have time to spare, I will test this out and post here.
Second, to ensure this never happened again I copied the source for the Mailboxer's Conversation and added an inclusion include MailboxerExt::Conversation.
I also structured my extension to not collide, reload Mailboxer's namespace.
The final result has the folders app/models/mailboxer, lib/mailboxer_ext.
The files are app/models/mailboxer/conversation.rb, lib/mailboxer_ext.rb and lib/mailboxer_ext/conversation.rb.

Auto-loading lib files in Rails 4

I use the following line in an initializer to autoload code in my /lib directory during development:
config/initializers/custom.rb:
RELOAD_LIBS = Dir[Rails.root + 'lib/**/*.rb'] if Rails.env.development?
(from Rails 3 Quicktip: Auto reload lib folders in development mode)
It works great, but it's too inefficient to use in production- Instead of loading libs on each request, I just want to load them on start up. The same blog has another article describing how to do this:
config/application.rb:
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib)
config.autoload_paths += Dir["#{config.root}/lib/**/"]
However, when I switch to that, even in development, I get NoMethodErrors when trying to use the lib functions.
Example of one of my lib files:
lib/extensions.rb:
Time.class_eval do
def self.milli_stamp
Time.now.strftime('%Y%m%d%H%M%S%L').to_i
end
end
Calling Time.milli_stamp will throw NoMethodError
I realize others have answered similar questions on SO but they all seem to deal with naming conventions and other issues that I didn't to have to worry about before- My lib classes already worked for per-request loading, I just want to change it to per-startup loading. What's the right way to do this?
I think this may solve your problem:
in config/application.rb:
config.autoload_paths << Rails.root.join('lib')
and keep the right naming convention in lib.
in lib/foo.rb:
class Foo
end
in lib/foo/bar.rb:
class Foo::Bar
end
if you really wanna do some monkey patches in file like lib/extensions.rb, you may manually require it:
in config/initializers/require.rb:
require "#{Rails.root}/lib/extensions"
P.S.
Rails 3 Autoload Modules/Classes by Bill Harding.
And to understand what does Rails exactly do about auto-loading?
read Rails autoloading — how it works, and when it doesn't by Simon Coffey.
Though this does not directly answer the question, but I think it is a good alternative to avoid the question altogether.
To avoid all the autoload_paths or eager_load_paths hassle, create a "lib" or a "misc" directory under "app" directory. Place codes as you would normally do in there, and Rails will load files just like how it will load (and reload) model files.
This might help someone like me that finds this answer when searching for solutions to how Rails handles the class loading ... I found that I had to define a module whose name matched my filename appropriately, rather than just defining a class:
In file lib/development_mail_interceptor.rb (Yes, I'm using code from a Railscast :))
module DevelopmentMailInterceptor
class DevelopmentMailInterceptor
def self.delivering_email(message)
message.subject = "intercepted for: #{message.to} #{message.subject}"
message.to = "myemail#mydomain.org"
end
end
end
works, but it doesn't load if I hadn't put the class inside a module.
Use config.to_prepare to load you monkey patches/extensions for every request in development mode.
config.to_prepare do |action_dispatcher|
# More importantly, will run upon every request in development, but only once (during boot-up) in production and test.
Rails.logger.info "\n--- Loading extensions for #{self.class} "
Dir.glob("#{Rails.root}/lib/extensions/**/*.rb").sort.each do |entry|
Rails.logger.info "Loading extension(s): #{entry}"
require_dependency "#{entry}"
end
Rails.logger.info "--- Loaded extensions for #{self.class}\n"
end

Rails 3 engine and code reloading in development mode

I have a rails 3 engine. In initializer it requires a bunch of files from some folder.
In this file user of my engine defines code, business logic, configures engine, etc..
All this data is stored statically in my engine main module (in application attribute)
module MyEngine
class << self
def application
#application ||= MyEngine::Application.new
end
end
end
I want this files to be reloaded on each request in development mode.
(So that the user don't have to reload server to see changes he just made)
Of course I can do something like this instead of initializer
config.to_prepare do
MyEngine.application.clear!
load('some/file')
end
But this way i will have issues (because constants defined in this file won't really be reloaded).
The ideal solution would be to make my whole engine reloadable on each request, but a have not found the way to do it.
It's an old question but I think adding ActiveSupport::Dependencies.explicitly_unloadable_constants += %w[ GemName ] to your development.rb should do the trick.
Have you tried turning reload_plugins on?
# environments/development.rb
config.reload_plugins = true
Its a bit of hack but using require_dependency and just reopening the class might work?
# app/models/project.rb
require_dependency File.join(MyEngine::Engine.root, 'app', 'models', 'project')
class Project
end
For those who are working on Engine views or I18n translations only: Those parts are autoreloaded by default, no need to restart the server!

How to extend an 'unloadable' Rails plugin?

I'm trying to write a plugin that will extend InheritedResources.
Specifically I want to rewrite some default helpers.
And I'd like it to "just work" once installed, w/o any changes to application code.
The functionality is provided in a module which needs to be included in a right place. The question is where? :)
The first attempt was to do it in my plugin's init.rb:
InheritedResources::Base.send :include, MyModule
It works in production, but fails miserably in development since InheritedResource::Base declared as unloadable and so its code is reloaded on each request. So my module is there for the first request,
and then its gone.
InheritedResource::Base is 'pulled' in again by any controller that uses it:
Class SomeController < InheritedResource::Base
But no code is 'pulling in' my extension module since it is not referenced anywhere except init.rb which is not re-loaded on each request
So right now I'm just including the module manually in every controller that needs it which sucks.
I can't even include it once in ApplicationController because InheritedResources inherites from it and so it will override any changes back.
update
I'm not looking for advice on how to 'monkey patch'. The extension is working in production just great. my problem is how to catch moment exactly after InheritedResources loaded to stick my extension into it :)
update2
another attempt at clarification:
the sequence of events is
a) rails loads plugins. my plugin loads after inherited_resources and patches it.
b) a development mode request is served and works
c) rails unloads all the 'unloadable' code which includes all application code and also
inherited_resources
d) another request comes in
e) rails loads controller, which inherites from inherited resources
f) rails loads inherited resources which inherit from application_controller
g) rails loads application_contrller (or may be its already loaded at this stage, not sure)
g) request fails as no-one loaded my plugin to patch inherited_resources. plugin init.rb files are not reloaded
I need to catch the point in time between g and h
The Rails::Configuration, config in the environment files, allows registering a callback on the dispatcher that runs before each request in development mode, or once when in production mode.
config.to_prepare do
# do something here
end
The problem is, I don't think your plugin has access to config when the init.rb file is run. Here is a way to register your callback directly in the dispatcher. Just put this in the init.rb file.
require 'dispatcher'
::Dispatcher.to_prepare do
puts "hi there from a plugin"
end
Warning: I don't know what side effects this may have. If possible, try to get access to config and register the callback tha right way.
What you are attempting to do is usually called "MonkeyPatch" - changing the way one module or class is working by "overriding" methods.
It is a common practice in Rails, but it doesn't mean it is the best way to do things - when possible, it is better to use common inheritance (it is more explicit about the changes you make).
Regarding your questions about "where to put the files": it is usually the lib/ directory. This can mean the lib of the rails app, or a lib directory inside a gem or plugin, if you are into that sort of thing.
For example, if the file you want to change is lib/generators/rails/templates/controller.rb of inherited resources, the first thing you have to do is replicate that directory structure inside your lib/ folder ('lib/generators/rails/templates/controller.rb')
Inside that new file of yours, (empty at the beginning) you can override methods. However, you must also the modules/classes hierarchy. So if the original gem had this:
module foo
module bar
def f1
...
end
def f2
...
end
end
def f3
...
end
end
And you wanted to modify f1, you would have to respect the foo-bar modules.
module foo
module bar
def f1
... # your code here
end
end
end
Now the last thing you need is to make sure this code is executed at the right time. If you are using the application's lib/ folder, you will need to create an entry on the initializers/ folder and require your new file. If you are developing a gem/plugin, you will have a init.rb file on the "root" folder of that plugin. Put the 'require' there.
I'm not very familiar with this unloadable stuff; maybe I'm asking something obvious but- have you tried making your extension module unloadable, too? (You shouldn't need this if you monkeypatched the module instead of creating a new one)

Resources