I am trying to work over a class using Ruby on Rails in order to create a simple controller. In that sense, I have a singleton and I need to refer routes to it. How is it possible?
The message I get:
The action 'foo' could not be found for Test::TestController
The controller file, inside a Test folder:
class Test::TestController < ApplicationController
class << self
def index
render json: {test:"Hello World!"}
end
def foo
render json: {response:"It works!"}
end
end
end
The routes file:
Rails.application.routes.draw do
namespace 'test' do
resources :test
end
get '/:id', to: 'test/test#foo'
end
Its not possible. And its not even a remotely good idea.
Rails controllers take their input in form of the request and env which encompasses things like server settings and anything the middleware has stuffed away as initializer arguments. They are not globals like in for example PHP.
The actions of a controller themselves don't actually take any arguments. So even if you could declare your actions as class methods you would have absolutely no context. And its actually pretty damn irrelevant since you would have to replace the entire router layer to even get it called.
Ruby does not even have real singleton classes either. If you want an object that can't be instantiated use a module (and no you can't make a module a controller in rails).
Related
I am trying to write a script/program that given a controller file name, can programmatically determine which routes are served by this controller. This should work for inheritance use cases as well. For example:
class UserController < UserBaseController
end
class UserBaseController
def update
# changes made here
end
end
Running the script given the UserBaseController should return something like: PUT users/:id
How can I go about doing this? I tried using rails routes. However, the routes map to the child class and not the parent class which is where the change was actually made.
Thank you!
I have some helpers that are defined on runtime that are specific for a single call, e.g. a single instance of a controller (the next call could have different helper methods). Is there a robust way to add a helper method to an instance of a controller and it's view only, without adding the helper to other instances and views of this controller?
To define a helper for ALL instances, you could use the .helper_method method, e.g.
class Article < ApplicationController
helper_method :my_helper
def my_helper
# do something
end
end
I digged around in the source code, and found the (fairly private looking) #_helpers method which returns a module that contains all helpers for this instance. I could now use some meta programming to define my methods on this module
def index
_helpers.define_singleton_method(:my_helper) do
# do something
end
end
But I don't like this approach because I'm using a clearly private intended method that could easily change in the future (see the leading _).
If I only needed the helper inside the controller instance only, I could just call #define_singleton_method on the instance directly, but this doesn't make it available to the view.
So I'm looking for an official "Rails way" to define a helper for a single instance of a controller and it's view, like Rails provides with it's class method .helper_method.
I'm not sure if there is an official Rails way of doing this.
You could create an anonymous module and extend from that. Since this solution uses pure Ruby, you'll have to extend both the controller and view.
before_action :set_helpers, only: :index
def index
# ...
end
private
def set_helpers
#helpers = Module.new do |mod|
define_method(:my_helper) do
# do something
end
end
extend(#helpers)
end
<% extend(#helpers) %>
I have a colleague that likes to pass off the controller into a service object. For example a controller method might look the following way:
class FooController < ApplicationController
...
def show
Foo.new(self).call
end
...
end
the service object looks like this then:
class Foo
attr_reader :controller, :resource_id
delegate :render, :params, :head, to: :controller
def initialize(controller, resource_id)
#controller = controller
#resource_id = resource_id
end
def call
resource = SomeActiveRecordModel.find(resource_id)
if resource
render json: resource.to_json
else
head :not_found
end
end
end
Somehow I feel that this is counterproductive and an instance of cargo-cult software engineering.
I would prefer to keep the service object completely separate from the controller. Dependencies would be passed into the service object's constructor, parameters would be passed into the service object as method arguments. Any result is simply returned from the method.
Sadly my colleagues are not exactly thrilled by this whenever I bring it up in a code review, which I in turn find relatively frustrating.
What are the pros an cons of the respective approaches? How can I argue my case better? Am I missing something here?
I suspect the answer is "it depends".
In the exact example you gave, I see no particular advantage and it creates a degree of obfuscation. Also, in general, I agree with you on keeping the service object separate from the controller.
However, there are times when I find myself passing the controller into the service object. For instance, when I have a lot of complex work to do in dynamically constructing a view.
I wrote a gem for Rails that extends ApplicationController with a certain method. This method parses the current URL and uses the result to do a lookup. It looks something like this (simplified):
#current_account = Account.where(subdomain => request.subdomains.first).first
I want to include a test in the gem that asserts that the subdomain is looked up correctly based on a given URL.
I am running into two problems trying to write the test:
1) Since i'm testing within a gem, there is no controller (or Rails app for that matter) so I don't actually know where to start (Unit test, Controller test?)
2) I have searched everywhere, but I cannot find a way to setup the request hash in Rspec for testing. I would expect I would be able to do something like request.url = 'account1.example.com'
Any help on how to setup a proper test for this situation on Rspec is highly appreciated
If you are doing a controller test, then the URLs are usually specified on the configuration, like:
config.action_controller.default_url_options = { host: 'www.test.host' }
that is, if you're testing this as a rails application.
To test that a method in an abstract class works, your best option is to create a Test Subclass, and test using that. Something like
class TestController < ApplicationController; end
and then do your specs around this controller, which should behave exactly as an ApplicationController
EDIT
This would be an example of what I am proposing:
class TestController < ApplicationController
def index
render text: 'fake page' #This is so the action does not fail
end
end
describe TestController do
it 'searches for the current account in the right subdomain' do
Account.should_receive(:where).with({subdomain: 'www'})
get :index
end
end
In models and controllers, we often use Rails macros like before_validation, skip_before_filter on top of the class definition.
How is this implemented? How do I add custom ones?
Thanks!
They're just standard Ruby functions. Ruby's flexible approach to syntax makes it look better than it is. You can create your own simply by writing your method as a normal Ruby function and doing one of the following:
putting it somewhere that's accessible by your controllers such as application.rb
putting it in a file and requiring it in.
mixing the code into a class via the Ruby include keyword.
That last option is great for model classes and the first option is really only for controllers.
An Example
An example of the first approach is shown below. In this example we add code into the ApplicationController class (in application.rb) and use it in the other controllers.
class BusinessEntitiesController < ApplicationController
nested_within :Glossary
private
# Standard controller code here ....
The nested_within provides helper functions and variables to help identify the id of the "parent" resource. In effect it parses the URL on the fly and is accessible by every one of our controllers. For example when a request comes into the controller, it is automatically parsed and the class attribute #parent_resource is set to the result of a Rails find. A side effect is that a "Not Found" response is sent back if the parent resource doesn't exist. That saves us from typing boiler plate code in every nested resource.
That all sounds pretty clever but it is just a standard Ruby function at heart ...
def self.nested_within(resource)
#
# Add a filter to the about-to-be-created method find_parent_id
#
before_filter :find_parent_id
#
# Work out what the names of things
#
resource_name = "#{resource.to_s.tableize.singularize}"
resource_id = "#{resource_name}_id"
resource_path = "#{resource.to_s.tableize}_path"
#
# Get a reference to the find method in the model layer
#
finder = instance_eval("#{resource}.method :find_#{resource_name}")
#
# Create a new method which gets executed by the before_filter above
#
define_method(:find_parent_id) do
#parent_resource = finder.call(params[resource_id])
head :status => :not_found, :location => resource_path
unless #parent_resource
end
end
The nested_within function is defined in ApplicationController (controllers/application.rb) and therefore gets pulled in automatically.
Note that nested_within gets executed inside the body of the controller class. This adds the method find_parent_id to the controller.
Summary
A combination of Ruby's flexible syntax and Rail's convention-over-configuration makes this all look more powerful (or weirder) than it actually is.
Next time you find a cool method, just stick a breakpoint in front of it and trace through it. Ahh Open Source!
Let me know if I can help further or if you want some pointers on how that nested_within code works.
Chris
Chris's answer is right. But here's where you want to throw your code to write your own:
The easiest way to add Controller methods like that is to define it in ApplicationController:
class ApplicationController < ActionController::Base
...
def self.acts_as_awesome
do_awesome_things
end
end
Then you can access it from individual controllers like so:
class AwesomeController < ApplicationController
acts_as_awesome
end
For models, you want to reopen ActiveRecord::Base:
module ActiveRecord
class Base
def self.acts_as_super_awesome
do_more_awesome_stuff
end
end
end
I personally would put that in a file in config/initializers so that it gets loaded once, and so that I know where to look for it always.
Then you can access it in models like so:
class MySuperAwesomeModel < ActiveRecord::Base
acts_as_super_awesome
end