I am writing a Rails application that has a REST API only (no web interface or such). I need to check if the requests are being made with the right parameters and return different error codes if not. For example, all my API endpoints require a user_access_token, and a client_id. Some other endpoints require other different parameters to be present.
In all the controllers, I have the code that does this checking, but the code is getting duplicated, and there are many if-conditions that can be extracted out and put somewhere else. So I thought of adding a before_filter in my ApplicationController that does this checking. I defined a hash that contains all the endpoint to required_params mapping, and this filter runs before the control passes to the actual controller in question.
But for some endpoints, it is getting a little complicated because some parameters are required if some other parameters are present, and in some cases one of two parameters is required. So now I am wondering if there is a better way of doing this.
Am I doing it right? Is there a better/standardized way of doing this? Or some gem that does this?
I would have to see some code to understand the context. But it essentiallys sounds like you have your base ApplicationController that all of your other controller's inherit from. The thing that varies is the parameters that you expect (except for user_access_token and client_id which always need to be supplied). Since you are using inheritance you could define a method in your ApplicationController that contains a list of which parameters you expect and then in your subclasses override the method to check add other params. Your base controller will be responsible for doing the actual validation but the subclasses will get a chance to override the required parameters.
class ApplicationController < ActionController::Base
before_filter :validate_params
protected
def required_params
[:user_access_token, :client_id]
end
def validate_params
unless (require_params - params.keys).count.zero?
# do something
end
end
end
class AnotherController < ApplicationController
protected
def required_params
p = super + [:email, :password]
p = p + [:another, :and_another] if some_condition?
p
end
end
Essentially you would let the subclass decide if it needed to add any additional required parameters. Of course I don't know what your exact context is, but hopefully this will help you in the right direction. Do some research on polymorphism when you get the chance :-)
Related
The question is what do you think about this pattern?
Problem:
You've got controller with index action, and this action is huge.
Action is full of ActiveRecord chaining and maybe some computations with records.
When you are adding new methods controller is getting bigger.
I've heard about "skinny controller fat model", and I'm just what? My models are already fat, are you crazy?
I've heard about service objects, they are not very usable as for me.
result = SomeService.new(params, request, flash, current_user).call
After such service object you could try:
result = SomeService.new(controller).call
# or
result = SomeService.new.(controller)
And what to do with returning error statutes from that service? As answered below, exceptions.
So, you need to create exceptions class, throw it, catch it and only then render something or make redirect.
Here is the pattern from subject:
# controllers/some_controller.rb
class SomeController < OtherController
before_actions
include Index
include Show
def create_update_and_destroy
# small methods have no reason to leave controller
end
private
def common_private_method
end
end
# controllers/some_controller/index.rb
module SomeController::Index
def index
# code here
end
private
def index_do_some_stuff
# this method is prefixed by "index" to avoid name collision
end
end
Yes, there is some_controller.rb and some_controller directory with actions as files.
Nobody in OOP likes prefixes and if your method has well explaining not short name - prefix is not necessary.
In my opinion, this is the most simple and obvious way. Just take fat code and split to modules!
What do you think?
Explainations about why I have many code:
In one project I have view, it requires records from several models, some related, some not related.
So I had started to write many scopes in models, time passed, I realized that this is wrong approach.
Different actions required very specific records selections, only action should know about such specifics. And I had scopes named "for_index", "for_show".
Now I'm creating module Index with metod index, and all record fetching and computing code is splitted into private methods right in place.
In other project I have API. Specific endpoint is returning specific deep nested json, several models are fetching. I already know that creating scopes in model for one specific endpoint is bad idea, so I'm splitting code amoung private methods. One action and five private methods for it. And next five public methods and 25 private? In single controller?
Combining form objects with service objects and other patterns can make your model and controller thin. You can also add an ActiveResource::Errors object in your service object to collect errors.
Here's an example using user's input. Customize it according to your specification
class ProductForm
...
def save
if service_object.call
self
else
append_errors(service_object)
false
end
end
def service_object
#service_object ||= ProductCreationService.new(params)
end
def append_errors object
errors.append object.errors # just for simplicity
end
end
In your controller
def create
#product = ProductForm.new params
if #product.save
...
else
...
end
end
Controller actions should be straight forward and as short as possible. Complexities inside these actions can be lessen with other design patterns.
Actions that collects data can be abstracted by using query/finder objects.
Here's a rough example
class DashboardQuery
attr_reader :options
def initalize(options = {})
#options = options
end
def branches
#branches ||= Branch.all
end
def branch_count
#branch_count ||= branches.count
end
end
# Usage
#dashboard = DashboardQuery.new(params)
#dashboard.branch_count
#dashabord.branches
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 have an app that has users whose profiles are accessible via site.com/username. When choosing a username, I make an AJAX call to a method in my UsersController to make sure the username is available (and check on the back end as well when submitted). I now want to add groups that will also be accessible through site.com/groupname. Since group and user names cannot collide, whatever controller method that responds to the AJAX call will need to check both so the check_username_available and check_groupname_available methods will do the exact same thing. What's the best practice / Rails way to handle this since I don't want to replicate code in both UsersController and GroupsController?
Having a method for each controller seems a bit redundant, even if the functionality is pulled out to a helper, since there will still be two routes that do the same thing. Having a separate controller solves the problem too but not sure this is good Rails practice.
code that is reused can be shared via a module
class UsersController < ActionController::Base
include NameUniqueness
end
class GroupsController < ActionController::Base
include NameUniqueness
end
module NameUniqueness
protected
def check_name
# implementation here
end
end
both controllers will now have access the check_name instance method.
DanPickett's answer is great.
Another choice is to make a class method in the user model and just call it from each controller. Since this name checking seems like a job for the model, that's what I would do.
class User
def self.check(stuff) ...
I am in the middle of migrating my application from using subdirectories for userspace to subdomains (ie. domain.com/~user to user.domain.com). I've got a method in my user class currently to get the "home" URL for each user:
class User
def home_url
"~#{self.username}"
# How I'd like to do it for subdomains:
#"http://#{self.username}.#{SubdomainFu.host_without_subdomain(request.host)}"
end
end
I'd like to update this for subdomains, but without hardcoding the domain into the method. As you can see, I am using the subdomain-fu plugin, which provides some methods that I could use to do this, except that they need access to request, which is not available to the model.
I know it's considered bad form to make request available in a model, so I'd like to avoid doing that, but I'm not sure if there's a good way to do this. I could pass the domain along every time the model is initialized, I guess, but I don't think this is a good solution, because I'd have to remember to do so every time a class is initialized, which happens often.
The model shouldn't know about the request, you're right. I would do something like this:
# app/models/user.rb
class User
def home_url(domain)
"http://#{username}.#{domain}"
end
end
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# ...
def domain
SubdomainFu.host_without_subdomain(request.host)
end
# Make domain available to all views too
helper_method :domain
end
# where you need it (controller or view)
user.home_url(domain)
If there is such a thing as a canonical user home URL, I would make a configurable default domain (e.g. YourApp.domain) that you can use if you call User#home_url without arguments. This allows you to construct a home URL in places where, conceptually, the "current domain" does not exist.
While molf's answer is good, it did not solve my specific problem as there were some instances where other models needed to call User#home_url, and so there would be a lot of methods I'd have to update in order to pass along the domain.
Instead, I took inspiration from his last paragraph and added a base_domain variable to my app's config class, which is the set in a before_filter in ApplicationController:
module App
class << self
attr_accessor :base_domain
end
end
class ApplicationController < ActionController::Base
before_filter :set_base_domain
def set_base_domain
App.base_domain = SubdomainFu.host_without_subdomain(request.host)
end
end
And thus, when I need to get the domain in a model, I can just use App.base_domain.
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