Skip to content

Instantly share code, notes, and snippets.

@jdickey
Last active August 29, 2015 14:17
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jdickey/3fe83f05b74bbb931b10 to your computer and use it in GitHub Desktop.
Save jdickey/3fe83f05b74bbb931b10 to your computer and use it in GitHub Desktop.
Simply subtle namespacing clusterfox FIXED

There once was a project that made extensive use of Pivotal's "Unbuilt Rails Dependency" pseudo-Gem architectural feature. After realising that that was far more limited in its applicable use cases than hoped, efforts were made to claw those back into namespaced classes within the main Rails app.

This was generally successful for a while. The use-case actions/service layer and one of the two core entities, Users, worked Just Fine. When work began on the Post entity, however, increasing forward progress was abruptly halted by a weird error.

Given the files spec/entities/post_spec.rb and app/entities/post.rb as below, running bundle exec rspec spec/entities/post_spec.rb gives the following output

Coverage report generated for RSpec to /Users/jeffdickey/src/rails/meldd/new_poc/coverage. 377 / 707 LOC (53.32%) covered.
Coverage = 53.32%. Sending report to https://codeclimate.com for branch post-entities... done.
/Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:495:in `load_missing_constant': Unable to autoload constant Post, expected /Users/jeffdickey/src/rails/meldd/new_poc/app/entities/post.rb to define it (LoadError)
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:184:in `const_missing'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:526:in `load_missing_constant'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:184:in `const_missing'
	from /Users/jeffdickey/src/rails/meldd/new_poc/spec/entities/post_spec.rb:5:in `<module:Entity>'
	from /Users/jeffdickey/src/rails/meldd/new_poc/spec/entities/post_spec.rb:4:in `<top (required)>'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `load'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1226:in `block in load_spec_files'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `each'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/configuration.rb:1224:in `load_spec_files'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:97:in `setup'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:85:in `run'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:70:in `run'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/lib/rspec/core/runner.rb:38:in `invoke'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/rspec-core-3.2.2/exe/rspec:4:in `<top (required)>'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/bin/rspec:23:in `load'
	from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/bin/rspec:23:in `<main>'

This Should Be an Evolution, Not a Revolution (Or a Devolution)

Bear in mind that Post is the second entity to be given this treatment. There is a corresponding Entity::User entity that Works Just Fine, including running its top-level RSpec specs as bundle exec rspec spec/entities/user_spec.rb.

FIXED

As i describe in a comment below. Major thanks to @bradstewart for his patient prodding that finally "[knocked] my mental rust loose".

# app/entities/post.rb
module Entity
class Post
def initialize(attributes)
end
end # class Entity::Post
end
# spec/entities/post_spec.rb
require 'spec_helper'
module Entity
describe Post do
end # describe Entity::Post
end
@jdickey
Copy link
Author

jdickey commented Mar 21, 2015

Now, if I add after line 3 the line

require_relative '../../app/entities/post'

it works as intended. However, the line

require 'entities/post'

does not. More WTFery.

I say "More WTFery" because the entities directory should be autoloaded, as the Rails doc says all subdirectories of app are autoloaded unless otherwise specified in config/application.rb. If it were not, the currently-existing specs for Entity::User couldn't work, right?

@bradstewart
Copy link

I'm just gonna start guessing. As you stated, Rails autoloads everything in subdirectories of app. But it expects the constants defined in those subdirectories to be top level; e.g. app/models defines classes like User not Model::User. So Rails goes off looking for Post in its autoload paths which it doesn't find because that doesn't actually exist, Entity::Post does.

class Entity::Post; end

and

module Entity
  class Post; end
end

Are not identical, they lookup constants differently. I'm guesing post_spec.rb goes looking for plain old Post which doesn't exist in an autoload path while user_spec.rb goes looking for Entity::User which exists in the autoload paths. I think it would pick it up correctly if Entity::Post were defined in app/entities/entity/post.rb.

@bradstewart
Copy link

It probably has something to do with how Rspce.describe works as well.

@jdickey
Copy link
Author

jdickey commented Mar 21, 2015

The following was my response to @bradstewart on the Reddit post; included here for completeness, as I want to encourage this to be the main forum for discussion.



Actually, I tried that with identical results; it made no difference whether I had

module Entity
  class Post
  end
end

or

class Entity::Post
end

I still got the same error.


As to "here or the Gist", I was going to let the responders decide; I can see arguments for either forum. Given your stated confusion, though, I'm going to ask that this be continued on the Gist for future participants. Thanks.


I'm still troubled by the require vs require_relative bit, as it implies that Rails "knows" where it should go find the file, but when you specify the path-independent name using require, all hell breaks loose.


@jdickey
Copy link
Author

jdickey commented Mar 21, 2015

At first I, too, believed this to be an issue with RSpec rather than with Rails proper. But IMO this Rails-console session argues against that:

$ bundle exec rails c
Loading development environment (Rails 4.2.0)

Frame number: 0/5
[1] pry(main)> ActiveRecord::Base.establish_connection;
[2] pry(main)> p = Entity::Post.new
NameError: uninitialized constant Entity
from (pry):2:in `<main>'
[3] pry(main)> require 'entities/post'
LoadError: cannot load such file -- entities/post
from /Users/jeffdickey/src/rails/meldd/new_poc/vendor/ruby/2.2.0/gems/activesupport-4.2.0/lib/active_support/dependencies.rb:274:in `require'
[4] pry(main)> require_relative 'app/entities/post'
=> true
[5] pry(main)> p = Entity::Post.new
=> #<Entity::Post:0x007ff89c4a1ec8>
[6] pry(main)> exit
$ 

We still have the problem with require; why?

@bradstewart
Copy link

So I got busy and forgot about this, any progress?

As far as I know, require goes looking in your system's Ruby load paths for a dependency with the specified name. Rails adds project-level files to your "system" paths in order to require the project's files, and it does so according to its own autoloading rules, not necessarily file paths. So I'll bet you can require 'post'and it will load just fine.

@jdickey
Copy link
Author

jdickey commented Mar 30, 2015

@bradstewart That does, indeed, seem to work. I've been off on an epic quest for the last week-plus and just happened to notice I had this tab open as I was about to shut down (at 02.30 on a long Monday night/Tuesday morning).

I was entirely too fixated on requiring entities/post when a simple require 'post' does indeed work Just Fine. Thanks for knocking my mental rust loose.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment