Ruby on Rails: Configure API in initializer in development - ruby-on-rails

I have a Ruby on Rails app with an API in lib. Files in lib are autoloaded, and the API is configured in an initializer.
# lib/my_api.rb
module MyApi
extend Configuration
end
# lib/my_api/configuration.rb
module MyApi
module Configuration
attr_accessor :my_setting
def configure
yield self
end
end
end
# config/initializers/my_api.rb
MyApi.configure do |config|
config.my_setting = 'foo'
end
This works in production, but in development the API gets configured when the server is started. After I change some code, the configuration is lost and there are errors because the settings are nil:
irb(main):001:0> MyApi.my_setting
=> "foo"
irb(main):002:0> reload!
Reloading...
=> true
irb(main):003:0> MyApi.my_setting
=> nil
My guess is that in development, the classes are reloaded, but the initializer is not, which means it only gets configured once after starting the server.
Right now I'm duplicating my configuration in lib/my_api.rb, but that's very hacky.
What's a clean solution for this problem?

module MyApi
module Configuration
mattr_accessor :my_setting
def configure
yield self
end
end
end
mattr_accessor is an ActiveSupport macro for creating module level accessors.

Well, until someone comes up with a better solution, I've come up with two workarounds. I went with 2.
Don't autoload the lib directory (meaning don't autoload the API). That means having to restart the server when the API code changes, but solves the problem. That's why configuring gems like this works -- because they aren't autoloaded.
Manually reload the initializer in development at the end of lib/my_api.rb:
load Rails.root.join('config/initializers/smart_meter.rb') if Rails.env.development?
The MyApi constant will be replaced by a new one when Rails autoloads classes. The configuration is still available on the old object:
Loading development environment (Rails 4.2.0)
irb: warn: can't alias context from irb_context.
irb(main):001:0> MyApi.my_setting
=> "foo"
irb(main):002:0> OldMyApi = MyApi
=> MyApi
irb(main):003:0> reload!
Reloading...
=> true
irb(main):004:0> MyApi.my_setting
=> nil
irb(main):005:0> OldMyApi.my_setting
=> "foo"
irb(main):006:0> load Rails.root.join('config/initializers/smart_meter.rb')
=> true
irb(main):007:0> MyApi.my_setting
=> "foo"

Related

Zeitwerk doesn't requires lib classes properly in Rails 6.1.6

I'm trying to update my app from Rails 5.2 to 6.1, and use Zeitwerk autoload, and I am having some issues for autoloading /lib classes.
I included this in config/application.rb:
config.load_defaults 5.2
config.autoloader = :zeitwerk
config.enable_dependency_loading = true
config.autoload_paths += Dir[Rails.root.join('lib/**/*')]
config.eager_load_paths += Dir[Rails.root.join('lib/**/*')]
And, when I run zeitwerk:check, it alerts me that:
Hold on, I am eager loading the application.
expected file lib/facades/coconut/v2/foo.rb to define constant Foo
It is declared as:
# /lib/facades/coconut/v2/foo.rb
module Coconut
module V2
class Foo
# ...
end
end
end
When I try to debug it (putting a binding.pry in some test file), Zeitwerk says it do not defined, until I call it without modules (namespaces):
[1] pry(#<#filename>)> Coconut::V2::Foo
NameError: uninitialized constant Coconut::V2::Foo
from (pry):1:in `setup`
[2] pry(#<#filename>)> Foo
Zeitwerk::NameError: expected file /my-app/lib/api/coconut/v2/foo.rb to define constant Foo, but didn't
from /usr/local/bundle/gems/zeitwerk-2.5.4/lib/zeitwerk/loader/callbacks.rb:25:in `on_file_autoloaded'
[3] pry(#<#filename>)> Coconut::V2::Foo
=> Coconut::V2::Foo
It sounds really strange, because there are another classes in /lib that follows the same structure, but it works well, example
# /lib/api/services/youtube/client.rb
module Services
module Youtube
class Client
# ...
end
end
end
Inspecting it with binding.pry in some test:
pry(#<#filename>)> Services::Youtube::Client
=> Services::Youtube::Client
Has anyone had this problem or know what could be happening?
System:
ruby 2.7.6p219
Rails 6.1.6
Zeitwerk (2.5.4)
An autoload path is a root directory, not its contents.
You need to remove the wildcards as documented here.

Cannot override core ruby class in Rails 2.3.4

I want to extend the ruby class, for example,
# lib/core_ext/hash.rb
class Hash
def gop_compact
delete_if{|k, v| (k.blank? || v.blank?)}
end
end
I have created a separate folder in the /lib directory as follows,
lib/core_ext/hash.rb
And I tried to include this path in load_paths as follows,
# config/environment.rb
config.load_paths += %W( #{RAILS_ROOT}/lib/core_ext )
After all this setup, restarted the server and tried calling method on a Hash object but it throws an undefined method exception.
Note:- Rails version is 2.3.4
I spent lot of time on this but no luck yet. Any help is appreciated.
Thanks in advance!
Even though you've added the core_ext folder to your load paths, you'll still need to require it with require 'hash'. To minimize memory usage, Rails won't actually require ruby files just because you add them to your load_path.
>> Hash.instance_methods.grep(/gop/)
=> []
>> require "hash"
=> true
>> Hash.instance_methods.grep(/gop/)
=> [:gop_compact]

Initializer doesn't seem to be loading

I have an API wrapper with the following code:
module MyApi
mattr_accessor :app_id
end
I'm trying to set the value of app_id in an initializer like this:
# config/initializers/my_api.rb
MyApi.app_id = Rails.application.secrets.my_api["app_id"]
In my secrets file I have:
# secrets.yml
development:
my_api:
app_id: foo
But when I open my console or run tests I get this:
master ✗ $ rails c
Loading development environment (Rails 4.1.4)
2.2.0 :001 > MyApi.app_id
=> nil
2.2.0 :002 > MyApi.app_id = Rails.application.secrets.my_api["app_id"]
=> "foo"
2.2.0 :003 > MyApi.app_id
=> "foo"
I've followed gem readme's about using initializers but have never implemented one myself. Is there something I'm missing here? Using Rails 4.1.4.
Try adding this inside your module:
module MyApi
### Your code here
private
def self.setup
yield self
end
end
And in the initializer change it to have a setup block:
MyApi.setup do |config|
config.app_id = Rails.application.secrets.my_api["app_id"]
end
I have only built a few gems with initializers, but this worked for me.

Middleware not always called in capybara feature test (with rack-test)

I have a gem I'm working on that uses a railtie to add a middleware. Very simple stuff, followed the rails guides section almost exactly. Works fine in development/staging/production.
The middleware initializes a hash-like object in the env at a particular key.
But in my capybara tests, this key is only sometimes initialized. I added a debugger to the middleware and found that it isn't called every time I use the visit method.
What's more is that in this particular spec file, there are 4 examples, and each example calls visit once. But when I run the spec file, the middleware is sometimes called 3 times and sometimes called 2 times. Obviously the middleware stack should be called for every request.
tl;dr: sometimes calling visit in my capybara feature specs (with rack-test driver) does not result in my middleware stack being called.
Help?
ruby 2.0.0-p353
rails 4.0.2
capybara 2.2.1
rack-test 0.6.2
EDIT: This is some of the relevant code here: how the middleware is added and what it does. MyGem::Middleware#call is only sometimes called when using Capybara's visit method.
# railtie.rb
module MyGem
class Railtie < Rails::Railtie
initializer "my_gem.configure_rails_initialization" do |app|
app.middleware.use MyGem::Middleware
end
end
end
# middleware.rb
module MyGem
class Middleware
def initialize(app, options={})
#app = app
# options unused
end
def call(env)
# using a special internal version of the Rack::Session::Cookie class
session = MyGem::Rack::Session::Cookie.new(
#app,
:coder => MyGem::Rack::Session::Cookie::Base64::Marshal.new,
:key => ENV_SESSION_KEY,
:path => '/',
:domain => domain(env),
:expire_after => 6.weeks.to_i, # seconds till this expires
:secret => 'my_gem_secret_14f1c4ad25a6be00fe53f5fd2d746167',
)
# use Rack::Session:Cookie method
session.context(env, #app)
end
end
end
Figured it out!
I was also adding a Warden hook that expected the env key to be added after signing users in and out, and if the Warden::Manager middleware was added prior to my gem's middleware, then it would error out when running my hook that expected that env key to be set.
Solution was to do this in my railtie:
app.middleware.insert_before Warden::Manager, MyGem::Middleware

NameError when using act_as_ferret

I am getting this error when I am using
acts_as_ferret :fields =>[:competitor], :remote => true
NameError in PartController#index
uninitialized constant PartController::Competitor
My Model
class Competitor < ActiveRecord::Base
validates_presence_of :fee_earner_id, :notes
belongs_to :fee_earner
belongs_to :country
belongs_to :state
belongs_to :user
acts_as_ferret :fields =>[:competitor], :remote => true
end
My controller
class PartController < ApplicationController
def index
#proscribeds = Competitor.paginate(:all,
:order => sort ,
:page => params[:page],
:per_page => 70 )
end
end
Its working fine in localhost but when I deploy it in the server than I get this error.
act_as_ferret is working good with other models. I don't know why this is not working with only Competitor model.
These might seem like simplistic suggestions, but here's what comes to mind:
Do you have the same OS on localhost and the server? I've been burned by little discrepancies like the differences between how Unix and Windows handle mixed case pathnames.
Have the most updated versions of all your files been moved? Can you do a diff and ensure that there isn't a missing config file or environment variable or something?
I know those suggestions aren't really Rail/Ruby-specific, but I've found that little configuration problems tend to give me more headaches than actual code errors do.
Good luck!
This could be a problem with the bin/ferret_server file on the remote drb server you are running. What it is complaining about is that it knows nothing about the PartController::Competitor model. This is because the ferret-server does not eager load all the Rails classes like Rails does by default.
I had a similar issue and the solution was to also require the Rails config/environment file as well as the Rails config/application file.
Something like this:
begin
ENV['FERRET_USE_LOCAL_INDEX'] = 'true'
if env = $ferret_server_options['environment']
ENV['RAILS_ENV'] = env
end
# determine RAILS_ROOT unless already set
root = File.expand_path(determine_rails_root)
begin
require File.join(root, 'config', 'application')
# Also require environment to eager load Rails classes
require File.join(root, 'config', 'environment')
rescue
puts "Error booting your rails app at #{root}: #{$!}\n#{$!.backtrace.join("\n")}"
raise $!
end
puts "Rails.root: #{Rails.root}"
puts "Rails.env: #{Rails.env}"
require 'acts_as_ferret/server/server'
ActsAsFerret::Server::Server.new.send($ferret_server_action)
rescue Exception => e
$stderr.puts(e.message)
$stderr.puts(e.backtrace.join("\n")) if $ferret_server_options['debug']
exit(1)
end

Resources