Class name conflict between Rails app and Rake - ruby-on-rails

I have a Document model in my Rails 4 app which starts like this:
class Document < ActiveRecord::Base
TYPES = [FileList, Article, VariantList, LinkedItemsList].collect(&:model_name)
...
end
The TYPES array contains a list of other models in my app and is used in a few places, for example to display a list of possible document types in one of my forms.
Now I'm writing a Rake task to automate some stuff and I get this error at line 2 in my Document model when I run the task:
NoMethodError: undefined method 'model_name' for Rake::FileList:Class
So apparently there's the Rake::FileList class which is being loaded first and I get a name conflict. Using ::FileList in my code doesn't help, I get the same error.
Any idea how to get around this without renaming my model?
UPDATE:
My Rake task definition looks like this:
namespace :store do
desc 'Some description'
task :my_task_name, [:a_parameter_name] => [:environment] do |t, args|
...
end
end
I think this is loading the environment correctly, as I'm calling (loading from DB) some other models before I get to Document and that is working correctly.

One think you can try is to give it an alias to FileList, at the end of you class definition:
class FileList < ActiveRecord::Base
...
end
FileListCopy = FileList
then in your rake access to FileListCopy.

When you're calling FileList from Document model, it tries to resolve this constant in the following order:
It tries to find the constant in the elements of the Module.nesting chain. In your case nesting is just [Document] and it does not contain FileList constant.
Then it tries to find constant in object's ancestors.
And finally the constant is looked up in Object.
Rake exposes its DSL globally by extending Object with Rake::DSL module. Thus, while using rake you're always getting FileList from the rake.
To overcome this issue you may move all your code into namespace. For example MyApp::FileList and MyApp::Document. In that case constant lookup algorithm will try to find constant in the elements of the nesting ([MyApp::Document, MyApp]). The second element of the nesting (MyApp) contain FileList constant. So, it would be resolved from MyApp module instead of Rake.
To avoid such kind of issues it's always a good idea to put your code into some namespace.

Related

Module method in Rails 6 YAML fixtures

One model in a Rails application I'm working on has a JSON field. This can be created in the fixtures with:
first_record:
name: "First Record"
metadata: <%= File.read("#{Rails.root}/test/fixtures/files/metadata/default.json") %>
...and so on. But, for some tests a value other than the default is needed in the metadata; this could be done by having separate metadata files for each record, or embedding JSON in the YAML file, but there are many records and there is much metadata. So, another option is to create a module in lib/modules with something like this:
module TestMetadataHelper
def create_metadata(args)
metadata = JSON.parse(File.read("#{Rails.root}/test/fixtures/files/metadata/default.json"))
args.each do |key, value|
metadata[key] = value
end
metadata.to_json
end
end
And, to make this accessible in tests, this goes into test_helper.rb:
ActiveRecord::FixtureSet.context_class.send :include, TestMetadataHelper
The, in the fixtures:
metadata: <%= create_metadata(field_to_change: "custom value") %>
Tests run perfectly well using this method, but there's one problem:
$ RAILS_ENV=test bundle exec rake db:fixtures:load
rake aborted!
NoMethodError: undefined method `create_metadata' for #<#<Class:0x00007feeba246958>:0x00007feeba246890>
So, how about fixing this by loading my module in config/environments/test.rb?
DEPRECATION WARNING: Initialization autoloaded the constant TestMetadataHelper.
Being able to do this is deprecated. Autoloading during initialization is going
to be an error condition in future versions of Rails.
Reloading does not reboot the application, and therefore code executed during
initialization does not run again. So, if you reload TestMetadataHelper, for example,
the expected changes won't be reflected in that stale Module object.
This autoloaded constant has been unloaded.
Please, check the "Autoloading and Reloading Constants" guide for solutions.
Having looked at that guide it's not entirely clear to me how best to handle this situation, so if anyone has any thoughts (e.g. how to fix this warning, or a better way to deal with the test requirements), please do comment.

Monkey patching a db model class in Rails with Mongoid causes weird behaviour

I am using a development script file to check out new possible ideas. Recently I tried to monkey patch MyDBObject from within that script file.
Assume an empty dev.rb file and add a monkey patch right in the top like so:
class MyDBObject
def test_function
'function works'
end
end
Starting up the pry console and loading the file yields random results.
First I received:
NoMethodError: undefined method `relations' for MyDBObject:Class
Later the script loaded, but I couldn't access the original class any longer:
undefined method `first' for MyDBObject:Class
I noticed that prepending the line:
MyDBObject
right before the monkey patching, the intended functionality is achieved.
This appears to be some sort of lazy loading of the class objects. Can somebody cast some light on this for me please?
Depending on the order in which source files are loaded, you'll either be redefining the entire class, or having your changes replaced.
I highly recommend giving this a read: http://www.justinweiss.com/articles/3-ways-to-monkey-patch-without-making-a-mess/ (TLDR - put your patch in a module and explicitly include it)

Ruby on Rails: colon in commands

When working with ruby on rails, it is common to use terminal commands like rake db:migrate or rails g devise:install. But what exactly does the : in these commands mean? In rake db:migrate is migrate a parameter or something else?
You can think of the colon as a namespace. Somewhere within Rails there is a rake task file that looks similar to this:
namespace db
task :migrate do...
....
end
end
It's a way to group related tasks together and prevent them from colliding with other tasks. This way you could potentially have devise:migrate, db:migrate, foobar:migrate, etc.
Like Philip explained in his answer when using rake the colon defines the seperator between namespaces/tasks
When using rails g(enerate) it's basically the same. The difference is that Rails generators aren't defined with rake's DSL, instead they're classes.
But to answer your initial Questions: The Colon works as seperator, in both cases, that's it.
In the code the only thing what it's important for is splitting up the string:
https://github.com/rails/rails/blob/4-0-stable/railties/lib/rails/generators.rb#L124
You can find some more infos about generators and how to create your one ones (which definetley will help you understanding the mechanics behind it) in the official Ruby on Rails Guides
//EDIT
Ok, let's take a closer look at the generator lookup process:
First of all there is Rails::Generators.invoke
It receives the namespaces passed on the CLI and split's it up (using the colon)
names = namespace.to_s.split(':')
then it get's the according class by passing the last part of the passed namespace (the actual generator name) and the remaining part joined with a colon again (the namespace path, in our case it's devise) to Rails::Generators::Base.find_by_namespace
if klass = find_by_namespace(names.pop, names.any? && names.join(':'))
This method again will join the base (namespace path) and name (generator name) and push it to an array:
lookups = []
lookups << "#{base}:#{name}" if base
after that it calls Rails::Generators.lookup which will lookup the classes to invoke for the called generator:
lookup(lookups)
which will again will call Rails::Generators.namepaces_to_paths
There's no big magic in that method, it'll simply return an array of two possible source paths for the invoked generator in our case these two are "devise/install/install" and "devise/install".
Whereby those aren't the actual paths rails will check, they are only the part's of it that are depend on the namespace:generator construct.
The lookup method will now take those two, let's call them subpaths, and check for files to require at the following places:
rails/generators/devise/install/install_generator
generators/devise/install/install_generator
rails/generators/devise/install_generator
generators/devise/install_generator
in our case the second path is the desired file, rails require's it and through that the inherited (more about that callback callback on Rails::Generators::Base will get called since Devise::Generators::InstallGenerator inherits from it.
This will add the Class to the subclasses array of Rails::Generators which is mapped to an Hash which will have the format { namespace => klass } and so rails is finally able to get the desired Generator Class
klass = namespaces[namespace]
and start it
klass.start(args, config)

problem accessing namespaced class in rake task

Given a rake task that references both a namespaced and non-namespaced model:
namespace :thing do
task :thingo => :environment do
Klass.first.some_method
Namespaced::Klass.first.some_other_method
end
end
Using ruby 1.9.2, rails 3.0.9, and rake 0.9.2, this yields an exception, like so:
undefined method 'some_other_method' for #<Klass:0x007fcfafbaa6e0>
Two things:
Why doesn't rails return the proper namespacing in the rake environment (in a debugger session), but it does in a console session?
Why does changing the order of reference work? (That is, if the environment is already calling "Namespaced::Klass" as "Klass", then calling "Klass" should fail with undefined method 'some_method' for #<Klass:0x007fcfafbaa6e0> right?
By the way, I've tried ::Namespaced::Klass.first.some_other_method
If the answer isn't simple, I'll put together a test app - let me know! :-)
First, some background on metaphor shear - two different kinds of namespaces:
Although Rake Namepsaces and Ruby Namespaces share the word Namespace, they are separate concepts. Rake namespaces are just organizing containers for Rake Tasks, not Ruby namespaces/modules. So code inside your thing:thingo rake task is actually executing at the top-level Ruby namespace.
Second: If Klass is a single class not in a namespace, you can reference it directly. If the class exists as Foo::Klass then you'll need to use the fully-qualified Foo::Klass reference unless the scope of the reference is already within the Foo namespace.
Because Rake namespaces aren't Ruby modules, you are not in the context of a Ruby namespace within your task. This is why Klass.some_method works if Klass isn't in a module.
If this doesn't explain the question, please post the class definition for Klass including any module/namespace membership.

ruby ::Module or just Module

I'm slowly making my way through the rails source, to get a better grip on ruby and rails in general. In the following rails class test_case.rb
the line is
class TestCase < ::Test::Unit::TestCase
and I was wondering if there is any difference with doing the following
class TestCase < Test::Unit::TestCase
It may seem trivial, but these things matter picking up a new language. The tests still run for ActiveSupport if I remove the leading :: so what does it do... :P
::Test ensures that you get a toplevel module named Test.
The latter case (Test::Unit::TestCase) doesn't ensure that Test is a toplevel module, it could be a class, for example. It means that most of the time, it'll work, but you could accidentally break it.
Imagine you have this code
module UserTesting
class Test # Details about a simple test
end
class TestCases < Test::Unit::TestCase
end
end
# => NameError: uninitialized constant UserTesting::Test::Unit
This would error out since your Test constant available to the class you are creating has no Unit constant in it. If you address it by :: this is like the leading slash in a path.
There is also a special case for using these - you can be evaluating your code in something else than the default root namespace, and there you actually need the double colon to address classes like ::Object (usually to monkeypatch them).

Resources