Ruby inheritance and require order - ruby

I am trying to set up a new modularised Sinatra app. This is the relevant folder structure:
- app
- controllers
- application_controller.rb
- website_controller.rb
- config.ru
The application_controller.rb defines the ApplicationController class, requires sinatra/base and, apart from that, doesn't do anything interesting.
The website_controller.rb inherits application_controller.rb:
# website_controller.rb
class WebsiteController < ApplicationController
include Sinatra::Cookies
...
end
My config.ru file is what I run to start the Sinatra app, so it is supposed to require all the classes:
#config.ru
require 'sinatra/base'
Dir.glob('./app/{models,helpers,controllers}/*.rb').each {|file|
require file
}
...
When it requires all files, it requires website_controller.rb before requiring application_controller.rb. That way, when the inheritance on WebsiteController is parsed, it does not know yet what ApplicationController is, and I get the error uninitialized constant ApplicationController (NameError).
What is the best practice to set up my project so I have all required files ready without facing this kind of ordering problems?

So, the other answer is actually the correct and easiest way - but if for whatever reason you don't want to do it that way, you can use autoload provided you've developed your Sinatra app in the "modular" style.
In config.ru you are currently "eager-loading" all of your files. Instead, you would explicitly list the load paths for unknown constants - like so:
autoload :ApplicationController, 'app/controllers/application_controller'
autoload :WebsiteController, 'app/controllers/website_controller'
Note that autoload is a method on Module so you have to put the calls to autoload within the same 'namespace' - ie:
module MyApp
autoload :MyThing, 'models/my_thing'
end
Will fire the autoload when the constant MyApp::MyThing is missing. That said - unless you know why you need to autoload your classes, just require it at the top of the file.

At the top of website_controller.rb, require application_controller.rb.
You may feel it redundant, but that won't harm. This is the most robust way.

Related

Why doesn't require_all work inside a Sinatra helper?

So I have a function reload_config inside a Sinatra helper module which is defined below in helpers.rb.
module Web
module Helpers
def reload_config
require_all 'config/*.rb'
end
end
end
It is then loaded in Web::Base the recommended way using helpers Web::Helpers.
Inside config there is a file called redis.rb which is then loaded by reload_config. At least it should be. Calling on require_all inside my config.ru (top level) effectively loads the content of redis.rb, but inside my helper module it does not.
redis.rb contains assignments to configatron.redis (which should be accessible from any scope level right?)
Another troubling fact: if I change the path I give to require_all it raises an exception.
Also, if I write, for example, puts "Haaaaa!" inside the redis.rb, it is executed when loaded from config.ru but not when loaded from helpers.rb.
What could be the cause of this ? If it has to do with lexical scoping, then why is it impossible to actually execute statements such as puts or configatron.foo = 'hello' ?
Note that require_all and configatron are loaded in config.ru.
The solution is simply to user load_all instead of require_all.
The code would not load not because of some lexical scoping issue but because of that require_all does not load twice the same file. Therefore using load_all solves the problem.

Chaining autoloaded classes in Ruby

I have a class, User in user.rb that will be autoloaded as needed by the following statement:
autoload :User, 'models/user.rb'
This model is shared between a few different codebases (as a Git submodule, if that makes a difference). In one such codebase, I have a need to reopen the User class and add some methods. Where this gets complicated, for me at least, is that I need the resultant, extended class to be autoloaded in place of the original class.
Is there a pattern for chaining autoloaded classes in Ruby? Something like:
autoload :User, ['models/user.rb', 'extended_models/user.rb']
Or should I be using inheritance instead of monkey-patching? I'm open to suggestions. Thanks in advance.
Here's what I ended up doing: my main file autoloads the extended class, then the extended class autoloads the base class. Reopening the class triggers the second autoload. Although a little clumsy, this lets me keep my base classes pristine while preserving autoloading behavior. (Autoloading is a requirement for me because the database isn't available for Sequel ORM to discover the table schemas when the app first fires up.)
It looks like this:
main.rb:
autoload :User, 'extended_models/user.rb'
extended_models/user.rb:
autoload :User, '../models/user.rb'
class User
def self.from_omniauth(authhash)
# ...
end
end
models/user.rb:
class User < Sequel::Model
# ...
end
I have some helper functions that help me autoload directories of models and single models with relative paths, but this is the general idea.
If its shared between several codebases, it would probably be the best to make it a Module instead of a class.

Is there a way in Sinatra to separate helpers used in views and helpers used in routes?

All helpers in Sinatra can be accessed from both my HAML code and my routes. But I'm finding myself writing two completely separate sets of code for these two.
Ideally I'd like to keep them apart. Currently I have them in two different directories, but that seems kind of pointless. Can anyone suggest a way I can limit the potential damage caused by namespace collision?
At first I thought modules. From the Sinatra readme:
module FooUtils
def foo(name) "#{name}foo" end
end
module BarUtils
def bar(name) "#{name}bar" end
end
helpers FooUtils, BarUtils
But is there any point in actually doing this? Aren't they going to have to share the same namespace inside my Sinatra app?
Some tentative and not-entirely-satisfactory answers:
1) Methods defined in the same way as helpers but without the call to Sinatra::helpers don't appear to be accessible from views, but are accessible from within the Sinatra application block. That is:
require 'sinatra'
module Sinatra
module MyThing
# Helper classes and methods go here
end
helpers MyThing # <- leave this out if you don't want access from views
end
2) You can of course also have MyThingHelpers and MyThingLib modules, instead of just MyThing, to prevent namespace collision.
3) And you can put them in separate subdirectories.
I've yet to be convinced that these represent a comprehensive solution to my concerns. Time and testing will tell, I suppose, unless someone can give me a better answer here.

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?

How to mount a Sinatra application inside another Sinatra app?

I am trying to write a Sinatra application that groups components together (sort of like controllers). So for the "blog" related things, I want an app called Blog mounted at /blog. All of the routes contained in the Blog app would be relative to its mounted path, so I could simply define an index route without having to specify the mount path in the route.
I originally handled this by using a config.ru file and maping the routes to the different apps. The problem with this that I ran into, was that I was using various sinatra gems/extensions/helpers that needed to be included in all of the apps, so there was a lot of duplicate code.
How can I mount one sinatra app inside of another so that the routes defined in the app are relative to where the app is mounted? If this is not possible out-of-the-box, can you show a code sample of how this could be done?
Here's a simplified example of what it might look like:
class App
mount Blog, at: '/blog'
mount Foo, at: '/bar'
end
class Blog
get '/' do
# index action
end
end
class Foo
get '/' do
# index action
end
end
Take a look at https://stackoverflow.com/a/15699791/335847 which has some ideas about namespacing.
Personally, I would use the config.ru with mapped routes. If you're really in that space between "should this be a separate app or is it just helpful to organize it like this" it allows that, and then later you can still farm off one of the apps on its own without changing the code (or only a little). If you're finding that there's a lot of duplicated set up code, I'd do something like this:
# base_controller.rb
require 'sinatra/base'
require "haml"
# now come some shameless plugs for extensions I maintain :)
require "sinatra/partial"
require "sinatra/exstatic_assets"
module MyAmazingApp
class BaseController < Sinatra::Base
register Sinatra::Partial
register Sinatra::Exstatic
end
class Blog < BaseController
# this gets all the stuff already registered.
end
class Foo < BaseController
# this does too!
end
end
# config.ru
# this is just me being lazy
# it'd add in the /base_controller route too, so you
# may want to change it slightly :)
MyAmazingApp.constants.each do |const|
map "/#{const.name.downcase}" do
run const
end
end
Here's a quote from Sinatra Up and Running:
Not only settings, but every aspect of a Sinatra class will be inherited by its subclasses. This includes defined routes, all the error handlers, extensions, middleware, and so on.
It has some good examples of using this technique (and others). Since I'm in shameless plug mode I recommend it, even though I've nothing to do with it! :)
I just meet the same problems.
I found that whatever inclued extend register or rack middleware style use had problems.
You can use Ruby to solve this problems like that:
require "sinatra"
class App < Sinatra::Base
class << self
def define_routes(&block)
class_eval(&block)
end
end
end
App.define_routes do
get "/hello"
"hello world"
end
end

Resources