minitwin 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +7 -0
- data/README.md +99 -0
- data/USAGE.md +390 -0
- data/lib/minitwin/assignment.rb +95 -0
- data/lib/minitwin/class_methods/caches.rb +94 -0
- data/lib/minitwin/class_methods/coercion.rb +114 -0
- data/lib/minitwin/class_methods/constructors.rb +120 -0
- data/lib/minitwin/class_methods/dsl.rb +404 -0
- data/lib/minitwin/class_methods/rbs.rb +129 -0
- data/lib/minitwin/class_methods/types_helper.rb +70 -0
- data/lib/minitwin/class_methods.rb +26 -0
- data/lib/minitwin/initialization.rb +188 -0
- data/lib/minitwin/railtie.rb +9 -0
- data/lib/minitwin/serialization.rb +172 -0
- data/lib/minitwin/sync.rb +137 -0
- data/lib/minitwin/version.rb +5 -0
- data/lib/minitwin.rb +129 -0
- data/lib/tasks/minitwin.rake +70 -0
- data/sig/generated/minitwin/assignment.rbs +29 -0
- data/sig/generated/minitwin/class_methods/caches.rbs +40 -0
- data/sig/generated/minitwin/class_methods/constructors.rbs +41 -0
- data/sig/generated/minitwin/class_methods/dsl.rbs +79 -0
- data/sig/generated/minitwin/class_methods/rbs.rbs +20 -0
- data/sig/generated/minitwin/initialization.rbs +45 -0
- data/sig/generated/minitwin/serialization.rbs +47 -0
- data/sig/generated/minitwin/sync.rbs +19 -0
- data/sig/module.rbs +19 -0
- metadata +89 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# rbs_inline: enabled
|
|
3
|
+
|
|
4
|
+
class Minitwin
|
|
5
|
+
module Sync
|
|
6
|
+
# Cache constant reference for JIT optimization
|
|
7
|
+
MODEL_PREFIX = Minitwin::INTERNAL_MODEL_PREFIX
|
|
8
|
+
|
|
9
|
+
#: (untyped, validate: bool) -> bool
|
|
10
|
+
def sync(model = nil, validate: true)
|
|
11
|
+
# Resolve target model
|
|
12
|
+
target_model = model
|
|
13
|
+
if target_model.nil?
|
|
14
|
+
ivar = self.class.internal_model_name("model")
|
|
15
|
+
target_model = instance_variable_defined?(ivar) ? instance_variable_get(ivar) : nil
|
|
16
|
+
if target_model.nil?
|
|
17
|
+
any_ivar = instance_variables.find { |v| v.to_s.start_with?(MODEL_PREFIX) }
|
|
18
|
+
target_model = instance_variable_get(any_ivar) if any_ivar
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
return false if validate && !valid?
|
|
23
|
+
return false if target_model.nil?
|
|
24
|
+
|
|
25
|
+
attribute_methods.each do |method_name| # rubocop: disable Metrics/BlockLength
|
|
26
|
+
prop_meta = self.class.properties[method_name] || self.class.collections[method_name]
|
|
27
|
+
as_name = prop_meta&.[](:as)
|
|
28
|
+
target_name = as_name.is_a?(Symbol) || as_name.is_a?(String) ? as_name.to_sym : method_name
|
|
29
|
+
|
|
30
|
+
writer = :"#{target_name}="
|
|
31
|
+
next unless target_model.respond_to?(writer) || target_model.respond_to?(target_name)
|
|
32
|
+
|
|
33
|
+
value = respond_to?(method_name, true) ? send(method_name) : nil
|
|
34
|
+
|
|
35
|
+
# Nested twins
|
|
36
|
+
if value.is_a?(Minitwin)
|
|
37
|
+
if target_model.respond_to?(target_name)
|
|
38
|
+
begin
|
|
39
|
+
child_model = target_model.public_send(target_name)
|
|
40
|
+
if child_model
|
|
41
|
+
value.sync(child_model, validate: false)
|
|
42
|
+
next
|
|
43
|
+
end
|
|
44
|
+
rescue StandardError
|
|
45
|
+
# fall through to writer
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
elsif value.is_a?(Array)
|
|
49
|
+
if target_model.respond_to?(target_name)
|
|
50
|
+
begin
|
|
51
|
+
coll = target_model.public_send(target_name)
|
|
52
|
+
if coll.respond_to?(:each)
|
|
53
|
+
deep_synced_any = false
|
|
54
|
+
|
|
55
|
+
id_map = build_target_id_lookup(coll)
|
|
56
|
+
|
|
57
|
+
value.each_with_index do |elem, idx|
|
|
58
|
+
next unless elem.is_a?(Minitwin)
|
|
59
|
+
|
|
60
|
+
target = nil
|
|
61
|
+
|
|
62
|
+
# Try id-based match first
|
|
63
|
+
target ||= begin
|
|
64
|
+
elem_id = elem.respond_to?(:id, true) ? elem.send(:id) : nil
|
|
65
|
+
id_map && elem_id ? id_map[elem_id] : nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
if target.nil? && coll.respond_to?(:[])
|
|
69
|
+
begin
|
|
70
|
+
target = coll[idx]
|
|
71
|
+
rescue StandardError
|
|
72
|
+
target = nil
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
next unless target
|
|
77
|
+
|
|
78
|
+
elem.sync(target, validate: false)
|
|
79
|
+
deep_synced_any = true
|
|
80
|
+
next
|
|
81
|
+
|
|
82
|
+
# Unmatched elements are handled later by writer fallback
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
next if deep_synced_any
|
|
86
|
+
end
|
|
87
|
+
rescue StandardError
|
|
88
|
+
# continue to writer fallback
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
assignable = case value
|
|
94
|
+
when Minitwin
|
|
95
|
+
value.to_hash
|
|
96
|
+
when Array
|
|
97
|
+
value.map { |v| v.is_a?(Minitwin) ? v.to_hash : v }
|
|
98
|
+
else
|
|
99
|
+
value
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
begin
|
|
103
|
+
target_model.public_send(writer, assignable)
|
|
104
|
+
rescue StandardError
|
|
105
|
+
# ignore and continue
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
true
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
# Builds a lookup hash from the provided collection.
|
|
115
|
+
# The collection may be an array or something convertible to an array.
|
|
116
|
+
# The result is a hash map with the ids of the models in the collection as
|
|
117
|
+
# keys and the models itself as values.
|
|
118
|
+
def build_target_id_lookup(coll)
|
|
119
|
+
ary = if coll.is_a?(Array)
|
|
120
|
+
coll
|
|
121
|
+
elsif coll.respond_to?(:to_a)
|
|
122
|
+
coll.to_a
|
|
123
|
+
else
|
|
124
|
+
return nil
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
ary.each_with_object({}) do |m, h|
|
|
128
|
+
next unless m.respond_to?(:id)
|
|
129
|
+
|
|
130
|
+
key = m.id
|
|
131
|
+
h[key] = m if key
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# No append or match_on helpers
|
|
136
|
+
end
|
|
137
|
+
end
|
data/lib/minitwin.rb
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
require "zeitwerk"
|
|
5
|
+
begin
|
|
6
|
+
require "dry-types"
|
|
7
|
+
rescue LoadError
|
|
8
|
+
# dry-types is an optional dependency
|
|
9
|
+
end
|
|
10
|
+
require_relative "minitwin/version"
|
|
11
|
+
|
|
12
|
+
begin
|
|
13
|
+
require "active_model"
|
|
14
|
+
rescue LoadError
|
|
15
|
+
# active_model is an optional dependency
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Setup Zeitwerk loader for the gem to support Rails autoloading
|
|
19
|
+
loader = Zeitwerk::Loader.for_gem
|
|
20
|
+
loader.setup
|
|
21
|
+
|
|
22
|
+
class Minitwin
|
|
23
|
+
# Constants for internal variable naming
|
|
24
|
+
INTERNAL_MODEL_PREFIX = "@internal_model__"
|
|
25
|
+
NESTED_READER_PREFIX = "__nested_read__"
|
|
26
|
+
DYNAMIC_ALIASES_VAR = "@__dynamic_aliases__"
|
|
27
|
+
DYNAMIC_ALIASES_REV_VAR = "@__dynamic_aliases_rev__"
|
|
28
|
+
|
|
29
|
+
# Shared utility methods
|
|
30
|
+
module Utils
|
|
31
|
+
module_function
|
|
32
|
+
|
|
33
|
+
# Normalize property name to instance variable name (handles ? suffix)
|
|
34
|
+
def ivar_name(name)
|
|
35
|
+
"@#{name}".delete_suffix("?")
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Convert instance variable to attribute key symbol
|
|
39
|
+
def ivar_to_key(ivar)
|
|
40
|
+
ivar.to_s.delete_prefix("@").to_sym
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Traverse a nested path on an object
|
|
44
|
+
def traverse_path(obj, path)
|
|
45
|
+
path.each { |seg| obj = obj.public_send(seg) }
|
|
46
|
+
obj
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
include ActiveModel::Model if defined?(ActiveModel::Model)
|
|
51
|
+
include Minitwin::Initialization
|
|
52
|
+
include Minitwin::Assignment
|
|
53
|
+
include Minitwin::Serialization
|
|
54
|
+
include Minitwin::Sync
|
|
55
|
+
extend Minitwin::ClassMethods
|
|
56
|
+
|
|
57
|
+
# Lazy-memoized lookups for late-loaded optional deps (Dry::Types,
|
|
58
|
+
# ActiveSupport's HashWithIndifferentAccess). Resolved on first call so
|
|
59
|
+
# boot-order between minitwin and the deps does not matter.
|
|
60
|
+
class << self
|
|
61
|
+
def hash_klass
|
|
62
|
+
@hash_klass ||= defined?(HashWithIndifferentAccess) ? HashWithIndifferentAccess : Hash
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def coercion_error_classes
|
|
68
|
+
@coercion_error_classes ||= begin
|
|
69
|
+
arr = [TypeError, ArgumentError]
|
|
70
|
+
arr.unshift(::Dry::Types::CoercionError) if defined?(::Dry::Types::CoercionError)
|
|
71
|
+
arr.freeze
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def active_model_initialized?(klass)
|
|
76
|
+
return false unless defined?(ActiveModel::API) || defined?(ActiveModel::Model)
|
|
77
|
+
|
|
78
|
+
ancestors = klass.ancestors
|
|
79
|
+
(defined?(ActiveModel::API) && ancestors.include?(ActiveModel::API)) ||
|
|
80
|
+
(defined?(ActiveModel::Model) && ancestors.include?(ActiveModel::Model))
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Track descendants via WeakMap so anonymous subclasses can be GC'd.
|
|
85
|
+
# The map lives only on Minitwin itself; subclasses' inherited hooks
|
|
86
|
+
# delegate up so that deep hierarchies still register on the root.
|
|
87
|
+
@__descendants_map__ = ObjectSpace::WeakMap.new
|
|
88
|
+
class << self
|
|
89
|
+
def __descendants__
|
|
90
|
+
Minitwin.instance_variable_get(:@__descendants_map__).keys
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def inherited(sub)
|
|
94
|
+
Minitwin.instance_variable_get(:@__descendants_map__)[sub] = true
|
|
95
|
+
super
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Optional: Generate RBS on exit when configured via ENV.
|
|
101
|
+
if ENV["MINITWIN_RBS_OUT"] && !ENV["MINITWIN_RBS_OUT"].empty?
|
|
102
|
+
at_exit do
|
|
103
|
+
require "fileutils"
|
|
104
|
+
path = ENV["MINITWIN_RBS_OUT"]
|
|
105
|
+
begin
|
|
106
|
+
twins = Minitwin.__descendants__.select(&:name)
|
|
107
|
+
content = +""
|
|
108
|
+
content << "# This file is generated by minitwin. DO NOT EDIT.\n"
|
|
109
|
+
content << "# Generated at: #{Time.now}\n\n"
|
|
110
|
+
twins.sort_by!(&:name)
|
|
111
|
+
twins.each do |k|
|
|
112
|
+
content << k.to_rbs
|
|
113
|
+
content << "\n\n"
|
|
114
|
+
end
|
|
115
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
116
|
+
File.write(path, content)
|
|
117
|
+
rescue StandardError => exception
|
|
118
|
+
warn "[minitwin] Failed to generate RBS: #{exception.class}: #{exception.message}"
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
require "minitwin/railtie" if defined?(Rails::Railtie)
|
|
124
|
+
# Minitwin
|
|
125
|
+
# --------
|
|
126
|
+
# Entry point that wires together the core modules and conditionally enables
|
|
127
|
+
# ActiveModel integration when it is available. The goal is to keep runtime
|
|
128
|
+
# dependencies minimal while providing a clean DSL for twin/presenter objects.
|
|
129
|
+
# See docs/ARCHITECTURE.md for an overview.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../minitwin"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
namespace :minitwin do
|
|
7
|
+
desc "Generate RBS signatures for all loaded Minitwin subclasses into sig/generated/ " \
|
|
8
|
+
"(override output dir with OUTPUT_DIR env var or task argument)"
|
|
9
|
+
|
|
10
|
+
task :generate_rbs, [:output_dir] do |_t, args|
|
|
11
|
+
# In Rails, invoke :environment to ensure the app is fully initialized
|
|
12
|
+
# before eager loading. We invoke it here rather than declaring it as a
|
|
13
|
+
# prerequisite because Rails defines :environment *after* running railtie
|
|
14
|
+
# rake_tasks blocks, so Rake::Task.task_defined?(:environment) is always
|
|
15
|
+
# false at rake file load time.
|
|
16
|
+
Rake::Task[:environment].invoke if Rake::Task.task_defined?(:environment)
|
|
17
|
+
|
|
18
|
+
# Eager load the rails app if the task runs in Rails context
|
|
19
|
+
Rails.application.eager_load! if defined?(Rails)
|
|
20
|
+
|
|
21
|
+
# Require files "manually" in non-rails projects.
|
|
22
|
+
# Provide pattern for file paths as ENV var:
|
|
23
|
+
#
|
|
24
|
+
# MINITWIN_REQUIRE_GLOB="lib/**/*.rb" rake minitwin:generate_rbs
|
|
25
|
+
#
|
|
26
|
+
if (glob = ENV.fetch("MINITWIN_REQUIRE_GLOB", nil))
|
|
27
|
+
Dir[glob].each { |f| require File.expand_path(f) }
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
output_dir = args[:output_dir] || ENV.fetch("MINITWIN_RBS_DIR", "sig/generated")
|
|
31
|
+
|
|
32
|
+
twins = Minitwin.__descendants__.select(&:name).sort_by(&:name)
|
|
33
|
+
|
|
34
|
+
if twins.empty?
|
|
35
|
+
warn "[minitwin] No Minitwin subclasses found. " \
|
|
36
|
+
"Ensure your classes are loaded before running this task."
|
|
37
|
+
next
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
twins.each do |klass|
|
|
41
|
+
file_path = klass.name.
|
|
42
|
+
gsub("::", "/").
|
|
43
|
+
gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2').
|
|
44
|
+
gsub(/([a-z\d])([A-Z])/, '\1_\2').
|
|
45
|
+
downcase.
|
|
46
|
+
concat(".rbs")
|
|
47
|
+
|
|
48
|
+
output_path = File.join(output_dir, file_path)
|
|
49
|
+
FileUtils.mkdir_p(File.dirname(output_path))
|
|
50
|
+
File.write(output_path, "# Generated by minitwin. DO NOT EDIT.\n\n#{klass.to_rbs}\n")
|
|
51
|
+
puts " [minitwin] #{output_path}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc "Check that generated RBS signatures are up to date"
|
|
56
|
+
task :check_rbs do
|
|
57
|
+
FileUtils.rm_rf(Dir["sig/generated/**/*.rbs"])
|
|
58
|
+
sh "bundle exec rbs-inline --output lib"
|
|
59
|
+
|
|
60
|
+
diff = `git diff --name-only sig/generated`
|
|
61
|
+
untracked = `git ls-files --others --exclude-standard sig/generated`
|
|
62
|
+
|
|
63
|
+
unless diff.empty? && untracked.empty?
|
|
64
|
+
warn "[minitwin] RBS signatures are out of date. Run: bundle exec rbs-inline --output lib"
|
|
65
|
+
warn diff unless diff.empty?
|
|
66
|
+
warn untracked unless untracked.empty?
|
|
67
|
+
exit 1
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Generated from lib/minitwin/assignment.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
# Assign/update helpers to merge incoming data into an existing twin.
|
|
5
|
+
# - assign_object: copy readable attributes from an object and remember it
|
|
6
|
+
# - assign_hash / assign_params: update only known attributes, recursing into
|
|
7
|
+
# nested twins and collection items when possible
|
|
8
|
+
module Assignment
|
|
9
|
+
# Mirror values from a model's getters into this twin's setters
|
|
10
|
+
# : (untyped) -> instance
|
|
11
|
+
def to_object: (untyped) -> instance
|
|
12
|
+
|
|
13
|
+
# : (untyped) -> instance
|
|
14
|
+
def assign_object: (untyped) -> instance
|
|
15
|
+
|
|
16
|
+
# : (Hash[ String | Symbol, untyped ] hash) -> instance
|
|
17
|
+
def assign_hash: (Hash[String | Symbol, untyped] hash) -> instance
|
|
18
|
+
|
|
19
|
+
# Actually, this is expected to be an `ActionController::Parameters`
|
|
20
|
+
# object. The type will be unknown when used without rails. So for RBS
|
|
21
|
+
# the argument is typed `untyped`.
|
|
22
|
+
# : (untyped params) -> instance
|
|
23
|
+
def assign_params: (untyped params) -> instance
|
|
24
|
+
|
|
25
|
+
# Gets the non-readonly methods, which can be assigned with new values.
|
|
26
|
+
# : () -> Array[Symbol]
|
|
27
|
+
def assignable_attribute_methods: () -> Array[Symbol]
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# Generated from lib/minitwin/class_methods/caches.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
module ClassMethods
|
|
5
|
+
module Caches
|
|
6
|
+
# : () -> bool
|
|
7
|
+
def dynamic_aliases?: () -> bool
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def invalidate_caches: () -> untyped
|
|
12
|
+
|
|
13
|
+
def serializable_getters: () -> untyped
|
|
14
|
+
|
|
15
|
+
# Methods defined directly on the twin class hierarchy: this class and any
|
|
16
|
+
# intermediate Minitwin subclasses, but not Minitwin itself. Methods mixed
|
|
17
|
+
# in via modules (e.g. ActionView helpers, which include arg-taking methods
|
|
18
|
+
# like #link_to) are excluded because instance_methods(false) reports only
|
|
19
|
+
# methods owned by the class, not by included modules.
|
|
20
|
+
def serializable_method_candidates: () -> untyped
|
|
21
|
+
|
|
22
|
+
# Base names of every declared property and collection across the twin
|
|
23
|
+
# class hierarchy. Only methods whose declaration name is one of these
|
|
24
|
+
# serialize, so plain getters hand-defined on a twin are excluded. `as:`
|
|
25
|
+
# aliases pass because #original_name maps them back to the original
|
|
26
|
+
# property (the aliased reader is made protected and rejected separately).
|
|
27
|
+
def declared_property_keys: () -> untyped
|
|
28
|
+
|
|
29
|
+
# This class and any intermediate Minitwin subclasses, but not Minitwin
|
|
30
|
+
# itself or mixed-in modules.
|
|
31
|
+
def twin_class_hierarchy: () -> untyped
|
|
32
|
+
|
|
33
|
+
def allowed_attribute_keys: () -> untyped
|
|
34
|
+
|
|
35
|
+
def allowed_attribute_keys_array: () -> untyped
|
|
36
|
+
|
|
37
|
+
def setter_methods: () -> untyped
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Generated from lib/minitwin/class_methods/constructors.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
module ClassMethods
|
|
5
|
+
module Constructors
|
|
6
|
+
# : () -> Hash[untyped, untyped]
|
|
7
|
+
def properties: () -> Hash[untyped, untyped]
|
|
8
|
+
|
|
9
|
+
# : () -> Hash[untyped, untyped]
|
|
10
|
+
def collections: () -> Hash[untyped, untyped]
|
|
11
|
+
|
|
12
|
+
# : (Hash[untyped, untyped] args) -> instance
|
|
13
|
+
def from_hash: (Hash[untyped, untyped] args) -> instance
|
|
14
|
+
|
|
15
|
+
# : (String body) -> instance
|
|
16
|
+
def from_json: (String body) -> instance
|
|
17
|
+
|
|
18
|
+
# Actually, this is expected to be an `ActionController::Parameters`
|
|
19
|
+
# object. The type will be unknown when used without rails. So for RBS
|
|
20
|
+
# the argument is typed `untyped`.
|
|
21
|
+
# : (untyped params) -> instance
|
|
22
|
+
def from_params: (untyped params) -> instance
|
|
23
|
+
|
|
24
|
+
# : (untyped model) -> instance
|
|
25
|
+
def from_object: (untyped model) -> instance
|
|
26
|
+
|
|
27
|
+
# : (Hash[Symbol, untyped] **models) -> instance
|
|
28
|
+
def from_objects: (**untyped models) -> untyped
|
|
29
|
+
|
|
30
|
+
# : (Array[untyped] models) -> Array[instance]
|
|
31
|
+
def from_collection: (Array[untyped] models) -> Array[instance]
|
|
32
|
+
|
|
33
|
+
# : (Symbol name) -> String | nil
|
|
34
|
+
def internal_model_name: (Symbol name) -> String
|
|
35
|
+
|
|
36
|
+
def enrich_attributes_from_models!: (untyped attributes, untyped models) -> untyped
|
|
37
|
+
|
|
38
|
+
def enrich_attribute_from_models: (untyped attributes, untyped models, untyped key, is_collection: untyped) -> untyped
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Generated from lib/minitwin/class_methods/dsl.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
module ClassMethods
|
|
5
|
+
module Dsl
|
|
6
|
+
# : () -> Array[Symbol]
|
|
7
|
+
def block_properties: () -> Array[Symbol]
|
|
8
|
+
|
|
9
|
+
# : () -> Array[Symbol]
|
|
10
|
+
def collection_properties: () -> Array[Symbol]
|
|
11
|
+
|
|
12
|
+
# : () -> Array[Symbol]
|
|
13
|
+
def unexposed_properties: () -> Array[Symbol]
|
|
14
|
+
|
|
15
|
+
# : () -> Array[Symbol]
|
|
16
|
+
def property_order: () -> Array[Symbol]
|
|
17
|
+
|
|
18
|
+
# : () -> Array[Hash]
|
|
19
|
+
def dynamic_nested_aliases: () -> Array[Hash]
|
|
20
|
+
|
|
21
|
+
# @rbs name: Symbol
|
|
22
|
+
# @rbs validates: Hash[Symbol, untyped]
|
|
23
|
+
# @rbs default: untyped
|
|
24
|
+
# @rbs as: Symbol | Proc
|
|
25
|
+
# @rbs getter: Proc
|
|
26
|
+
# @rbs twin: untyped
|
|
27
|
+
# @rbs on: Symbol
|
|
28
|
+
# @rbs return: void
|
|
29
|
+
def collection: (Symbol name, ?validates: Hash[Symbol, untyped], ?default: untyped, ?as: untyped, ?getter: Proc, ?twin: untyped, ?on: Symbol, **untyped _opts) ?{ (?) -> untyped } -> void
|
|
30
|
+
|
|
31
|
+
# @rbs name: Symbol
|
|
32
|
+
# @rbs validates: Hash[Symbol, untyped]
|
|
33
|
+
# @rbs default: untyped
|
|
34
|
+
# @rbs as: Symbol | Proc
|
|
35
|
+
# @rbs expose: bool
|
|
36
|
+
# @rbs readonly: bool
|
|
37
|
+
# @rbs type: untyped
|
|
38
|
+
# @rbs getter: Proc
|
|
39
|
+
# @rbs setter: Proc
|
|
40
|
+
# @rbs twin: untyped
|
|
41
|
+
# @rbs on: Symbol
|
|
42
|
+
# @rbs return: void
|
|
43
|
+
def property: (Symbol name, ?validates: Hash[Symbol, untyped], ?default: untyped, ?as: untyped, ?expose: bool, ?readonly: bool, ?type: untyped, ?getter: Proc, ?setter: Proc, ?twin: untyped, ?on: Symbol, **untyped _opts) ?{ (?) -> untyped } -> void
|
|
44
|
+
|
|
45
|
+
# @rbs name: Symbol
|
|
46
|
+
# @rbs as: Symbol | Proc
|
|
47
|
+
# @rbs return: void
|
|
48
|
+
def nested: (Symbol name, ?as: untyped) ?{ (?) -> untyped } -> void
|
|
49
|
+
|
|
50
|
+
private
|
|
51
|
+
|
|
52
|
+
def constantize_name: (untyped name) -> untyped
|
|
53
|
+
|
|
54
|
+
def create_nested_class: (name: untyped) ?{ (?) -> untyped } -> untyped
|
|
55
|
+
|
|
56
|
+
def define_getter_method: (name: untyped, as: untyped, on: untyped, default: untyped, getter: untyped, ?type: untyped) -> untyped
|
|
57
|
+
|
|
58
|
+
def build_getter_proc: (name: untyped, on: untyped, default: untyped, getter: untyped, type: untyped) -> untyped
|
|
59
|
+
|
|
60
|
+
def build_composition_getter: (name: untyped, on: untyped, default: untyped, type: untyped) -> untyped
|
|
61
|
+
|
|
62
|
+
def build_regular_getter: (name: untyped, default: untyped, type: untyped) -> untyped
|
|
63
|
+
|
|
64
|
+
def apply_alias_to_getter: (name: untyped, as: untyped) -> untyped
|
|
65
|
+
|
|
66
|
+
def add_validation: (name: untyped, validates: untyped) -> untyped
|
|
67
|
+
|
|
68
|
+
def add_unexposed_property: (name: untyped, expose: untyped) -> untyped
|
|
69
|
+
|
|
70
|
+
def add_block_property: (name: untyped) -> untyped
|
|
71
|
+
|
|
72
|
+
def add_collection_property: (name: untyped) -> untyped
|
|
73
|
+
|
|
74
|
+
def add_to_property_order: (untyped name) -> untyped
|
|
75
|
+
|
|
76
|
+
def resolve_default_value: (untyped default, untyped type) -> untyped
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Generated from lib/minitwin/class_methods/rbs.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
module ClassMethods
|
|
5
|
+
module Rbs
|
|
6
|
+
# : () -> String
|
|
7
|
+
def to_rbs: () -> String
|
|
8
|
+
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def rbs_type_for: (untyped meta) -> untyped
|
|
12
|
+
|
|
13
|
+
def rbs_elem_type_for: (untyped meta) -> untyped
|
|
14
|
+
|
|
15
|
+
def rbs_class_name: (untyped klass) -> untyped
|
|
16
|
+
|
|
17
|
+
def dry_type_to_rbs: (untyped dry_type) -> untyped
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# Generated from lib/minitwin/initialization.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
# Instance construction and low-level helpers used by the DSL-generated
|
|
5
|
+
# accessors. Filters unknown keys on initialize and seeds nested block
|
|
6
|
+
# properties so that validations on nested twins can run.
|
|
7
|
+
module Initialization
|
|
8
|
+
# Cache constant references for JIT optimization
|
|
9
|
+
ALIASES_VAR: untyped
|
|
10
|
+
|
|
11
|
+
ALIASES_REV_VAR: untyped
|
|
12
|
+
|
|
13
|
+
# Forbidden method names that should never be aliased for security reasons
|
|
14
|
+
FORBIDDEN_ALIAS_NAMES: untyped
|
|
15
|
+
|
|
16
|
+
# : (**untyped) -> instance
|
|
17
|
+
def initialize: (**untyped) -> instance
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def assign_attribute: (method: untyped, value: untyped) -> untyped
|
|
22
|
+
|
|
23
|
+
def attribute_methods: () -> untyped
|
|
24
|
+
|
|
25
|
+
def define_instance_variable: (name: untyped, value: untyped) -> untyped
|
|
26
|
+
|
|
27
|
+
# Define or update per-instance alias methods for properties/collections
|
|
28
|
+
# where `as:` was provided as a Proc. The Proc is executed in the context
|
|
29
|
+
# of the instance to compute the alias name.
|
|
30
|
+
def __recompute_dynamic_aliases__: () -> untyped
|
|
31
|
+
|
|
32
|
+
def __recompute_aliases_for_collection__: (untyped collection_method) -> untyped
|
|
33
|
+
|
|
34
|
+
def __recompute_nested_aliases__: () -> untyped
|
|
35
|
+
|
|
36
|
+
def __compute_alias_name__: (untyped as_proc) -> untyped
|
|
37
|
+
|
|
38
|
+
def __compute_nested_alias_name__: (untyped entry) -> untyped
|
|
39
|
+
|
|
40
|
+
def __apply_dynamic_alias__: (untyped target_method, untyped alias_name) -> untyped
|
|
41
|
+
|
|
42
|
+
# Public: expose current dynamic aliases as a Hash of alias_name => target_method
|
|
43
|
+
def dynamic_aliases: () -> untyped
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Generated from lib/minitwin/serialization.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
# Serialization to Hash/JSON and ActiveModel validation aggregation.
|
|
5
|
+
# Converts nested twins recursively and preserves array items (dropping only
|
|
6
|
+
# nil). When ActiveModel validations are available, nested errors are
|
|
7
|
+
# surfaced on the parent using dot/bracket notation.
|
|
8
|
+
module Serialization
|
|
9
|
+
# Cache constant references for JIT optimization
|
|
10
|
+
ALIASES_VAR: untyped
|
|
11
|
+
|
|
12
|
+
NESTED_PREFIX: untyped
|
|
13
|
+
|
|
14
|
+
# : (render_nil: bool) -> Hash[Symbol, untyped]
|
|
15
|
+
def to_hash: (render_nil: bool) -> Hash[Symbol, untyped]
|
|
16
|
+
|
|
17
|
+
alias to_h to_hash
|
|
18
|
+
|
|
19
|
+
# : (**untyped) -> String
|
|
20
|
+
def to_json: (**untyped) -> String
|
|
21
|
+
|
|
22
|
+
# : () -> Hash[Symbol, untyped]
|
|
23
|
+
def attributes: () -> Hash[Symbol, untyped]
|
|
24
|
+
|
|
25
|
+
# : () -> bool
|
|
26
|
+
def valid?: () -> bool
|
|
27
|
+
|
|
28
|
+
# : () -> String
|
|
29
|
+
def inspect: () -> String
|
|
30
|
+
|
|
31
|
+
# Internal helper for PrettyPrint. Do not call this on your own.
|
|
32
|
+
# : (PP) -> void
|
|
33
|
+
def pretty_print: (PP) -> void
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def transform_value_for_serialization: (untyped value) -> untyped
|
|
38
|
+
|
|
39
|
+
def ordered_attributes_for_pp: () -> untyped
|
|
40
|
+
|
|
41
|
+
def ordered_methods_for_pp: () -> untyped
|
|
42
|
+
|
|
43
|
+
def display_name_for: (untyped method) -> untyped
|
|
44
|
+
|
|
45
|
+
def dynamic_aliases_for_pp: () -> untyped
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Generated from lib/minitwin/sync.rb with RBS::Inline
|
|
2
|
+
|
|
3
|
+
class Minitwin
|
|
4
|
+
module Sync
|
|
5
|
+
# Cache constant reference for JIT optimization
|
|
6
|
+
MODEL_PREFIX: untyped
|
|
7
|
+
|
|
8
|
+
# : (untyped, validate: bool) -> bool
|
|
9
|
+
def sync: (untyped, validate: bool) -> bool
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
# Builds a lookup hash from the provided collection.
|
|
14
|
+
# The collection may be an array or something convertible to an array.
|
|
15
|
+
# The result is a hash map with the ids of the models in the collection as
|
|
16
|
+
# keys and the models itself as values.
|
|
17
|
+
def build_target_id_lookup: (untyped coll) -> untyped
|
|
18
|
+
end
|
|
19
|
+
end
|
data/sig/module.rbs
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# This should come from Dry Types, but they do not ship rbs signatures.
|
|
2
|
+
|
|
3
|
+
module Types
|
|
4
|
+
module Params
|
|
5
|
+
String: untyped
|
|
6
|
+
Nil: untyped
|
|
7
|
+
Date: untyped
|
|
8
|
+
DateTime: untyped
|
|
9
|
+
Time: untyped
|
|
10
|
+
True: untyped
|
|
11
|
+
False: untyped
|
|
12
|
+
Bool: untyped
|
|
13
|
+
Integer: untyped
|
|
14
|
+
Float: untyped
|
|
15
|
+
Decimal: untyped
|
|
16
|
+
Array: untyped
|
|
17
|
+
Hash: untyped
|
|
18
|
+
end
|
|
19
|
+
end
|