I have a task in application_rake. The lines that matter are:
family = Family.find(114)
SysMailer.deliver_feedback_memo(emails_str,"escorter", family)
In the SysMailer model i try to write the name of the family like this:
puts family.name
name is an attribute of family (for sure!!!)
But when I run:
rake the_task
I get an error:
rake aborted!
missing attribute: name
Why? is there a problem to send an object from rake to a model?
UPDATE:
After struggling with the issue, I tried not to pass the object, but pass only the family.id to the model like this:
SysMailer.deliver_feedback_memo(emails_str,"escorter", **family.id**)
(The asterisks are just to show the change).
Then in the model I created a method that takes this argument:
def some_method(emails, type, **family_id**)
family = Family.find(family_id)
puts family.name
And it works.
So my question remains. Is there a problem to pass an object with rake?
There definitely shouldn't be a problem passing objects via method calls -- even if from a rake task.
If the family.name statement was actually what was failing I'd expect you'd see a message more like:
undefined method :name for #<Family>
So it sounds like you may be looking at the wrong line of code (i.e. it may be the fault of something else in the code). A missing attribute error sounds more like a mass-assignment problem.
Have you tried running the rake task with the --trace option?
rake the_task --trace
This may help identify the problem, either way.
Related
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.
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)
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 get an error whenever I try to use the function gets within a rake task. Is there a way to make it work?
The error says, "no such file or directory - (rake task name)"
The problem is that Kernel#gets (which is what you're calling if you just use gets by itself) assumes you're pulling from a file named by the arguments passed to Rake. That means gets tries to return the content of a file called [rake-task-here], which almost certainly doesn't exist.
Try STDIN.gets.
I don't think that you should be using gets in a rake task, if you need to get input from the command line you probably should pass it in as a parameter, but if you post some code that is not working then I am sure you will get a better answer.