Class: TypedViewModel::Base
- Inherits:
-
Literal::Data
- Object
- Literal::Data
- TypedViewModel::Base
- Defined in:
- lib/typed_view_model/base.rb
Overview
Base class for all view model objects Provides common functionality and structure
Class Method Summary collapse
-
.__validate_trait_requirements! ⇒ Object
Validates that every name a ‘use`d trait lists in `requires` is available on the host class — either as a declared `prop` or as an instance method.
-
.helpers(*names) ⇒ Object
DSL for including helper modules.
- .new ⇒ Object
-
.use(trait_module) ⇒ Object
Use a trait module that provides model-specific presentation logic.
Class Method Details
.__validate_trait_requirements! ⇒ Object
Validates that every name a ‘use`d trait lists in `requires` is available on the host class — either as a declared `prop` or as an instance method. (The trait’s runtime contract is “I will call ‘.foo` on `self`”; the host can satisfy that with either form.) Idempotent and cheap on warm classes — clears the pending list after the first pass so subsequent `.new` calls are a single `defined?` check.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
# File 'lib/typed_view_model/base.rb', line 45 def __validate_trait_requirements! return unless defined?(@__used_traits_with_requires) return if @__used_traits_with_requires.blank? declared = literal_properties.map { |p| p.name.to_sym }.to_set @__used_traits_with_requires.each do |trait_module| missing = trait_module.required_props.reject do |p| sym = p.to_sym declared.include?(sym) || method_defined?(sym) || private_method_defined?(sym) end next if missing.empty? raise ArgumentError, "TypedViewModel: trait `#{trait_module.name || trait_module.inspect}` " \ "requires #{missing.inspect} on host class `#{name || inspect}` but neither " \ "a declared `prop` nor an instance method matches. Add the missing prop/method " \ "or remove the entry from the trait's `requires` declaration." end @__used_traits_with_requires = nil end |
.helpers(*names) ⇒ Object
DSL for including helper modules. Looks up ‘“#NameHelpers”` in each module listed in `TypedViewModel.helper_namespaces`. Host apps can register their own namespaces:
TypedViewModel.helper_namespaces << MyApp::ViewModelHelpers
Usage:
helpers :i18n, :path, :format
78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 |
# File 'lib/typed_view_model/base.rb', line 78 def helpers(*names) names.each do |name| helper_class_name = "#{name.to_s.camelize}Helpers" helper_module = nil ::TypedViewModel.helper_namespaces.each do |ns| ns_module = resolve_helper_namespace(ns) next unless ns_module.const_defined?(helper_class_name, false) helper_module = ns_module.const_get(helper_class_name, false) break end unless helper_module searched = ::TypedViewModel.helper_namespaces.map { |ns| ns.is_a?(::String) ? ns : ns.name }.join(", ") raise ArgumentError, "Unknown helper: #{name}. Searched: #{searched}" end include helper_module end end |
.new ⇒ Object
64 65 66 67 |
# File 'lib/typed_view_model/base.rb', line 64 def new(...) __validate_trait_requirements! super end |
.use(trait_module) ⇒ Object
Use a trait module that provides model-specific presentation logic. Records the trait so we can validate on first instantiation that every prop the trait ‘requires` is declared on the host class. Validation is deferred to `new` because the conventional pattern is `use Foo` before `prop :bar` — class-define-time validation would false-positive on every VM that follows that ordering.
31 32 33 34 35 36 37 |
# File 'lib/typed_view_model/base.rb', line 31 def use(trait_module) include trait_module if trait_module.respond_to?(:required_props) && trait_module.required_props.any? (@__used_traits_with_requires ||= []) << trait_module end end |