Skip to content

Commit

Permalink
Merge pull request #3 from bdurand/additional_helpers
Browse files Browse the repository at this point in the history
Add named instance attribute helpers
  • Loading branch information
bdurand authored Apr 27, 2023
2 parents c115feb + cc5d68b commit b84deb7
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 19 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@ 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.1.0

### Added

- Helper methods can defined on the class to expose attributes for named instances without requiring a database connection.

## 1.0.0

### Added

- Add SupportTableData concern to enable automatic syncing of data on support tables.
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,21 @@ status.completed? # status.id == 3

Helper methods will not override already defined methods on a model class. If a method is already defined, an `ArgumentError` will be raised.

You can also define helper methods for named instance attributes. These helper methods will return the hard coded values from the data file. Calling these methods does not require a database connection.


```ruby
class Status < ApplicationRecord
include SupportTableData

named_instance_attribute_helpers :id
end

Status.pending_id # => 1
Status.in_progress_id # => 2
Status.completed_id # => 3
```

### Caching

You can use the companion [support_table_cache gem](https://github.com/bdurand/support_table_cache) to add caching support to your models. That way your application won't need to constantly query the database for records that will never change.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
1.0.0
1.1.0
93 changes: 75 additions & 18 deletions lib/support_table_data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,30 @@ def add_support_table_data(data_file_path)
define_support_table_named_instances
end

# Add class methods to get attributes for named instances. The methods will be named
# like `#{instance_name}_#{attribute_name}`. For example, if the name is "active" and the
# attribute is "id", then the method will be "active_id" and you can call
# `Model.active_id` to get the value.
#
# @param attributes [String, Symbol] The names of the attributes to add helper methods for.
# @return [void]
def named_instance_attribute_helpers(*attributes)
@support_table_attribute_helpers ||= {}
attributes.flatten.collect(&:to_s).each do |attribute|
@support_table_attribute_helpers[attribute] = []
end
define_support_table_named_instances
end

# Get the names of any named instance attribute helpers that have been defined
# with `named_instance_attribute_helpers`.
#
# @return [Array<String>] List of attribute names.
def support_table_attribute_helpers
@support_table_attribute_helpers ||= {}
@support_table_attribute_helpers.keys
end

# Get the data for the support table from the data files.
#
# @return [Array<Hash>] List of attributes for all records in the data files.
Expand Down Expand Up @@ -135,35 +159,52 @@ def protected_instance?(instance)
def define_support_table_named_instances
@support_table_data_files ||= []
@support_table_instance_names ||= Set.new
key_attribute = (support_table_key_attribute || primary_key).to_s

@support_table_data_files.each do |file_path|
data = support_table_parse_data_file(file_path)
if data.is_a?(Hash)
data.each do |key, attributes|
method_name = key.to_s.freeze
next if method_name.start_with?("_")
next unless data.is_a?(Hash)

unless attributes.is_a?(Hash)
raise ArgumentError.new("Cannot define named instance #{method_name} on #{name}; value must be a Hash")
end
data.each do |name, attributes|
define_support_table_named_instance_methods(name, attributes)
end
end
end

unless method_name.match?(/\A[a-z][a-z0-9_]+\z/)
raise ArgumentError.new("Cannot define named instance #{method_name} on #{name}; name contains illegal characters")
end
def define_support_table_named_instance_methods(name, attributes)
method_name = name.to_s.freeze
return if method_name.start_with?("_")

unless @support_table_instance_names.include?(method_name)
@support_table_instance_names << method_name
key_value = attributes[key_attribute]
define_support_table_instance_helper(method_name, key_attribute, key_value)
define_support_table_predicates_helper("#{method_name}?", key_attribute, key_value)
end
end
unless attributes.is_a?(Hash)
raise ArgumentError.new("Cannot define named instance #{method_name} on #{name}; value must be a Hash")
end

unless method_name.match?(/\A[a-z][a-z0-9_]+\z/)
raise ArgumentError.new("Cannot define named instance #{method_name} on #{name}; name contains illegal characters")
end

key_attribute = (support_table_key_attribute || primary_key).to_s
key_value = attributes[key_attribute]

unless @support_table_instance_names.include?(method_name)
define_support_table_instance_helper(method_name, key_attribute, key_value)
define_support_table_predicates_helper("#{method_name}?", key_attribute, key_value)
@support_table_instance_names << method_name
end

if defined?(@support_table_attribute_helpers)
@support_table_attribute_helpers.each do |attribute_name, defined_methods|
attribute_method_name = "#{method_name}_#{attribute_name}"
next if defined_methods.include?(attribute_method_name)

define_support_table_instance_attribute_helper(attribute_method_name, attributes[attribute_name])
defined_methods << attribute_method_name
end
end
end

def define_support_table_instance_helper(method_name, attribute_name, attribute_value)
return if @support_table_instance_names.include?("self.#{method_name}")

if respond_to?(method_name, true)
raise ArgumentError.new("Could not define support table helper method #{name}.#{method_name} because it is already a defined method")
end
Expand All @@ -175,7 +216,23 @@ def self.#{method_name}
RUBY
end

def define_support_table_instance_attribute_helper(method_name, attribute_value)
return if @support_table_instance_names.include?("self.#{method_name}")

if respond_to?(method_name, true)
raise ArgumentError.new("Could not define support table helper method #{name}.#{method_name} because it is already a defined method")
end

class_eval <<~RUBY, __FILE__, __LINE__ + 1
def self.#{method_name}
#{attribute_value.inspect}
end
RUBY
end

def define_support_table_predicates_helper(method_name, attribute_name, attribute_value)
return if @support_table_instance_names.include?(method_name)

if method_defined?(method_name) || private_method_defined?(method_name)
raise ArgumentError.new("Could not define support table helper method #{name}##{method_name} because it is already a defined method")
end
Expand Down
4 changes: 4 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,12 @@ class Group < ActiveRecord::Base

self.primary_key = :group_id

named_instance_attribute_helpers :group_id

add_support_table_data "groups.yml"

named_instance_attribute_helpers :name

validates_uniqueness_of :name
end

Expand Down
16 changes: 16 additions & 0 deletions spec/support_table_data_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,22 @@
end
end

describe "named_instance_attribute_helpers" do
it "adds helper methods for each attribute on named instances" do
expect(Group.primary_group_id).to eq 1
expect(Group.secondary_group_id).to eq 2
expect(Group.gray_group_id).to eq 3
expect(Group.primary_name).to eq "primary"
expect(Group.secondary_name).to eq "secondary"
expect(Group.gray_name).to eq "gray"
end

it "can get a list of the defined attribute helpers" do
expect(Group.support_table_attribute_helpers).to match_array ["group_id", "name"]
expect(Color.support_table_attribute_helpers).to match_array []
end
end

describe "protected_instance?" do
it "returns true if the instance came from a data file" do
red = Color.new
Expand Down

0 comments on commit b84deb7

Please sign in to comment.