Create bare rails controller class - ruby-on-rails

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

Related

How can I extend my controller from installed Spree gem's controller?

I have spree gem installed successfully. I don't need spree_frontend. Here is the Gemfile
gem 'spree_core', '4.2.0.rc2'
gem 'spree_backend', '4.2.0.rc2'
gem 'spree_sample', '4.2.0.rc2'
gem 'spree_cmd', '4.2.0.rc2'
gem 'spree_auth_devise', '~> 4.2'
So I want to extend my ApplicationController from Spree's BaseController. Here is the code:
class ApplicationController < Spree::BaseController
include Spree::Core::ControllerHelpers::Order
end
But I get following errors:
uninitialized constant Spree::BaseController (NameError)
How can I extend my controller from installed Spree gem's controller?
The problem you're running into is that Spree::BaseController already inherits from ApplicationController; see https://github.com/spree/spree/blob/master/core/app/controllers/spree/base_controller.rb. This is to allow your ApplicationController to define things like current_user and similar basic functions before Spree sees it.
Declaring them the other way around as well creates a circular dependency, and the class loading fails as a result. Without changing Spree itself, the only fix is to do something else.
Instead, to have your controllers use Spree::BaseController as a superclass, first define ApplicationController in the more usual fashion e.g.:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
end
then invent a new abstract controller, for your own use, that inherits from Spree, e.g. let's name it StoreBaseController:
# app/controllers/store_base_controller.rb
class StoreBaseController < Spree::BaseController
include Spree::Core::ControllerHelpers::Order
# ...
end
This StoreBaseController can now be used in place of ApplicationController when defining more specific controllers. It works because it doesn't create a loop in the inheritance tree, which now looks like this:
Note: if you're also using the rails generator command to produce controllers or scaffolds from templates, be aware that the generator has ApplicationController hard-coded in the templates, so you'll need to amend them once created.
Is there any reason why you need to extend strictly ApplicationController?
I advise you alternative approach to create a new Base controller class, and then inherit all the children from it and leave ApplicationController to basic rails
app/controller/my_base_controller.rb
class MyBaseController < Spree::BaseController
def foo
# ...
end
end
app/controller/my_resources_controller.rb
class MyResourcesController < MyBaseController
def bar
# ...
end
end
As the errors states, Spree::BaseController is not defined within your app - it is defined in the spree-core gem. If you re-create the filepath to the base controller locally, that is app/controllers/spree/, and copy and paste the code from the controller into a local base_controller.rb, you can edit it and add custom functionality.
Note that it will still inherit from the ApplicationController, but you can place any of the code you wanted to put in the ApplicationController into here and have your classes inherit from Spree::BaseContoller and the effect will be the same.
hmmm, I tried what you want to do but I succeeded (?)
class PagesController < Spree::BaseController
include Spree::Core::ControllerHelpers::Order
end
in the console
2.6.5 :006 > pp PagesController.ancestors
[PagesController,
Spree::Core::ControllerHelpers::Order,
#<Module:0x00007fca27610410>,
Spree::BaseController,
Spree::Core::ControllerHelpers::CurrencyHelpers,
Spree::Core::ControllerHelpers::StrongParameters,
...
I'm using
ruby 2.6.5
rails 6.0.3.4
run bundle update after adding the your spree's gems in the Gemfile
So I think its the requiring or auto-loading problem
what's your rails version? 6? spree >= 4.1 should use rails >= 6
Does Spree::BaseController exist in rails console?
Is Bundler.require(*Rails.groups) in config/application.rb?
Does the gems included in the right group of the Gemfile? ex: spree gems are in :production group.
Does it have config.load_defaults 6.0 in config/application.rb?

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?

How to include a module in a class

A module in my gem is included in a class in another gem, which is extended by a custom class in a Rails app:
My gem:
module MyGem
def my_method
end
end
AnotherGem.send :include, MyGem
Another gem:
class AnotherGem
end
Class in Rails app:
class ClassInRailsApp < AnotherGem
end
Running this leads to the following behavior:
$ rails c
Loading development environment (Rails 5.1.4)
irb(main):004:0> MyGem.method_defined? :my_method
=> true
irb(main):005:0> AnotherGem.method_defined? :my_method
=> true
irb(main):006:0> ClassInRailsApp.method_defined? :my_method
NoMethodError: undefined method `my_method' for ClassInRailsApp:Class
How can I make sure my module is included before the class is extended?
EDIT:
I tried to directly include MyGem in ClassInRailsApp and the specified instance method is still not available. Could the issue be related to that?
In your thinking, you're just calling a method, e.g. #object.my_method. In reality, you're calling Class level method, e.g. Object.my_method, but have it defined as an instance level method. The correct way to do what you're trying would be Object.new.my_method, however, don't do that.
To call a method like this you'd have to define it as a method on the class. See this page, for a better understanding. Specifically the section "A Common Idiom" on how to define Class level methods via a module.

Auto-create the containing module of a class

In Rails you can create a model under app/foo/bar.rb, with bar.rb containing:
class Foo::Bar
def some_method
puts "I work fine"
end
end
If you try to do this in a pure ruby app you'd get a NameError: uninitialized constant Foo unless you've already initialized a module Foo.
What is Rails doing that allows it to create classes without first initializing their containing module? Is it possible to import this behavior through something like activesupport, or are we left to implement on our own?
Rails modifies the Class class to include a const_missing method which gets called when an undefined class is used. It then loads things to try and load the requested class.
The implementation of this in ActiveSupport is in lib/active_support/dependencies.rb.
actually model class created is extend to < ActiveRecord::Base

running a constructer from within a controller

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.

Resources