How to organize models in Sinatra? - ruby-on-rails

I have a tirable orgnizing my Models in a Sinatra project.
Let's say I have 2 models: Post and Comment, nn Post model, I have to call Comment model. And now I have <class:Post>': uninitialized constant Comment (NameError).
I know its an issue in ordering the requiring for the models, but what about if I have lots of models? What's the Rails way in requiring models, etc.?
UPDATE
I use this code to auto_load my models in Sinatra/Rack/Grape applications. This code should be at the top of your code ie in the boot file.
models = File.join(File.dirname(__FILE__), 'app', 'models') # path to your models
$LOAD_PATH << File.expand_path(models)
# Constent Missing for requiring models files
def Object.const_missing(const)
require const.to_s.underscore
klass = const_get(const)
return klass if klass
end

You should put all of your models in a folder, such as lib in your application, then you can add this to the top of your Sinatra app file:
$: << File.dirname(__FILE__) + "/lib" # Assuming app.rb is at the same level as lib
require 'post'
require 'comment'
You should organise your code so that you do not call other models until all model declarations are loaded.

The Rails way is based on a very nice Ruby feature: const_missing. You could write your const_missing method or looking around the web for a solution with const_missing and sinatra.

no prob when I tried this
a Comment if it is in a method of Post shouldn't be actually evaluated
there must be some circumstance triggering the NameError
don't call Post in the body of the class declaration
load all the model files per the first commenter's suggestion
shouldnt be having the same reference troubles as Java per se
in a dynamic lang like Ruby

Related

Zeitwerk and Modules Nested in Classes

I'm having some trouble switching from the classic autoloader to Zeitwerk with a Rails app that's seen the light of day back in Rails 3 days – so there's some crust there.
Some model code has been extracted to modules and these modules are nested in the model class (which acts as the namespace):
# app/models/donation
class Donation < ApplicationRecord
(...)
end
# app/models/donation/download
class Donation
module Download
def csv
(...)
end
end
end
The modules are then used on the fly when needed:
donation = Donation.find(...)
donation.extend(Donation::Download).csv
Since the subdirs in app/models are not added by default, it's done explicitly in application.rb:
Dir[
"#{config.root}/app/models/*/"
].then do |paths|
config.autoload_paths += paths
config.eager_load_paths += paths
end
The eager_load_paths are required by Zeitwerk (as per the Rails guides), however, Zeitwerk doesn't seem to like this constellation:
% rails zeitwerk:check
Hold on, I am eager loading the application.
expected file app/models/donation/download.rb to define constant Download
Strange, because Download is defined there. Any idea what's going on here and how best to refactor things to work with Zeitwerk?
Thanks for your hints!
Hmmm, that should work out of the box, looks like a regular setup to me.
Since app/models is in the autoload paths, Donation and Donation::Download are going to be autoloaded just fine, no custom configuration is needed.
If they do not, the app has to be doing something funky. We could debug it.

Make Rails autoloading/reloading follow dynamic includes

Context
I want to add some admin specific code to all models via concerns that are automatically included. I'm using the naming convention MyModel => MyModelAdmin and they're in the standard Rails directory app/models/concerns/my_model_admin.rb. Then I can glob over all of them and do MyModel.include(MyModelAdmin).
Issue
Dynamic includes work fine, but when changing the concern in development Rails doesn't reload it properly. In fact the concern seems to get removed.
Reproduction
app/models/my_model.rb
class MyModel
end
app/models/concerns/my_model_admin.rb
module MyModelAdmin
extend ActiveSupport::Concern
def say
"moo"
end
end
config/initializers/.rb
MyModel.include(MyModelAdmin)
So far so good, MyModel.new.say == "moo".
But now change say to "baa" and you get NoMethodError undefined method 'say'.
Notes
I tried a number of things that didn't help:
require_dependency
Model.class_eval "include ModelAdministration"
config.autoload_paths
Using another explicit concern in ApplicationModel with an included hook that includes the specific concern in each model.
ActiveSupport.on_load only triggered on Base not each model.
Does this mean Rails can only autoload using static scope? I guess Rails sees the concern change, knows the model has it included, reloads the model but the static model definition doesn't have the include so the concern goes missing and stops being tracked. Is there a way to force Rails to track dynamically included modules?

Rails 3 trouble with namespaces & custom classes (uninitialized constant)

I have a file in my Rails 3.2.11 project called app/queries/visible_discussions.rb which looks like the following:
class VisibleDiscussions
...
end
I'd like to namespace the query so that I can call it using something like Queries::VisibleDiscussions so I tried to do the following:
module Queries
class VisibleDiscussions
...
end
end
However, I'm getting a uninitialized constant Queries (NameError) when I try to call Queries::VisibleDiscussions from the rails console.
Any ideas?
if you add lib to your autoload_paths then it will respect the namespacing under lib - lib/query/visible_discussions.rb
or create a new dir under app - say src and then nest your code there - app/src/query/visible_discussions.rb
i would use the 3rd style in your post for either of these, i.e.
module Query
class VisibleDiscussions
...
end
end
both of these solutions are annoying to me, there might be a way to tell rails to namespace directories under app, but i have no clue how it would be done
Rails needs to know what directories to load (a part from the defaults). Try:
#config.application.rb
config.autoload_paths += %W(#{config.root}/queries)

Rails include module in model trouble

I have module in /lib/models/scopes.rb
module Models
module Scopes
extend ActiveSupport::Concern
...
end
end
I'm trying to include it from model:
class User < ActiveRecord::Base
include Models::Scopes
end
And getting error:
NameError: uninitialized constant User::Models
How to solve this trouble? Maybe it`s wrong to keep this types of files in /lib?
Environment:
Rails v3.1
Ruby v1.9.3
Rails doesn't require files in the lib directory automatically, but you can add to the autoloaded paths in config/application.rb:
config.autoload_paths += %W(#{config.root}/lib)
Restart the server to pick up the new settings.
This will now load the file automatically when the module name is first used. In development mode, you might want to reload the module after every change in order to see the changes without restarting the server. To do that, add it as an eager load path instead:
config.eager_load_paths += %W(#{config.root}/lib)
The scope shouldn't be a problem as long as you don't have a Models class or module within User or anywhere else.
when you define your class, you're "opening" a new scope. So when you do Models::Scopes, ruby is looking for User::Models::Scopes. You can fix this by using ::Models::Scopes, the :: telling ruby to look in the global scope.
FYI: I'm not sure about the terms I used or even if my train of thought if correct; but the solution should be good anyway. I'd think Ruby would try for ::Models::Scope after failing to find User::Models::Scope, but it doesn't.. Maybe there is a User::Models scope defined somewhere? Anyway, as you can see, I'm not yet familiar with those. You might want to dig on the subject if that interests you

Loading ActiveRecord models in the proper order outside of a rails app

How do I load/require my activerecord models in the proper order outside of a rails app. I have many STI models and I am getting an uninitialized constant exception.
$:.push File.expand_path("../../../app/models", __FILE__)
require "active_record"
Dir["#{File.expand_path('../../../app/models', __FILE__)}/*.rb"].each do |path|
require "#{File.basename(path, '.rb')}"
end
I have a lot of jobs that I need to run with resque and I would rather not have my rails app load everytime and be deployed to all of the worker machines
EDIT: One point to clarify as well. There are two projects a Rails project and a project that is a rails engine which contains my models. I dont load the rails engine itself with my resque jobs I just use the snippet above in a separate class to load active record on the models. This always worked until I added some STI models which because of the naming caused the children to attempt to be loaded before the parent. The rails engine project loads just fine in the rails project no issues there this is just because I am trying to use active record outside of a rails project.
A very simple solution if you don't want to autoload is to require the base class in the children classes. Explicitly requiring dependencies is a good thing. :)
app/models/profile.rb
class Profile < ActiveRecord::Base
end
app/models/student.rb
require 'models/profile'
class Student < Profile
end
app/models/teacher.rb
require 'models/profile'
class Teacher < Profile
end
Models will be autoloaded on their first mention. So just name them somewhere in a proper order (say, in config/initializers/load_order.rb):
Product
LineItem
Cart
and check if it helps.
I fixed my issue. There may be a better way but this does it for me.
basedir = File.expand_path('../../../app/models', __FILE__)
Dir["#{basedir}/*.rb"].each do |path|
name = "#{File.basename(path, '.rb')}"
autoload name.classify.to_sym, "#{basedir}/#{name}"
end

Resources