Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Vlad Hilko
Vlad Hilko

Posted on

     

How to add Concern in Ruby on Rails?

Overview:

In this article, we will discuss the usage ofActiveSupport::Concern. We will delve into what it is, why we need it, and the problems it can solve. Additionally, we will explore the pros and cons associated with its usage. To illustrate its practical implementation, we will provide three examples.

Definition:

In simple terms,Concern allows you to extract common logic from different models into a reusable module. You may ask, "Why can't we just use a module? Why do we need to define something else?" Here are the main differences: Essentially,Concern is an ordinary module, but it allows you to include anyActiveRecord behaviors that you can define on the model. Therefore, we can consider it as a module for encapsulating and reusingActiveRecord methods.

Advantages:

  • Code Reusability: ActiveSupport Concern allows us to follow the DRY principle by extracting common logic into reusable modules.

Problems:

  • Complex Dependencies: Overusing concerns can result in a complex web of dependencies, making the code harder to understand and maintain.
  • Difficulty in Code Search: Finding the definition of a specific behavior or functionality can be more challenging due to the dispersed nature of concerns.

In essence, concerns are well-suited for storing technical components, such as:

  • Adding file helpers to facilitate handling files with a clear interface.
  • Creating wrappers for existing attributes and values to enhance interface clarity.
  • Implementing technical features like change history tracking, logging, and more.
  • etc...

However, concerns are not well suitable for storing business logic.

Implementation

In the following section, we will discuss the key elements of concerns. To understand concerns, we only need to know what a module is and theincluded andclass_methods methods fromActiveSupport::Concern.

Module

A module in Ruby is a container that groups together related methods, constants, and other module or class definitions. It provides a way to organize and reuse code in a modular manner. And Сoncern is just a module, so we need to know how to create it.

# app/models/concerns/concern_sample.rb# frozen_string_literal: truemoduleConcernSampledefhello_worldputs'Hello World!'endend
Enter fullscreen modeExit fullscreen mode

ActiveSupport::Concern methods

So, what is the main difference between a simple Ruby module and a concern? The main differences are that when you includeActiveSupport::Concern in a module, two new methods are added. These methods are:

  • included

This method enables us to add Active Record logic directly to the module and reuse it at the model level. For example:

# app/models/concerns/concern_sample.rb# frozen_string_literal: truemoduleConcernSampleextendActiveSupport::Concernincludeddo# Define class methods heredefself.my_class_method# Class method logicend# Define instance methods heredefmy_instance_method# Instance method logicend# Define associations herehas_many:commentsbelongs_to:category# Define validations herevalidates_presence_of:namevalidates_uniqueness_of:email# Define callbacks herebefore_save:do_somethingafter_create:do_something_else# Define scopes herescope:active,->{where(active:true)}scope:recent,->{order(created_at: :desc)}endend
Enter fullscreen modeExit fullscreen mode
  • class_methods

This method allows us to add methods that can be called from the class itself:

# app/models/concerns/concern_sample.rb# frozen_string_literal: truemoduleConcernSampleextendActiveSupport::Concernclass_methodsdodefmy_class_method# Class method logicendendend
Enter fullscreen modeExit fullscreen mode

Let's consider three examples to gain a better understanding of this concept.

Example 1: FullDescription.

In the first example, we will consider a very basic scenario to gain an understanding of how this actually works. For instance, let's assume we have the following model:

# app/models/animal.rbclassAnimal<ApplicationRecordend
Enter fullscreen modeExit fullscreen mode

Then we decided to add a method to the model that would provide the full description of this animal.

# app/models/animal.rbclassAnimal<ApplicationRecorddeffull_description"Name:#{name}. Kind:#{kind}. Status:#{status}."endend
Enter fullscreen modeExit fullscreen mode

Here's how it works:

animal=Animal.firstanimal.full_description# => "Name: Simba. Kind: Cat. Status: Adopted"animal=Animal.secondanimal.full_description# => "Name: Rocky. Kind: Rabbit. Status: Available"
Enter fullscreen modeExit fullscreen mode

What do we need to do if we want to move this logic to the concern? First of all, we need to create a new concern:

# app/models/concerns/full_description.rb# frozen_string_literal: truemoduleFullDescriptiondeffull_description"Name:#{name}. Kind:#{kind}. Status:#{status}"endend
Enter fullscreen modeExit fullscreen mode

And include this concern at the model level:

# app/models/animal.rbclassAnimal<ApplicationRecordincludeFullDescriptionend
Enter fullscreen modeExit fullscreen mode

So the same interface is still available:

animal=Animal.firstanimal.full_description# => "Name: Simba. Kind: Cat. Status: Adopted"
Enter fullscreen modeExit fullscreen mode

We didn't includeActiveSupport::Concern here just to highlight that for simple methods withoutActiveRecord logic, it will still work fine. However, it will also work withActiveSupport::Concern:

# app/models/concerns/full_description.rb# frozen_string_literal: truemoduleFullDescriptionextendActiveSupport::Concernincludeddodeffull_description"Name:#{name}. Kind:#{kind}. Status:#{status}."endendend
Enter fullscreen modeExit fullscreen mode

Like this:

animal=Animal.firstanimal.full_description# => "Name: Simba. Kind: Cat. Status: Adopted."
Enter fullscreen modeExit fullscreen mode

And if you want to add class methods, we can do the following:

# app/models/concerns/full_description.rb# frozen_string_literal: truemoduleFullDescriptionextendActiveSupport::Concernclass_methodsdodeffull_descriptionall.map{_1.full_description}.join(' ')endendend
Enter fullscreen modeExit fullscreen mode

Now thefull_description method is available on theAnimal class:

Animal.full_description# => "Name: Simba. Kind: Cat. Status: Adopted. Name: Rocky. Kind: Rabbit. Status: Available."
Enter fullscreen modeExit fullscreen mode

Example 2: UUID

In the second example, we will create a concern that generates and saves a unique UUID value before creating a new database record. To achieve this, we will define a new concern as follows:

# app/models/concerns/uuid.rb# frozen_string_literal: truemoduleUUIDextendActiveSupport::Concernincludeddobefore_create:set_uuidprivatedefset_uuidself.uuid||=SecureRandom.uuidendendend
Enter fullscreen modeExit fullscreen mode

And we will include it at the model level:

# app/models/animal.rbclassAnimal<ApplicationRecordincludeUUIDend
Enter fullscreen modeExit fullscreen mode

Then, every time we create a new Animal record in the database, the UUID will be automatically generated for us:

animal=Animal.createanimal.uuid# => '7f934646-bc67-446c-920d-ca5415d28b94'
Enter fullscreen modeExit fullscreen mode

P.S. To use the UUID acronym, you need to add the following code toconfig/initializers/inflections.rb:

# config/initializers/inflections.rbActiveSupport::Inflector.inflectionsdo|inflect|inflect.acronym'UUID'end
Enter fullscreen modeExit fullscreen mode

Example 3: PredicateMethods

In the last example, let's consider adding a concern that allows us to simplify the interface for attributes with a fixed number of available values. For instance, if ananimal record has astatus attribute with the following available values:['available', 'adopted', 'pending'], then we would like to have the following interface:

animal=Animal.create(status:'available')animal.available?# => trueanimal.adopted?# => falseanimal.pending?# => false# or with prexixanimal.status_available?# => trueanimal.status_adopted?# => flase# etc ..
Enter fullscreen modeExit fullscreen mode

What do we need to do to implement it? Let's create a new concern calledPredicateMethods and add its method to the model:

# app/models/animal.rbclassAnimal<ApplicationRecordincludePredicateMethodsdefine_predicate_methodsattribute: :status,available_values:['available','adopted','pending'],prefix:falseend
Enter fullscreen modeExit fullscreen mode

So how is this concern implemented? It's quite simple. We just need to define a new method calleddefine_predicate_methods, and this method will generate new methods on the model for every available value that you pass as an argument. Inside this method, we will check if the current status value is equal to the given value or not. Here's the implementation:

# app/models/concerns/predicate_methods.rb# frozen_string_literal: truemodulePredicateMethodsextendActiveSupport::ConcernMethodAlreadyDefined=Class.new(ArgumentError)class_methodsdodefdefine_predicate_methods(attribute:,available_values:,prefix:false)available_values.eachdo|value|method_name=[(prefix?"#{attribute}_":''),value,'?'].join.to_symraiseMethodAlreadyDefined,"#{method_name} already defined"ifinstance_methods.include?(method_name)define_method(method_name)dopublic_send(attribute)==valueendendendendend
Enter fullscreen modeExit fullscreen mode

Now our interface works as desired:

animal=Animal.lastanimal.available?# => trueanimal.adopted?# => false
Enter fullscreen modeExit fullscreen mode

If you'd like to add thestatus prefix, you just need to change the value of theprefix attribute fromfalse totrue:

animal=Animal.lastanimal.status_available?# => trueanimal.status_adopted?# => false
Enter fullscreen modeExit fullscreen mode

That's it! Now we can add this concern to any of our models and make their interfaces more readable and simplified.

Conclusion

In conclusion, in this article we delved into the usage ofActiveSupport::Concern in Rails. We explored what it was, why we needed it, and the problems it could solve. We examined the pros and cons of utilizing this approach, considering different perspectives. Furthermore, to enhance our understanding of the concept, we provided three examples that illustrated its practical implementation.

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

  • Location
    USA, Portland
  • Work
    Ruby On Rails Developer
  • Joined

More fromVlad Hilko

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp