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.
Related
I have a model named 'Task' in my project.
I upgraded from Rails 3.0 to Rails 3.1 and now I receive the following error. My code hasn't changed.
>> Task.new
WARNING: Deprecated reference to top-level constant 'Task' found at: /Library/Ruby/Gems/1.8/gems/rake-0.8.7/lib/rake.rb:2470:in `rakefile_location'
Use --classic-namespace on rake command
or 'require "rake/classic_namespace"' in Rakefile
ArgumentError: wrong number of arguments (0 for 2)
from (irb):1:in `initialize'
from (irb):1:in `new'
from (irb):1
I'm scared I've called my model something that should have been reserved, what should I do for best practice? Refactor and change the name? Or something else?
Update: I tried it's suggestion of updating the Rakefile, but this did not work.
In the end, it turns out 'Task' has been a reserved word for a long time. I used text mate's find and replace to do a refactor, and created migrations to update the database. It only took about an hour, and I feel it was worthwhile to avoid future problems.
I dug a bit deeper and found this out:
When my specs auto-load the Task constant, Rake's const_missing (source code) kicks in, issues that warning (source code) and returns Rake::Task. Which makes my specs fail because I'm now testing that instead of my model.
I then get a lot of these:
NoMethodError:
undefined method `enqueue' for Rake::Task:Class
Well, of course it doesn't implement enqueue — that's not my model!
So, in short, Rake tells me not to use their top-level Task (even though I didn't mean to), and provides me with a different constant, effectively breaking the auto-loading in Rails!
There's only way around that — I had to manually require 'task' in the spec. Now it's all ponies and rainbows.
Dear Jim Weinrich, if you read this: Next time you declare something deprecated, please ensure that you only warn people about this when they actually use a deprecated API!
Another way to handle this is to get rid of rake's deprecation warning. In your spec_helper, before any activity that would reference the model, do this:
# Rake defines a const_missing that, if you reference any of several
# top-level constants, issues a deprecation warning and then defines
# it. Since Rake defines it, rail's own const_missing doesn't
# happen, and our own class doesn't get loaded. The workaround is to
# remove rake's const_missing.
class Module
def const_missing(*args)
rake_original_const_missing(*args)
end
end
We could have renamed our model, but that was more work than this. This, however, might break with future versions of rake. Choose your poison.
This is a 2nd part to the following question:
Where to put model "utility" functions in Ruby on Rails
Problem is, I need access to these utility functions from a rake task as well. Using the accepted technique in in the other thread, I get an "undefined method" error when accessing my model from a rake task.
What is the best way to fix this?
Thanks
You probably need to define your rake task as dependent on the Rails environment:
task :my_task => :environment do
# Will load Rails stack before executing this block
MyModel.foo
end
The default behavior is to load almost nothing, so you won't have access to your models unless you ask for it.
I successfully used em-dir-watcher as shown in its example.rb file from the command line, but I found myself needing to call a method from another class and so I opted to go the script/runner route instead, invoking rails.
I copy/pasted the example code and placed inside of a self.methodName and encountered this error:
myapp/vendor/rails/railties/lib/commands/runner.rb:48: undefined method `watch' for EventMachine:Module (NoMethodError)
Even if I run something totally simple like:
def self.watcher
EM.run {
dw = EMDirWatcher.watch '.' do |paths|
paths.each do |path|
puts path
end
end
puts "Monitoring"
}
end
and invoke script/runner:
script/runner "require 'rubygems'; require 'em-dir-watcher'; Myclass.watcher"
I still get the error. What am I doing wrong?
After spending another day trying to sort through the stack, I've concluded that there's some inherently weird interaction between em-dir-watcher and script/runner. After moving over to another eventmachine-based gem that could do the same task (directory_watcher), everything worked great.
I know that there are performance and feature tradeoffs between the two gems, but for my purposes, I don't notice a difference.
Today, I use the factory_girl instead of the rails fixtures, but i get a problem:
After I run the command "spec spec" done, the data resets to the fixtures, who can tell me the answer?
thank you!
If you intend to use both factories and fixtures in your project and not running them through the rake tasks: eg rake spec, you will need to make sure that you are doing removal of the values from the db by hand. its likely what you are doing is just grabbing an old record in the database and you think the data is being reset. You can verify this by using puts inside your spec to trace the number of records in the db.
puts MyRecord.count
You can clear values in an after or before block.
before(:each) do
Factory(:my_model)
end
after(:each) do
MyModel.delete_all
end
if you intend to use this model or factory in other spec files, you can add these to global before and after blocks in the spec helper.
In order to override the table_exists? method in the Rails PostgreSQL adapter I have tried the following in an initializer file:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def table_exists?(name)
raise 'got here'
end
end
This will raise the the following error:
uninitialized constant ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
I believe this would have worked in previous versions of rails and I even found a small plugin that did something like this in Rails 2.3.6. Also I only encounter this error when I am trying to run a rake task like db:migrate and not when I start my application server.
Could someone show me the right way to do this and / or explain why PostgreSQLAdapter doesn't seem to be loaded when I am in an initializer file?
Instead of config/initializers, place that code in lib/ folder.
While this means that the active_record is loaded after the rails initializers, which is unusual. I ll update this with more detail, once I am done investigating the whole flow. If you want some more details about the rails 3 initialization process, check out this link:
http://ryanbigg.com/guides/initialization.html
I had success by moving this code into a Rails plugin. It is a little bit more overhead, but it is working consistently when I run rails s and when I run rake db:migrate.
I just followed the rails guide page on the topic and ran
rails generate plugin rails_patches --with-generator
and moved my init.rb file into rails as recommended.
~vendor/
`~plugins/
`~rails_patches/
|~lib/
| `-rails_patches.rb
|~rails/
| `-init.rb
|+test/
|-install.rb
|-MIT-LICENSE
|-Rakefile
|-README
`-uninstall.rb
I put this code in init.rb:
require 'rails_patches'
I put this code in rails_patches.rb:
ActiveRecord::ConnectionAdapters::PostgreSQLAdapter.class_eval do
def table_exists?(name)
raise 'got here'
end
end
This now behaves as I expected.