running a constructer from within a controller - ruby-on-rails

So I'm attempting to run a generator from within a controller
the idea being that i could generate a scaffold from within a rails application
for testing purposes ive created a small generator that creates an initializer with no real content. i've run this from within the shell (rails generate initializer)
lib/generators/initializer_generator.rb
class InitializerGenerator < Rails::Generators::Base
def create_initializer_file
create_file "config/initializers/initializer.rb", "# Add initialization content here"
end
end
but the problem comes in when i attempt to run the generator from a controller
class GeneratorController < ApplicationController
include Rails::Generators
include Rails::Generators::Actions
def index
generate(:initializer)
end
end
so this bring the response 'undefined method `behavior'
then i did some research knowing that rails generators are based apon Thor
and found that the behavior method is a thor method in the Thor::Actions modules
http://rubydoc.info/github/wycats/thor/master/Thor/Actions:behavior
So I changed the controller code to include that module:
class GeneratorController < ApplicationController
include Rails::Generators
include Rails::Generators::Actions
include Thor::Actions
def index
generate(:initializer)
end
end
Now the error is 'wrong number of arguments (3 for 0)'. Am very confused now about what might be going wrong.

Have you tried calling the class directly?
InitializerGenerator.new.initializer
This is an advantage of Thor over Rake, these are regular Ruby classes.
I had to set the destination_root inside my method, example:
def create_initializer_file
destination_root = Rails.root
create_file "config/initializers/initializer.rb", "# Add initialization content here"
end
I've only just started using generators so there might be a better way.
One issue I ran in to after this, which I have yet to solve is that Thor prompts for user input if the file already exists.

Related

Accessing namespaced class in gem vs Rails

I've worked with a couple of Ruby gems and also Rails. One thing I've never fully understood is why Rails required explicit class constant references for code defined in the /lib folder. In a ruby gem, I could create something like this:
lib/my_gem/custom_error.rb
module MyGem
class CustomError < StandardError
end
end
lib/my_gem/some_class.rb
module MyGem
class SomeClass
def initialize
raise CustomError
end
end
end
Whether it's an error, another class or whatever, as long as the calling class is in the same namespace as the referenced class, ruby would initialize the correct class CustomError in the case above. Moving to Rails, this is a different story and this code would result in an uninitialized constant error. In Rails I would have to raise MyGem::CustomError instead. Why is this the case? I assume it has something to do with autoloading. Is there a way around this or is this standard?

Create bare rails controller class

I'm trying to create clean controller based on ActionController::Base. That's what I try:
class MetalController
ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
include left
end
end
From Rails doc:
Shortcut helper that returns all the modules included in
ActionController::Base except the ones passed as arguments:
This gives better control over what you want to exclude and makes it
easier to create a bare controller class, instead of listing the modules
required manually.
My another controller inherits from MetalController :
class API::BaseController < MetalController
#.... my awesome api code
end
So this not work then i launch rails server:
block in <module:AssetPaths>': undefined methodconfig_accessor' for
MetalController:Class (NoMethodError)
Rails 4.1.0, Ruby 2.1.0
Update:
If i include ActiveSupport::Configurable
throws the errors:
_implied_layout_name': undefined local variable or method
controller_path' for MetalController:Class (NameError)
You need to inherit from ActionController::Metal:
class MetalController < ActionController::Metal
ActionController::Base.without_modules(:ParamsWrapper, :Streaming).each do |left|
include left
end
end

How does including a module work inside a controller?

If I do:
rails generate scaffold account/user username
I get a controller that looks like this:
class Account::UsersController < ApplicationController
def index
#account_users = Account::User.all
end
...
end
If I include the Account Module, then it looks like all the database calls don't need to be prefixed with "Account::". I.e.
class Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this works because I included the Account Module above
end
...
end
Now if I were to move my
controllers/account/users_controller.rb
file to:
controllers/admin/account/users_controller.rb
The file looks like this (note: I also corrected my routes file after this move):
class Admin::Account::UsersController < ApplicationController
include Account
def index
#account_users = User.all #this call does not work now
end
...
end
But I get an error saying "uninitialized constant Admin::Account::UsersController::User"
It looks like rails is trying to make a database call on the "User" model without the "Account::" module in front of it.
So how does including modules in controllers work? Why does this not work when I move my controller into a different file (and leave the model in the same location from the generated scaffold) but it works with the scaffold generated files? How can I fix this issue?
Resolving the name of a module is done relative to the current module. Try and change it to:
include ::Account
or
include ::Admin::Account
(depending on the module in which your User model is defined)
This will tell ruby to look in the global namespace for the module Account
I guess I didn't realize you can just explicitly require the path to the module you would like to include. I learned this after reading up on modules some more...
So adding an explicit call to "require 'account/user'" just outside the controller class makes it so including the module in the controller works.

Include Railtie initialization in seeds.rb in Rails 3.1

I have created a simple railtie, adding a bunch of stuff to ActiveRecord:
0 module Searchable
1 class Railtie < Rails::Railtie
2 initializer 'searchable.model_additions' do
3 ActiveSupport.on_load :active_record do
4 extend ModelAdditions
5 end
6 end
7 end
8 end
I require this file (in /lib) by adding the following line to config/environment.rb before the application is called:
require 'searchable'
This works great with my application and there are no major problems.
I have however encountered a problem with rake db:seed.
In my seeds.rb file, I read data in from a csv and populate the database. The problem I am having is that the additions I made to ActiveRecord don't get loaded, and seeds fails with a method_missing error. I am not calling these methods, but I assume that since seeds.rb loads the models, it tries to call some of the methods and that's why it fails.
Can anyone tell me a better place to put the require so that it will be included every time ActiveRecord is loaded (not just when the full application is loaded)? I would prefer to keep the code outside of my models, as it is code shared between most of my models and I want to keep them clean and DRY.
Putting the extend there just adds it to ActiveRecord::Base.
When a model class is referenced, via Rails 3.1 autoloading/constant lookup, it will load the class. At that point, it is pure Ruby (nothing magic) as to what happens, basically. So I think you have at least a few options. The "bad" option that kind of does what you want it to hook into dependency loading. Maybe something like:
module ActiveSupport
module Dependencies
alias_method(:load_missing_constant_renamed_my_app_name_here, :load_missing_constant)
undef_method(:load_missing_constant)
def load_missing_constant(from_mod, const_name)
# your include here if const_name = 'ModelName'
# perhaps you could list the app/models directory, put that in an Array, and do some_array.include?(const_name)
load_missing_constant_renamed_my_app_name_here(from_mod, const_name)
end
end
end
Another way to do it would be to use a Railtie like you were doing and add a class method to ActiveRecord::Base that then includes stuff, like:
module MyModule
class Railtie < Rails::Railtie
initializer "my_name.active_record" do
ActiveSupport.on_load(:active_record) do
# ActiveRecord::Base gets new behavior
include ::MyModule::Something # where you add behavior. consider using an ActiveSupport::Concern
end
end
end
end
If using an ActiveSupport::Concern:
module MyModule
module Something
extend ActiveSupport::Concern
included do
# this area is basically for anything other than class and instance methods
# add class_attribute's, etc.
end
module ClassMethods
# class method definitions go here
def include_some_goodness_in_the_model
# include or extend a module
end
end
# instance method definitions go here
end
end
Then in each model:
class MyModel < ActiveRecord::Base
include_some_goodness_in_the_model
#...
end
However, that isn't much better than just doing an include in each model, which is what I'd recommend.

Can't access model from inside ActionController method added by a Rails engine

I am developing a Rails engine to be packaged as a gem. In my engine's main module file, I have:
module Auditor
require 'engine' if defined?(Rails) && Rails::VERSION::MAJOR == 3
require 'application_controller'
end
module ActionController
module Auditor
def self.included(base)
base.extend(ClassMethods)
end
module ClassMethods
def is_audited
include ActionController::Auditor::InstanceMethods
before_filter :audit_request
end
end
module InstanceMethods
def audit_request
a = AuditorLog.new
a.save!
end
end
end
end
ActionController::Base.send(:include, ActionController::Auditor)
where AuditorLog is a model also provided by the engine. (My intent is to have "is_audited" added to the controllers in an application using this engine which will cause audit logging of the details of the request.)
The problem I have is that when this code gets called from an application where the engine is being used, the AuditorLog model isn't accessible. It looks like Ruby thinks it should be a class in ActionController:
NameError (uninitialized constant
ActionController::Auditor::InstanceMethods::AuditorLog)
rather than a model from my engine.
Can anyone point me in the right direction? This is my first time creating an engine and attempting to package it as a gem; I've searched for examples of this and haven't had much luck. My approach to adding this capability to the ActionController class was based on what mobile_fu does, so please let me know if I'm going about this all wrong.
Use ::AuditorLog to access the ActiveRecord class (unless you have it in a module or namespace, in which case you'll need to include the module name).

Resources