rails-pattern_matching 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e22e2e09c3fcaad2a569d0020da8e60991b836923693e2d49bea810cc8c18cf2
4
- data.tar.gz: 6f53bdd3290b045c87db287190b6ba74abb7a083d7fd6578b02fb6358c68c956
3
+ metadata.gz: '08e126de1c151ea3a985acfbed7d9b819c86fcf5f3ec72f8d49c9d47b8fd1ee2'
4
+ data.tar.gz: 9c86543873217efc90ddf02d01075ac67926d84055f13c7ea7fe3a016385abe0
5
5
  SHA512:
6
- metadata.gz: c1d8c33df5c0914f84ed9fd73a96265f30d830f62da0b03bca860d8c7ce710ef1446bc54178b55164d9e46750debb3f3750c654d15a54cf60d3458d3c367a59d
7
- data.tar.gz: 1d440c27f72b3a4f3dcd9d262938553953162585a3235d68d75a424464b9517881348b9446a2dd6b98918c1959f99a52b2f4b7e6208d68fa0c81b20826f7bdb1
6
+ metadata.gz: 92d70dae9127ab8f9184c985f35c994b7031b5ae1879893d24cddbe575dae2cedb8b2e92c6cefa91477511a6ba897723c2fca6a5e671b8101e13abd7074a5272
7
+ data.tar.gz: be879b4de1aa2ee54e1d73448ed1a9cf93f15132b38867e50ceae0524693584bdc553c0c00a53dfbd132fbbe848eac98a3ef965d66953212df79628408c69c40
@@ -4,3 +4,7 @@ updates:
4
4
  directory: "/"
5
5
  schedule:
6
6
  interval: "daily"
7
+ - package-ecosystem: "github-actions"
8
+ directory: "/"
9
+ schedule:
10
+ interval: "daily"
@@ -12,7 +12,7 @@ jobs:
12
12
  steps:
13
13
  - name: Dependabot metadata
14
14
  id: metadata
15
- uses: dependabot/fetch-metadata@v1.3.3
15
+ uses: dependabot/fetch-metadata@v1.6.0
16
16
  with:
17
17
  github-token: "${{ secrets.GITHUB_TOKEN }}"
18
18
  - name: Enable auto-merge for Dependabot PRs
@@ -1,25 +1,22 @@
1
1
  name: Main
2
2
 
3
3
  on:
4
- push:
5
- branches:
6
- - main
7
- pull_request:
8
- branches:
9
- - main
10
-
11
- permissions:
12
- contents: read
4
+ - push
5
+ - pull_request
13
6
 
14
7
  jobs:
15
8
  ci:
16
9
  name: CI
17
10
  runs-on: ubuntu-latest
11
+ env:
12
+ CI: true
18
13
  steps:
19
14
  - uses: actions/checkout@master
20
15
  - uses: ruby/setup-ruby@v1
21
16
  with:
22
- ruby-version: '3.1'
17
+ ruby-version: '3.3'
23
18
  bundler-cache: true
24
- - name: Test
25
- run: bundle exec rake
19
+ - name: Lint and test
20
+ run: |
21
+ bundle exec rake stree:check
22
+ bundle exec rake
@@ -0,0 +1,76 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ In the interest of fostering an open and welcoming environment, we as
6
+ contributors and maintainers pledge to making participation in our project and
7
+ our community a harassment-free experience for everyone, regardless of age, body
8
+ size, disability, ethnicity, sex characteristics, gender identity and expression,
9
+ level of experience, education, socio-economic status, nationality, personal
10
+ appearance, race, religion, or sexual identity and orientation.
11
+
12
+ ## Our Standards
13
+
14
+ Examples of behavior that contributes to creating a positive environment
15
+ include:
16
+
17
+ - Using welcoming and inclusive language
18
+ - Being respectful of differing viewpoints and experiences
19
+ - Gracefully accepting constructive criticism
20
+ - Focusing on what is best for the community
21
+ - Showing empathy towards other community members
22
+
23
+ Examples of unacceptable behavior by participants include:
24
+
25
+ - The use of sexualized language or imagery and unwelcome sexual attention or
26
+ advances
27
+ - Trolling, insulting/derogatory comments, and personal or political attacks
28
+ - Public or private harassment
29
+ - Publishing others' private information, such as a physical or electronic
30
+ address, without explicit permission
31
+ - Other conduct which could reasonably be considered inappropriate in a
32
+ professional setting
33
+
34
+ ## Our Responsibilities
35
+
36
+ Project maintainers are responsible for clarifying the standards of acceptable
37
+ behavior and are expected to take appropriate and fair corrective action in
38
+ response to any instances of unacceptable behavior.
39
+
40
+ Project maintainers have the right and responsibility to remove, edit, or
41
+ reject comments, commits, code, wiki edits, issues, and other contributions
42
+ that are not aligned to this Code of Conduct, or to ban temporarily or
43
+ permanently any contributor for other behaviors that they deem inappropriate,
44
+ threatening, offensive, or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies both within project spaces and in public spaces
49
+ when an individual is representing the project or its community. Examples of
50
+ representing a project or community include using an official project e-mail
51
+ address, posting via an official social media account, or acting as an appointed
52
+ representative at an online or offline event. Representation of a project may be
53
+ further defined and clarified by project maintainers.
54
+
55
+ ## Enforcement
56
+
57
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
58
+ reported by contacting the project team at kddnewton@gmail.com. All
59
+ complaints will be reviewed and investigated and will result in a response that
60
+ is deemed necessary and appropriate to the circumstances. The project team is
61
+ obligated to maintain confidentiality with regard to the reporter of an incident.
62
+ Further details of specific enforcement policies may be posted separately.
63
+
64
+ Project maintainers who do not follow or enforce the Code of Conduct in good
65
+ faith may face temporary or permanent repercussions as determined by other
66
+ members of the project's leadership.
67
+
68
+ ## Attribution
69
+
70
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71
+ available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72
+
73
+ [homepage]: https://www.contributor-covenant.org
74
+
75
+ For answers to common questions about this code of conduct, see
76
+ https://www.contributor-covenant.org/faq
data/Gemfile.lock CHANGED
@@ -1,38 +1,95 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rails-pattern_matching (0.2.0)
4
+ rails-pattern_matching (0.3.0)
5
+ actionpack
5
6
  activemodel
6
7
  activerecord
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
11
- activemodel (7.0.4)
12
- activesupport (= 7.0.4)
13
- activerecord (7.0.4)
14
- activemodel (= 7.0.4)
15
- activesupport (= 7.0.4)
16
- activesupport (7.0.4)
12
+ actionpack (7.1.3.2)
13
+ actionview (= 7.1.3.2)
14
+ activesupport (= 7.1.3.2)
15
+ nokogiri (>= 1.8.5)
16
+ racc
17
+ rack (>= 2.2.4)
18
+ rack-session (>= 1.0.1)
19
+ rack-test (>= 0.6.3)
20
+ rails-dom-testing (~> 2.2)
21
+ rails-html-sanitizer (~> 1.6)
22
+ actionview (7.1.3.2)
23
+ activesupport (= 7.1.3.2)
24
+ builder (~> 3.1)
25
+ erubi (~> 1.11)
26
+ rails-dom-testing (~> 2.2)
27
+ rails-html-sanitizer (~> 1.6)
28
+ activemodel (7.1.3.2)
29
+ activesupport (= 7.1.3.2)
30
+ activerecord (7.1.3.2)
31
+ activemodel (= 7.1.3.2)
32
+ activesupport (= 7.1.3.2)
33
+ timeout (>= 0.4.0)
34
+ activesupport (7.1.3.2)
35
+ base64
36
+ bigdecimal
17
37
  concurrent-ruby (~> 1.0, >= 1.0.2)
38
+ connection_pool (>= 2.2.5)
39
+ drb
18
40
  i18n (>= 1.6, < 2)
19
41
  minitest (>= 5.1)
42
+ mutex_m
20
43
  tzinfo (~> 2.0)
21
- concurrent-ruby (1.1.10)
22
- i18n (1.12.0)
44
+ base64 (0.2.0)
45
+ bigdecimal (3.1.6)
46
+ builder (3.2.4)
47
+ concurrent-ruby (1.2.3)
48
+ connection_pool (2.4.1)
49
+ crass (1.0.6)
50
+ drb (2.2.0)
51
+ ruby2_keywords
52
+ erubi (1.12.0)
53
+ i18n (1.14.1)
23
54
  concurrent-ruby (~> 1.0)
24
- minitest (5.16.3)
25
- power_assert (2.0.2)
26
- rake (13.0.6)
27
- sqlite3 (1.5.4-arm64-darwin)
28
- sqlite3 (1.5.4-x86_64-linux)
29
- test-unit (3.5.7)
55
+ loofah (2.22.0)
56
+ crass (~> 1.0.2)
57
+ nokogiri (>= 1.12.0)
58
+ minitest (5.22.2)
59
+ mutex_m (0.2.0)
60
+ nokogiri (1.16.2-arm64-darwin)
61
+ racc (~> 1.4)
62
+ nokogiri (1.16.2-x86_64-linux)
63
+ racc (~> 1.4)
64
+ power_assert (2.0.3)
65
+ prettier_print (1.2.1)
66
+ racc (1.7.3)
67
+ rack (3.0.9.1)
68
+ rack-session (2.0.0)
69
+ rack (>= 3.0.0)
70
+ rack-test (2.1.0)
71
+ rack (>= 1.3)
72
+ rails-dom-testing (2.2.0)
73
+ activesupport (>= 5.0.0)
74
+ minitest
75
+ nokogiri (>= 1.6)
76
+ rails-html-sanitizer (1.6.0)
77
+ loofah (~> 2.21)
78
+ nokogiri (~> 1.14)
79
+ rake (13.1.0)
80
+ ruby2_keywords (0.0.5)
81
+ sqlite3 (1.7.2-arm64-darwin)
82
+ sqlite3 (1.7.2-x86_64-linux)
83
+ syntax_tree (6.2.0)
84
+ prettier_print (>= 1.2.0)
85
+ test-unit (3.6.2)
30
86
  power_assert
31
- tzinfo (2.0.5)
87
+ timeout (0.4.1)
88
+ tzinfo (2.0.6)
32
89
  concurrent-ruby (~> 1.0)
33
90
 
34
91
  PLATFORMS
35
- arm64-darwin-21
92
+ arm64-darwin-22
36
93
  x86_64-linux
37
94
 
38
95
  DEPENDENCIES
@@ -40,7 +97,8 @@ DEPENDENCIES
40
97
  rails-pattern_matching!
41
98
  rake
42
99
  sqlite3
100
+ syntax_tree
43
101
  test-unit
44
102
 
45
103
  BUNDLED WITH
46
- 2.3.24
104
+ 2.4.1
data/README.md CHANGED
@@ -1,8 +1,11 @@
1
1
  # rails-pattern_matching
2
2
 
3
- This gem provides the pattern matching interface for `ActiveModel::AttributeMethods`, `ActiveRecord::Base`, and `ActiveRecord::Relation`.
3
+ [![Build Status](https://github.com/kddnewton/rails-pattern_matching/workflows/Main/badge.svg)](https://github.com/kddnewton/rails-pattern_matching/actions)
4
+ [![Gem](https://img.shields.io/gem/v/rails-pattern_matching.svg)](https://rubygems.org/gems/rails-pattern_matching)
4
5
 
5
- That means it allows you to write code using the pattern matching against your Rails models like the following example:
6
+ This gem provides the pattern matching interface for `ActionController::Parameters`, `ActiveModel::AttributeMethods`, `ActiveRecord::Base`, and `ActiveRecord::Relation`.
7
+
8
+ That means it allows you to write code using pattern matching within your Rails applications like the following examples:
6
9
 
7
10
  ```ruby
8
11
  # app/models/post.rb
@@ -15,30 +18,55 @@ class Comment < ApplicationRecord
15
18
  belongs_to :post
16
19
  end
17
20
 
21
+ # app/controllers/posts_controller.rb
22
+ class PostsController < ApplicationController
23
+ def update
24
+ @post = Post.find(params[:id])
25
+
26
+ case params.require(:post).permit(:title, :body, :published)
27
+ in { published: true } if !can_publish?(@post, current_user)
28
+ # Here we are matching against a single parameter with a guard clause.
29
+ # This allows us to express both the value check and the authorization
30
+ # check in a single line.
31
+ render :edit, alert: "You are not authorized to publish this post"
32
+ in permitted if @post.update(permitted)
33
+ # Here we are grabbing the entire set of parameters and then using it to
34
+ # update the post. If the update succeeds, this branch will be taken.
35
+ redirect_to @post, notice: "Post was successfully updated"
36
+ else
37
+ # Here we are providing a default in case none of the above match. This
38
+ # will be taken if the update fails, and presumably the view will render
39
+ # appropriate error messages.
40
+ render :edit
41
+ end
42
+ end
43
+ end
44
+
18
45
  # app/helpers/posts_helper.rb
19
46
  module PostsHelper
20
47
  def comment_header_for(post)
21
48
  case post
22
- # Here we're matching against an attribute on the post that is an
23
- # association. It will go through the association and then match against the
24
- # records in the relation.
25
49
  in { comments: [] }
50
+ # Here we're matching against an attribute on the post that is an
51
+ # association. It will go through the association and then match against
52
+ # the records in the relation.
26
53
  "No comments yet"
27
- # Here we're searching for a comment that has the same user_id as the post.
28
- # We can do this with the "find" pattern. This syntax is all baked into
29
- # Ruby, so we don't have to do anything other than define the requisite
30
- # deconstruct methods that are used by the pattern matching.
31
- in { comments: [*, { user_id: ^(post.user_id ) }, *] }
32
- "Host replied"
33
- # Here we're extracting the first comment out of the comments association.
34
- in { comments: [comment] }
35
- "One comment"
36
- # Here we provide a default in case none of the above match. Since we have
37
- # already matched against an empty array of comments and a single element
38
- # array, we know that there are at least two comments. We can get the length
39
- # of the comments association by capturing the comments association in the
40
- # pattern itself and then using it.
54
+ in { user_id:, comments: [*, { user_id: ^user_id }, *] }
55
+ # Here we're searching for a comment that has the same user_id as the
56
+ # post. We can do this with the "find" pattern. This syntax is all baked
57
+ # into Ruby, so we don't have to do anything other than define the
58
+ # requisite deconstruct methods that are used by the pattern matching.
59
+ "Author replied"
60
+ in { comments: [{ user: { name: } }] }
61
+ # Here we're extracting the first comment out of the comments association
62
+ # and using its associated user's name in the header.
63
+ "One comment from #{name}"
41
64
  in { comments: }
65
+ # Here we provide a default in case none of the above match. Since we have
66
+ # already matched against an empty array of comments and a single element
67
+ # array, we know that there are at least two comments. We can get the
68
+ # length of the comments association by capturing the comments association
69
+ # in the pattern itself and then using it.
42
70
  "#{comments.length} comments"
43
71
  end
44
72
  end
data/Rakefile CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "bundler/gem_tasks"
4
4
  require "rake/testtask"
5
+ require "syntax_tree/rake_tasks"
5
6
 
6
7
  Rake::TestTask.new do |t|
7
8
  t.libs << "test"
@@ -10,3 +11,11 @@ Rake::TestTask.new do |t|
10
11
  end
11
12
 
12
13
  task default: :test
14
+
15
+ configure = ->(task) do
16
+ task.source_files =
17
+ FileList[%w[Gemfile Rakefile *.gemspec lib/**/*.rb test/**/*.rb]]
18
+ end
19
+
20
+ SyntaxTree::Rake::CheckTask.new(&configure)
21
+ SyntaxTree::Rake::WriteTask.new(&configure)
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Rails
4
4
  module PatternMatching
5
- VERSION = "0.2.0"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -2,10 +2,14 @@
2
2
 
3
3
  require "active_model"
4
4
  require "active_record"
5
+ require "action_controller"
5
6
  require "rails/pattern_matching/version"
6
7
 
7
8
  module Rails
8
9
  module PatternMatching
10
+ # This error is raised when the pattern matching behavior is already
11
+ # defined in a class or module that we're trying to extend. We want to be
12
+ # careful not to silently override the behavior that's already there.
9
13
  class Error < StandardError
10
14
  def initialize(context)
11
15
  super(<<~MSG)
@@ -162,3 +166,55 @@ module ActiveModel::AttributeMethods
162
166
  end
163
167
  end
164
168
  end
169
+
170
+ class ActionController::Parameters
171
+ if method_defined?(:deconstruct_keys)
172
+ raise Rails::PatternMatching::Error, "ActionController::Parameters"
173
+ end
174
+
175
+ # Returns a hash of parameters for the given keys. Provides the pattern
176
+ # matching interface for matching against hash patterns. For example:
177
+ #
178
+ # class PostsController < ApplicationController
179
+ # before_action :find_post
180
+ #
181
+ # # PATCH /posts/:id
182
+ # def update
183
+ # case post_params
184
+ # in { published: true, ** } if !can_publish?(current_user)
185
+ # render :edit, alert: "You are not authorized to publish posts"
186
+ # in permitted if @post.update(permitted)
187
+ # redirect_to @post, notice: "Post was successfully updated"
188
+ # else
189
+ # render :edit
190
+ # end
191
+ # end
192
+ #
193
+ # private
194
+ #
195
+ # def find_post
196
+ # @post = Post.find(params[:id])
197
+ # end
198
+ #
199
+ # def post_params
200
+ # params.require(:post).permit(:title, :body, :published)
201
+ # end
202
+ # end
203
+ #
204
+ # Note that for security reasons, this method will only deconstruct keys that
205
+ # have been explicitly permitted. This is to avoid the potential accidental
206
+ # misuse of the `**` operator.
207
+ #
208
+ # Note: as an optimization, Hash#deconstruct_keys (and therefore
209
+ # ActiveSupport::HashWithIndifferentAccess#deconstruct_keys) always returns
210
+ # itself. This works because the return value then has #[] called on it, so
211
+ # everything works out. This can yield some somewhat surprising (albeit
212
+ # correct) results if you call this method manually.
213
+ def deconstruct_keys(keys)
214
+ if permitted?
215
+ to_h.deconstruct_keys(keys)
216
+ else
217
+ raise ArgumentError, "Only permitted parameters can be deconstructed."
218
+ end
219
+ end
220
+ end
@@ -5,27 +5,33 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require "rails/pattern_matching/version"
6
6
 
7
7
  Gem::Specification.new do |spec|
8
- spec.name = "rails-pattern_matching"
9
- spec.version = Rails::PatternMatching::VERSION
10
- spec.authors = ["Kevin Newton"]
11
- spec.email = ["kddnewton@gmail.com"]
8
+ spec.name = "rails-pattern_matching"
9
+ spec.version = Rails::PatternMatching::VERSION
10
+ spec.authors = ["Kevin Newton"]
11
+ spec.email = ["kddnewton@gmail.com"]
12
12
 
13
- spec.summary = "Pattern matching for Rails applications"
14
- spec.homepage = "https://github.com/kddnewton/rails-pattern_matching"
15
- spec.license = "MIT"
13
+ spec.summary = "Pattern matching for Rails applications"
14
+ spec.homepage = "https://github.com/kddnewton/rails-pattern_matching"
15
+ spec.license = "MIT"
16
16
 
17
- spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
- `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
19
- end
20
- spec.bindir = "exe"
21
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
17
+ spec.files =
18
+ Dir.chdir(File.expand_path("..", __FILE__)) do
19
+ `git ls-files -z`.split("\x0")
20
+ .reject { |f| f.match(%r{^(test|spec|features)/}) }
21
+ end
22
+ spec.bindir = "exe"
23
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
24
  spec.require_paths = ["lib"]
23
25
 
26
+ spec.metadata = { "rubygems_mfa_required" => "true" }
27
+
24
28
  spec.add_dependency "activemodel"
25
29
  spec.add_dependency "activerecord"
30
+ spec.add_dependency "actionpack"
26
31
 
27
32
  spec.add_development_dependency "bundler"
28
33
  spec.add_development_dependency "rake"
29
34
  spec.add_development_dependency "sqlite3"
35
+ spec.add_development_dependency "syntax_tree"
30
36
  spec.add_development_dependency "test-unit"
31
37
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails-pattern_matching
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kevin Newton
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-12-19 00:00:00.000000000 Z
11
+ date: 2024-03-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: actionpack
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -80,6 +94,20 @@ dependencies:
80
94
  - - ">="
81
95
  - !ruby/object:Gem::Version
82
96
  version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: syntax_tree
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  - !ruby/object:Gem::Dependency
84
112
  name: test-unit
85
113
  requirement: !ruby/object:Gem::Requirement
@@ -105,6 +133,7 @@ files:
105
133
  - ".github/workflows/auto-merge.yml"
106
134
  - ".github/workflows/main.yml"
107
135
  - ".gitignore"
136
+ - CODE_OF_CONDUCT.md
108
137
  - Gemfile
109
138
  - Gemfile.lock
110
139
  - LICENSE
@@ -117,7 +146,8 @@ files:
117
146
  homepage: https://github.com/kddnewton/rails-pattern_matching
118
147
  licenses:
119
148
  - MIT
120
- metadata: {}
149
+ metadata:
150
+ rubygems_mfa_required: 'true'
121
151
  post_install_message:
122
152
  rdoc_options: []
123
153
  require_paths:
@@ -133,7 +163,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
133
163
  - !ruby/object:Gem::Version
134
164
  version: '0'
135
165
  requirements: []
136
- rubygems_version: 3.3.21
166
+ rubygems_version: 3.4.1
137
167
  signing_key:
138
168
  specification_version: 4
139
169
  summary: Pattern matching for Rails applications