-
Notifications
You must be signed in to change notification settings - Fork 122
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement ActiveModel::Validations::Confirm compiler
This validation generates a virtual attribute that allows for checking confirmation logic over the given attribute name
- Loading branch information
Showing
4 changed files
with
268 additions
and
45 deletions.
There are no files selected for viewing
97 changes: 97 additions & 0 deletions
97
lib/tapioca/dsl/compilers/active_model_validations_confirmation.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
# typed: strict | ||
# frozen_string_literal: true | ||
|
||
begin | ||
require "active_model" | ||
rescue LoadError | ||
return | ||
end | ||
|
||
module Tapioca | ||
module Dsl | ||
module Compilers | ||
# `Tapioca::Dsl::Compilers::ActiveModelValidationsConfirmation` decorates RBI files for all | ||
# classes that use [`ActiveModel::Validates::Confirmation`](https://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_confirmation_of). | ||
# | ||
# For example, with the following class: | ||
# | ||
# ~~~rb | ||
# class User | ||
# include ActiveModel::Validations | ||
# | ||
# validates_confirmation_of :password | ||
# | ||
# validates :email, confirmation: true | ||
# end | ||
# ~~~ | ||
# | ||
# this compiler will produce an RBI file with the following content: | ||
# ~~~rbi | ||
# # typed: true | ||
# | ||
# class User | ||
# | ||
# sig { returns(T.untyped) } | ||
# def email_confirmation; end | ||
# | ||
# sig { params(email_confirmation=: T.untyped).returns(T.untyped) } | ||
# def email_confirmation=(email_confirmation); end | ||
# | ||
# sig { returns(T.untyped) } | ||
# def password_confirmation; end | ||
# | ||
# sig { params(password_confirmation=: T.untyped).returns(T.untyped) } | ||
# def password_confirmation=(password_confirmation); end | ||
# end | ||
# ~~~ | ||
class ActiveModelValidationsConfirmation < Compiler | ||
extend T::Sig | ||
|
||
ConstantType = type_member do | ||
{ | ||
fixed: T.all( | ||
T::Class[ActiveModel::Validations], | ||
ActiveModel::Validations::HelperMethods, | ||
ActiveModel::Validations::ClassMethods, | ||
), | ||
} | ||
end | ||
|
||
class << self | ||
sig { override.returns(T::Enumerable[Module]) } | ||
def gather_constants | ||
# Collect all the classes that include ActiveModel::Validations | ||
all_classes.select do |c| | ||
c < ActiveModel::Validations && | ||
c.name.to_s != "ActiveRecord::Base" | ||
end | ||
end | ||
end | ||
|
||
sig { override.void } | ||
def decorate | ||
confirmation_validators = constant.validators.select do |v| | ||
v.is_a?(ActiveModel::Validations::ConfirmationValidator) | ||
end | ||
|
||
return if confirmation_validators.empty? | ||
|
||
# Create a RBI definition for each class that includes Active::Model::Validations | ||
root.create_path(constant) do |klass| | ||
# Create RBI definitions for all the attributes that use confirmation validation | ||
confirmation_validators.each do |validator| | ||
validator.attributes.each do |attr_name| | ||
klass.create_method("#{attr_name}_confirmation", return_type: "T.untyped") | ||
klass.create_method( | ||
"#{attr_name}_confirmation=", | ||
parameters: [create_param("#{attr_name}_confirmation", type: "T.untyped")], | ||
return_type: "T.untyped", | ||
) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
## ActiveModelValidationsConfirmation | ||
|
||
`Tapioca::Dsl::Compilers::ActiveModelValidationsConfirmation` decorates RBI files for all | ||
classes that use [`ActiveModel::Validates::Confirmation`](https://api.rubyonrails.org/classes/ActiveModel/Validations/HelperMethods.html#method-i-validates_confirmation_of). | ||
|
||
For example, with the following class: | ||
|
||
~~~rb | ||
class User | ||
include ActiveModel::Validations | ||
|
||
validates_confirmation_of :password | ||
|
||
validates :email, confirmation: true | ||
end | ||
~~~ | ||
|
||
this compiler will produce an RBI file with the following content: | ||
~~~rbi | ||
# typed: true | ||
|
||
class User | ||
|
||
sig { returns(T.untyped) } | ||
def email_confirmation; end | ||
|
||
sig { params(email_confirmation=: T.untyped).returns(T.untyped) } | ||
def email_confirmation=(email_confirmation); end | ||
|
||
sig { returns(T.untyped) } | ||
def password_confirmation; end | ||
|
||
sig { params(password_confirmation=: T.untyped).returns(T.untyped) } | ||
def password_confirmation=(password_confirmation); end | ||
end | ||
~~~ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
87 changes: 87 additions & 0 deletions
87
spec/tapioca/dsl/compilers/active_model_validations_confirmation_spec.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
# typed: strict | ||
# frozen_string_literal: true | ||
|
||
require "spec_helper" | ||
|
||
module Tapioca | ||
module Dsl | ||
module Compilers | ||
class ActiveModelValidationsConfirmationSpec < ::DslSpec | ||
describe "Tapioca::Dsl::Compilers::ActiveModelValidationsConfirmationSpec" do | ||
describe "initialize" do | ||
it "gathers no constants if there are no classes using ActiveModel::Validations" do | ||
assert_empty(gathered_constants) | ||
end | ||
|
||
it "gathers only classes including ActiveModel::Attributes" do | ||
add_ruby_file("shop.rb", <<~RUBY) | ||
require "active_record" | ||
class Shop | ||
end | ||
class ShopWithValidations | ||
include ActiveModel::Validations | ||
end | ||
class ShopWithActiveRecord < ActiveRecord::Base | ||
end | ||
RUBY | ||
assert_equal(["ShopWithActiveRecord", "ShopWithValidations"], gathered_constants) | ||
end | ||
end | ||
|
||
describe "decorate" do | ||
it "does not generate a file when there's no confirm validations" do | ||
add_ruby_file("shop.rb", <<~RUBY) | ||
class Shop | ||
include ActiveModel::Validations | ||
validates :name, presence: true | ||
end | ||
RUBY | ||
|
||
expected = <<~RBI | ||
# typed: strong | ||
RBI | ||
|
||
assert_equal(expected, rbi_for(:Shop)) | ||
end | ||
|
||
it "generates a method for each confirm validation" do | ||
add_ruby_file("shop.rb", <<~RUBY) | ||
class Shop | ||
include ActiveModel::Validations | ||
validates :name, confirmation: true | ||
validates_confirmation_of :password | ||
end | ||
RUBY | ||
|
||
expected = <<~RBI | ||
# typed: strong | ||
class Shop | ||
sig { returns(T.untyped) } | ||
def name_confirmation; end | ||
sig { params(name_confirmation: T.untyped).returns(T.untyped) } | ||
def name_confirmation=(name_confirmation); end | ||
sig { returns(T.untyped) } | ||
def password_confirmation; end | ||
sig { params(password_confirmation: T.untyped).returns(T.untyped) } | ||
def password_confirmation=(password_confirmation); end | ||
end | ||
RBI | ||
|
||
assert_equal(expected, rbi_for(:Shop)) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |