/rails-pattern_matching

Pattern matching for Rails applications

Primary LanguageRubyMIT LicenseMIT

rails-pattern_matching

Build Status Gem

This gem provides the pattern matching interface for ActionController::Parameters, ActiveModel::AttributeMethods, ActiveRecord::Base, and ActiveRecord::Relation.

That means it allows you to write code using pattern matching within your Rails applications like the following examples:

# app/models/post.rb
class Post < ApplicationRecord
  has_many :comments
end

# app/models/comment.rb
class Comment < ApplicationRecord
  belongs_to :post
end

# app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def update
    @post = Post.find(params[:id])

    case params.require(:post).permit(:title, :body, :published)
    in { published: true } if !can_publish?(@post, current_user)
      # Here we are matching against a single parameter with a guard clause.
      # This allows us to express both the value check and the authorization
      # check in a single line.
      render :edit, alert: "You are not authorized to publish this post"
    in permitted if @post.update(permitted)
      # Here we are grabbing the entire set of parameters and then using it to
      # update the post. If the update succeeds, this branch will be taken.
      redirect_to @post, notice: "Post was successfully updated"
    else
      # Here we are providing a default in case none of the above match. This
      # will be taken if the update fails, and presumably the view will render
      # appropriate error messages.
      render :edit
    end
  end
end

# app/helpers/posts_helper.rb
module PostsHelper
  def comment_header_for(post)
    case post
    in { comments: [] }
      # Here we're matching against an attribute on the post that is an
      # association. It will go through the association and then match against
      # the records in the relation.
      "No comments yet"
    in { user_id:, comments: [*, { user_id: ^user_id }, *] }
      # Here we're searching for a comment that has the same user_id as the
      # post. We can do this with the "find" pattern. This syntax is all baked
      # into Ruby, so we don't have to do anything other than define the
      # requisite deconstruct methods that are used by the pattern matching.
      "Author replied"
    in { comments: [{ user: { name: } }] }
      # Here we're extracting the first comment out of the comments association
      # and using its associated user's name in the header.
      "One comment from #{name}"
    in { comments: }
      # Here we provide a default in case none of the above match. Since we have
      # already matched against an empty array of comments and a single element
      # array, we know that there are at least two comments. We can get the
      # length of the comments association by capturing the comments association
      # in the pattern itself and then using it.
      "#{comments.length} comments"
    end
  end
end

Installation

Add this line to your application's Gemfile:

gem "rails-pattern_matching"

And then execute:

$ bundle

Or install it yourself as:

$ gem install rails-pattern_matching

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/kddnewton/rails-pattern_matching.

License

The gem is available as open source under the terms of the MIT License.