Skip to content

Commit

Permalink
Support Rails7 style enum definitions
Browse files Browse the repository at this point in the history
Since Rails7, the enum definition style has been changed.  This uses a
private a private method `#_enum_methods_module` to obtain the
registered enum defintions to support both Rails5 and Rails7.

Thanks to @ksss.
  • Loading branch information
tk0miya committed Aug 15, 2023
1 parent 4ba3f0f commit f717832
Show file tree
Hide file tree
Showing 3 changed files with 17 additions and 89 deletions.
85 changes: 11 additions & 74 deletions lib/rbs_rails/active_record.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ def self.class_to_rbs(klass, dependencies: [])
end

class Generator
IGNORED_ENUM_KEYS = %i[_prefix _suffix _default _scopes]

def initialize(klass, dependencies:)
@klass = klass
@dependencies = dependencies
Expand Down Expand Up @@ -316,83 +314,22 @@ def authenticate_#{attribute}: (String) -> (#{klass_name} | false)
end

private def enum_instance_methods
# @type var methods: Array[String]
methods = []
enum_definitions.each do |hash|
hash.each do |name, values|
next if IGNORED_ENUM_KEYS.include?(name)

values.each do |label, value|
value_method_name = enum_method_name(hash, name, label)
methods << "def #{value_method_name}!: () -> bool"
methods << "def #{value_method_name}?: () -> bool"
end
end
end

methods.join("\n")
enum_definitions.map do |method|
<<~RBS.chomp
def #{method}!: () -> bool
def #{method}?: () -> bool
RBS
end.join("\n")
end

private def enum_scope_methods(singleton:)
# @type var methods: Array[String]
methods = []
enum_definitions.each do |hash|
hash.each do |name, values|
next if IGNORED_ENUM_KEYS.include?(name)

values.each do |label, value|
value_method_name = enum_method_name(hash, name, label)
methods << "def #{singleton ? 'self.' : ''}#{value_method_name}: () -> #{relation_class_name}"
end
end
end
methods.join("\n")
enum_definitions.map do |method|
"def #{singleton ? 'self.' : ''}#{method}: () -> #{relation_class_name}"
end.join("\n")
end

private def enum_definitions
@enum_definitions ||= build_enum_definitions
end

# We need static analysis to detect enum.
# ActiveRecord has `defined_enums` method,
# but it does not contain _prefix and _suffix information.
private def build_enum_definitions
ast = parse_model_file
return [] unless ast

traverse(ast).map do |node|
# @type block: nil | Hash[untyped, untyped]
next unless node.type == :send
next unless node.children[0].nil?
next unless node.children[1] == :enum

definitions = node.children[2]
next unless definitions
next unless definitions.type == :hash
next unless traverse(definitions).all? { |n| [:str, :sym, :int, :hash, :pair, :true, :false].include?(n.type) }

code = definitions.loc.expression.source
code = "{#{code}}" if code[0] != '{'
eval(code)
end.compact
end

private def enum_method_name(hash, name, label)
enum_prefix = hash[:_prefix]
enum_suffix = hash[:_suffix]

if enum_prefix == true
prefix = "#{name}_"
elsif enum_prefix
prefix = "#{enum_prefix}_"
end
if enum_suffix == true
suffix = "_#{name}"
elsif enum_suffix
suffix = "_#{enum_suffix}"
end

"#{prefix}#{label}#{suffix}"
@enum_definitions ||= klass.send(:_enum_methods_module).instance_methods.map { |m| m.to_s.chop }.uniq.sort
end

private def scopes(singleton:)
Expand Down Expand Up @@ -495,7 +432,7 @@ def authenticate_#{attribute}: (String) -> (#{klass_name} | false)
private def columns
mod_sig = +"module GeneratedAttributeMethods\n"
mod_sig << klass.columns.map do |col|
class_name = if enum_definitions.any? { |hash| hash.key?(col.name) || hash.key?(col.name.to_sym) }
class_name = if enum_definitions.include?(col.name)
'::String'
else
sql_type_to_class(col.type)
Expand Down
11 changes: 1 addition & 10 deletions sig/rbs_rails/active_record.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ class RbsRails::ActiveRecord::Generator
@enum_definitions: Array[Hash[Symbol, untyped]]
@klass_name: String

IGNORED_ENUM_KEYS: Array[Symbol]

def initialize: (singleton(ActiveRecord::Base) klass, dependencies: Array[String]) -> untyped

def generate: () -> String
Expand Down Expand Up @@ -51,14 +49,7 @@ class RbsRails::ActiveRecord::Generator

def enum_scope_methods: (singleton: untyped `singleton`) -> String

def enum_definitions: () -> Array[Hash[Symbol, untyped]]

# We need static analysis to detect enum.
# ActiveRecord has `defined_enums` method,
# but it does not contain _prefix and _suffix information.
def build_enum_definitions: () -> Array[Hash[Symbol, untyped]]

def enum_method_name: (Hash[Symbol, untyped] hash, Symbol name, Symbol label) -> String
def enum_definitions: () -> Array[String]

def scopes: (singleton: untyped `singleton`) -> untyped

Expand Down
10 changes: 5 additions & 5 deletions test/expectations/user.rbs
Original file line number Diff line number Diff line change
Expand Up @@ -221,21 +221,21 @@ class User < ::ApplicationRecord
end
include ActiveModel_SecurePassword_InstanceMethodsOnActivation_password

def temporary!: () -> bool
def temporary?: () -> bool
def accepted!: () -> bool
def accepted?: () -> bool
def self.temporary: () -> ::User::ActiveRecord_Relation
def temporary!: () -> bool
def temporary?: () -> bool
def self.accepted: () -> ::User::ActiveRecord_Relation
def self.temporary: () -> ::User::ActiveRecord_Relation
def self.all_kind_args: (untyped type, ?untyped m, ?untyped n, *untyped rest, untyped x, ?k: untyped, **untyped untyped) { (*untyped) -> untyped } -> ::User::ActiveRecord_Relation
def self.no_arg: () -> ::User::ActiveRecord_Relation
def self.with_attached_avatar: () -> ::User::ActiveRecord_Relation

module GeneratedRelationMethods
def temporary: () -> ::User::ActiveRecord_Relation

def accepted: () -> ::User::ActiveRecord_Relation

def temporary: () -> ::User::ActiveRecord_Relation

def all_kind_args: (untyped type, ?untyped m, ?untyped n, *untyped rest, untyped x, ?k: untyped, **untyped untyped) { (*untyped) -> untyped } -> ::User::ActiveRecord_Relation

def no_arg: () -> ::User::ActiveRecord_Relation
Expand Down

0 comments on commit f717832

Please sign in to comment.