Ruby on Rails - Using Concerns in Controllers

Ruby On Rails - Using concerns in controllers

If you include modules (extended by ActiveSupport::Concern or not), the methods of that module become instance methods of the including class/module.

Your Controller method should hence read

def show
somethingfoo
render plain: "Yeah, I'm shown!"
end

Where to store 'concerns' in a Ruby on Rails project? (Rails 5.2+)

the file path that using include or extend Rails does some magic when starting to autoload a lot of things so you don't have to worry later when you call "Bar". This talk is really helpfull to understand WHY you can just do include Bar inside a rails model without much thinking https://www.youtube.com/watch?v=I0a5zv7uBHw

Usually, you want model related concerns inside /app/models/concerns and controller related concerns inside /app/controllers/concerns, but that's just for organization purposes, rails will autoload them even if you use /app/whatever/concerns, so be carefull about name collisions.

You DO need to extend ActiveSupport::Concern if you want to use the syntax sugar that Concerns provide, but at the end they are just modules that can be included. https://api.rubyonrails.org/classes/ActiveSupport/Concern.html check this examples, concerns are just a way to write modules to share behaviour with a more friendly syntax for common rails patterns.

extend is a method of Object https://docs.ruby-lang.org/en/2.6.0/Object.html#method-i-extend
include is a method of Module https://docs.ruby-lang.org/en/2.6.0/Module.html#method-i-include (and Module inherits extend from Object)

How to properly set up, make or include concern in Rails

Related explanation has been written in official guide.

All right, Rails has a collection of directories similar to $LOAD_PATH in which to look up post.rb. That collection is called autoload_paths and by default it contains:

Any existing second level directories called app/*/concerns in the application and engines.

https://guides.rubyonrails.org/autoloading_and_reloading_constants.html

The reason why app/controllers/admin/concerns isn't loaded is it's not a second level directory.

Since files in the second level concerns directory are automatically loaded, in this case you sould put the test.rb file in the app/controllers/concerns/admin.

Or adding app/controllers/admin/concerns to autoload path, but it's not highly recommended because this is out of rails design pattern.

Rails concerns, how to include a concern inside an api controller

Since rails expects your module naming to follow your file structure, your concern should be named:

module Api::Concerns::ErrorSerializer

Since you're including it in Api::TemplatesController, I would do:

class Api::TemplatesController < ApiController
include Api::Concerns::ErrorSerializer
...
end

To help rails out with the constant lookup.

Rails: including concern in controller results in action 'index' could not be found

It should be include CurrentUserConcern instead of include current_user_concern

class EntriesController < ApplicationController
include CurrentUserConcern
end

Rails 5: Abstracting actions out of controllers

You have completly misunderstood pretty much everything about ActiveSupport::Concern and how module mixins work.

Lets start by using composition to seperate concerns. For example:

module LocalStorage
extend ActiveSupport::Concern
class_methods do
# use a memoized helper instead of a constant
# as its easier to stub for testing
def local_storage
@local_storage ||= ::PerceptSys::LocalStore
end
end
end

This makes sense to extract to a separate concern since its a reusable behaviour.

We can then compose an Uploadable concern:

module Uploadable
# we compose modules by extending
extend ActiveSupport::Concern
extend LocalStorage

# put instance methods in the module body

# GET /<model>/upload(.:format)
def webupload
# ...
end

#
# POST /<model>/upload(.:format)
#
def upload
# ...

end

# don't abuse use a callback for this - just use a straight
# method that returns a value and preferably does not have side effects
def upload_params
# ...
end

# ...

# use "included" to hook in the class definition
# self here is the singleton class instance
# so this is where you put callbacks, attr_accessor etc
# which would normally go in the class defintion
included do
before_action(:set_title_uploading,
:only => [
:upload,
:webupload,
])
before_action(:set_file_upload,
:only => [
:upload,
])
end

# just use class_methods for actual class_methods!
class_methods do
# for example to derive the name of a model from the controller name
def resource_class_name
controller_name.singularize
end

def resource_class
@resource_class ||= resource_class_name.classify.constantize
end
end
end

Concerns vs Services

Concerns and services are very different abstraction patterns used for entirely different purposes.

  • Services are operations/functions turned into classes.
  • Concerns are mixins.

Say I want to authenticate a user. I could easily just write a authenticate(username, password) function, but it might be useful to encapsulate the logic for that function in a class instead:

class AuthenticationService
def initialize(username, password); end
def run(); end
end

Aside from helping me encapsulate all of the logic for authentication in a single class, this allows me to create other authentication classes.

class GoogleAuthenticationService
def initialize(username, password); end
def run(); end
end

class FacebookAuthenticationService
def initialize(username, password); end
def run(); end
end

A concern, on the other hand, mixes functionality from a module into another class. Say you have a Rails controller that was concerned with authorizing a user. You might write a concern that implements a before_action which checks to see if the user is authorized. Then you could mix that concern into any controller where you wanted to enforce that authorization check.

how to include concerns module with only actions in rails controllers

AFAIK this is impossible out of the box. I use the following approach:

PLAN_FINDING_USE = [:show]
include Concerns::V1::PlanFinding

and

included do |base|
actions = base.const_defined?('PLAN_FINDING_USE') &&
base.const_get('PLAN_FINDING_USE')

if actions.is_a?(Array)
before_action :set_plan, only: actions
else
before_action :set_plan
end
end


Related Topics



Leave a reply



Submit