Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 1.6.1

### Fixed

- Fixed issue with YARD and RBS documentation tasks possibly raising an error if a named value method is deprecated and wrapped to return an error. Types are now inferred directly from the data rather than calling a method.

## 1.6.0

### Added
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.6.0
1.6.1
2 changes: 1 addition & 1 deletion lib/support_table_data/documentation/rbs_doc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def instance_signatures(name)
lines << "def self.#{name}: () -> #{klass.name}"
lines << "def #{name}?: () -> bool"
klass.support_table_attribute_helpers.each do |attribute_name|
return_type = TypeInference.rbs_type(TypeInference.value_type(klass, "#{name}_#{attribute_name}"))
return_type = TypeInference.rbs_type(TypeInference.value_type(klass, name, attribute_name))
lines << "def self.#{name}_#{attribute_name}: () -> #{return_type}"
end
lines
Expand Down
28 changes: 15 additions & 13 deletions lib/support_table_data/documentation/type_inference.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,28 @@
module SupportTableData
module Documentation
# Infers documentation types for the dynamically-defined attribute helpers
# by calling the generated method and inspecting the class of the value
# it returns. The values returned by these helpers are frozen literals from
# the parsed data file, so this does not require a database connection.
#
# This module must not be used on finder helpers (e.g. `Color.red`), which
# call `find_by!` and would hit the database.
# by reading the canonical value out of the parsed data file and
# inspecting its class. This avoids invoking the generated method, which
# may have been wrapped (e.g. deprecated) to raise.
module TypeInference
module_function

# Determine the documentation type for an attribute helper by calling
# the method and looking at the class of the returned value. Returns
# nil when the method is not defined.
# Determine the documentation type for a named-instance attribute
# helper by looking up the attribute value in the model's named
# instance data and returning its class. Returns nil when the
# attribute is not defined for the named instance.
#
# @param klass [Class] The model class
# @param method_name [String, Symbol] The class method name to call
# @param name [String, Symbol] The named instance name
# @param attribute_name [String, Symbol] The attribute name
# @return [Class, nil]
def value_type(klass, method_name)
return nil unless klass.respond_to?(method_name)
def value_type(klass, name, attribute_name)
return nil unless klass.respond_to?(:named_instance_data)

klass.public_send(method_name).class
data = klass.named_instance_data(name)
return nil unless data.is_a?(Hash) && data.key?(attribute_name.to_s)

data[attribute_name.to_s].class
end

# Map a Ruby value class to a YARD type string.
Expand Down
2 changes: 1 addition & 1 deletion lib/support_table_data/documentation/yard_doc.rb
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ def compact_instance_block(name)
end

def attribute_yard_return_type(name, attribute_name)
TypeInference.yard_type(TypeInference.value_type(klass, "#{name}_#{attribute_name}"))
TypeInference.yard_type(TypeInference.value_type(klass, name, attribute_name))
end
end
end
Expand Down
24 changes: 14 additions & 10 deletions spec/support_table_data/documentation/type_inference_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@

RSpec.describe SupportTableData::Documentation::TypeInference do
describe ".value_type" do
it "returns the class of the value returned by the helper method" do
it "returns the class of the value from the named instance data" do
# Group has named_instance_attribute_helpers :group_id, :name
expect(described_class.value_type(Group, "primary_name")).to eq(String)
expect(described_class.value_type(Group, "primary_group_id")).to eq(Integer)
expect(described_class.value_type(Group, "primary", "name")).to eq(String)
expect(described_class.value_type(Group, "primary", "group_id")).to eq(Integer)
end

it "returns nil when the method is not defined" do
expect(described_class.value_type(Group, "not_a_real_method")).to be_nil
it "returns nil when the attribute is not present in the named instance data" do
expect(described_class.value_type(Group, "primary", "not_a_real_attribute")).to be_nil
end

it "does not call the database for finder methods" do
# Sanity check: this confirms we are calling literal-returning helpers,
# not finder methods. Calling Group.primary would hit the DB; we should
# never invoke value_type on it.
it "returns nil when the named instance does not exist" do
expect(described_class.value_type(Group, "not_a_real_instance", "name")).to be_nil
end

it "does not invoke the generated helper methods" do
# The generated method may be wrapped (e.g. deprecated) to raise, so we
# must read the value from the data file rather than calling the method.
expect(Group).not_to receive(:primary)
described_class.value_type(Group, "primary_name")
expect(Group).not_to receive(:primary_name)
described_class.value_type(Group, "primary", "name")
end
end

Expand Down