axn 0.1.0.pre.alpha.5 → 0.1.0.pre.alpha.5.1
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 +4 -4
- data/CHANGELOG.md +9 -0
- data/lib/axn/core/context/facade.rb +1 -5
- data/lib/axn/core/contract/validator_class_cache.rb +72 -0
- data/lib/axn/core/contract.rb +54 -5
- data/lib/axn/core/executor.rb +9 -5
- data/lib/axn/core/validation/fields.rb +12 -15
- data/lib/axn/internal/call_logger.rb +22 -0
- data/lib/axn/version.rb +1 -1
- data/lib/axn.rb +9 -2
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3ff42be650685edfd89fd7ca703ea34d6f216caa0f2b1bcb1c6f1d939f5be32b
|
|
4
|
+
data.tar.gz: 3b99fe3510d0db9d62e159c41aa54572b709bfef8425422746ae1b57516c1fec
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c41fc0bf24b4ed5ee67d4c1e43e7d64fe661800c0aac87c3096b80f50707871446a9d844ee4bc54e66992b6fa7980c7d58dcd5d1e238a566c87610b1c62dbad5
|
|
7
|
+
data.tar.gz: e4603c1d65cf9cd152a329faa6f53764a25b39ecc8c96129f067e3c0e5000925412e30d2801d2bc33b0be299e9c15271d4eb6f8507f2726339e97f72f1c585e6
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.0-alpha.5.1
|
|
4
|
+
|
|
5
|
+
* [BUGFIX] The Rails engine now registers even when `axn` is required before Rails. `require "axn"` loaded `axn/rails/engine` only if `Rails` was already defined, and re-requiring axn later is a no-op — so a host whose `spec_helper.rb` requires `axn/testing/spec_helpers` (the placement we document) ahead of the `rails_helper.rb` that boots the app got no engine at all. Without it `app/actions` never received its configured `app_actions_autoload_namespace`, Rails pushed the directory at the root namespace instead, and every constant under it was unresolvable — in the test environment only, so a passing boot said nothing. Introduced in 0.1.0-alpha.5, when `axn/testing/spec_helpers` began requiring `axn/testing` (and through it the full gem). The engine load is now deferred to `ActiveSupport.on_load(:before_configuration)` when Rails is absent at require time.
|
|
6
|
+
|
|
7
|
+
### Performance
|
|
8
|
+
|
|
9
|
+
* [INTERNAL] The one-off ActiveModel validator class for a declared field/subfield is now compiled once per class and reused across every `.call`, instead of being recompiled (and its ActiveModel validator machinery re-instantiated) on every call. `auto_log` no longer builds its before/after payload when the configured logger's own severity level would discard it. `_model_fields` and `_declared_fields` are now cached per class instead of being rebuilt from the full contract on every reader definition / every read. Recovers roughly half of alpha-5's per-call allocation increase on the `basic` benchmark scenario — 489 → 250 objects/call (see PRO-3050).
|
|
10
|
+
* [BREAKING] `Result#declared_fields` now returns a frozen Array. Previously each call built a fresh Array, so appending to the one handed to a given result had no effect beyond that call; the Array is now cached and shared across every call of the class (see above), so mutating it raises `FrozenError` instead of silently being a no-op.
|
|
11
|
+
|
|
3
12
|
## 0.1.0-alpha.5
|
|
4
13
|
|
|
5
14
|
Most of what follows announces itself: a new option, a rename, or a declaration error you now get at class definition instead of on every call. These are the changes that do **not** — re-run your suite and look at these areas.
|
|
@@ -43,11 +43,7 @@ module Axn
|
|
|
43
43
|
end
|
|
44
44
|
end
|
|
45
45
|
|
|
46
|
-
def _model_fields
|
|
47
|
-
action.internal_field_configs.each_with_object({}) do |config, hash|
|
|
48
|
-
hash[config.field] = config.validations[:model] if config.validations.key?(:model)
|
|
49
|
-
end
|
|
50
|
-
end
|
|
46
|
+
def _model_fields = action.class._model_fields
|
|
51
47
|
|
|
52
48
|
def action_name = @action.class.name.presence || "The action"
|
|
53
49
|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Axn
|
|
4
|
+
module Core
|
|
5
|
+
module Contract
|
|
6
|
+
# Per-class cache of the one-off validator classes `Axn::Validation::Fields.validator_class_for`
|
|
7
|
+
# would otherwise compile fresh on every `.call` for every declared top-level field and subfield
|
|
8
|
+
# (PRO-3050). Mirrors `Redaction#_contract_redaction` exactly: outer key is `equal?` on the three
|
|
9
|
+
# copy-on-write config arrays (self-invalidating on any redeclaration, subclass, or
|
|
10
|
+
# Mountable/Factory rebuild — see redaction.rb's doctrine comment, which this reuses verbatim),
|
|
11
|
+
# inner key is the `FieldConfig`/`ShapeConfig` object's own IDENTITY.
|
|
12
|
+
#
|
|
13
|
+
# Identity, never `validations`: a config's `#validations` is not a stable object across reads.
|
|
14
|
+
# `Executor#_with_effective_coerce` mints a fresh merged Hash every call when `coerce_input_types`
|
|
15
|
+
# resolves on, and a config reached only via `internal_field_configs=`/`subfield_configs=` (never
|
|
16
|
+
# declared) can answer a DIFFERENT Hash on every `#validations` read (stored_shape_traversal_spec's
|
|
17
|
+
# generative member) — keying on either would never hit, or worse, would grow the table forever.
|
|
18
|
+
# The config object itself has neither problem: it is a frozen `Data`, minted once and replaced
|
|
19
|
+
# wholesale on redeclaration, never mutated.
|
|
20
|
+
#
|
|
21
|
+
# The SAME config can legitimately need two different compiled classes in one process — the
|
|
22
|
+
# `coerce_input_types` gate is per-call/per-class/global and can flip between calls — so the inner
|
|
23
|
+
# table is keyed on [config, coerce] rather than on config alone.
|
|
24
|
+
module ValidatorClassCache
|
|
25
|
+
def _validator_class_cache
|
|
26
|
+
internals = internal_field_configs
|
|
27
|
+
externals = external_field_configs
|
|
28
|
+
subfields = subfield_configs
|
|
29
|
+
memo = @_axn_validator_class_cache
|
|
30
|
+
return memo if memo&.current?(internals, externals, subfields)
|
|
31
|
+
|
|
32
|
+
@_axn_validator_class_cache = ValidatorClassCacheTable.new(internals:, externals:, subfields:)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The compiled validator class for one config under one coerce state, built once per class per
|
|
36
|
+
# (config, coerce) pair and reused across every `.call`. `effective_validations` is the caller's
|
|
37
|
+
# already-resolved Hash (with `_with_effective_coerce` applied if relevant) — computed by the
|
|
38
|
+
# caller regardless of hit/miss, same as before this cache existed, so a hit only saves the
|
|
39
|
+
# `Class.new` + `validates` compilation, not the coerce merge.
|
|
40
|
+
def _cached_validator_class_for(config:, effective_validations:, coerce:)
|
|
41
|
+
_validator_class_cache.fetch(config:, coerce:) do
|
|
42
|
+
Axn::Validation::Fields.validator_class_for(field: config.field, validations: effective_validations)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# The table `_validator_class_cache` hands out. Mutable (so not a `Data`), one Hash slot written
|
|
48
|
+
# once per (config, coerce) pair. `||=` is safe here (unlike `_contract_redaction`'s `dynamic`
|
|
49
|
+
# flag): a compiled Class is always truthy, so there is no false/nil tri-state to guard.
|
|
50
|
+
class ValidatorClassCacheTable
|
|
51
|
+
def initialize(internals:, externals:, subfields:)
|
|
52
|
+
@internals = internals
|
|
53
|
+
@externals = externals
|
|
54
|
+
@subfields = subfields
|
|
55
|
+
# Per config, by identity — a FieldConfig/ShapeConfig defines no `hash`/`eql?` axn would want
|
|
56
|
+
# to run (its `validations` Hash may hold a caller-supplied option container that does), and
|
|
57
|
+
# identity is the right question anyway: the stored config is the one axn built.
|
|
58
|
+
@classes = {}.compare_by_identity
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def current?(internals, externals, subfields)
|
|
62
|
+
@internals.equal?(internals) && @externals.equal?(externals) && @subfields.equal?(subfields)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def fetch(config:, coerce:)
|
|
66
|
+
by_coerce = (@classes[config] ||= {})
|
|
67
|
+
by_coerce[coerce] ||= yield
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
data/lib/axn/core/contract.rb
CHANGED
|
@@ -7,6 +7,7 @@ require "active_support/core_ext/module/delegation"
|
|
|
7
7
|
require "active_support/core_ext/object/blank"
|
|
8
8
|
|
|
9
9
|
require "axn/core/contract/redaction"
|
|
10
|
+
require "axn/core/contract/validator_class_cache"
|
|
10
11
|
require "axn/core/contract/shape_declaration"
|
|
11
12
|
require "axn/core/validation/fields"
|
|
12
13
|
require "axn/core/flow/handlers/invoker"
|
|
@@ -365,6 +366,7 @@ module Axn
|
|
|
365
366
|
# and in the same lookup position as when it lived in this file.
|
|
366
367
|
include ShapeDeclaration
|
|
367
368
|
include Redaction
|
|
369
|
+
include ValidatorClassCache
|
|
368
370
|
|
|
369
371
|
# rubocop:disable Metrics/ParameterLists
|
|
370
372
|
def expects(
|
|
@@ -534,23 +536,70 @@ module Axn
|
|
|
534
536
|
end
|
|
535
537
|
end
|
|
536
538
|
|
|
539
|
+
DeclaredFieldsCacheEntry = Data.define(:internal_field_configs, :external_field_configs, :fields)
|
|
540
|
+
|
|
541
|
+
# `configs.map(&:field)` was a fresh Array on every call — `Redaction#_context_slice` alone
|
|
542
|
+
# calls this twice per logged line (inbound + outbound), and the context facade calls it twice
|
|
543
|
+
# per action call. Cached per direction, invalidated by the identity of BOTH config arrays
|
|
544
|
+
# (rather than only the one a given direction reads) for simplicity: an outbound-only
|
|
545
|
+
# redeclaration then occasionally invalidates the (unaffected) :inbound slot too, which is
|
|
546
|
+
# never a wrong answer, only an avoidable rebuild.
|
|
547
|
+
#
|
|
548
|
+
# The cached Array is FROZEN before it's stored: `ContextFacade` exposes this exact object
|
|
549
|
+
# publicly as `Result#declared_fields`, so a fresh Array per call used to confine any caller
|
|
550
|
+
# mutation to that one facade — reusing the same object across every future call turns that
|
|
551
|
+
# mutation into permanent cache corruption (a caller-appended field name would start defining
|
|
552
|
+
# readers/passing `expose` for undeclared output on every subsequent call of the class) unless
|
|
553
|
+
# the shared object refuses to be mutated at all.
|
|
537
554
|
def _declared_fields(direction)
|
|
538
555
|
raise ArgumentError, "Invalid direction: #{direction}" unless direction.nil? || %i[inbound outbound].include?(direction)
|
|
539
556
|
|
|
557
|
+
internals = internal_field_configs
|
|
558
|
+
externals = external_field_configs
|
|
559
|
+
cache = (@_axn_declared_fields_cache ||= {})
|
|
560
|
+
cached = cache[direction]
|
|
561
|
+
return cached.fields if cached && cached.internal_field_configs.equal?(internals) && cached.external_field_configs.equal?(externals)
|
|
562
|
+
|
|
540
563
|
configs = case direction
|
|
541
|
-
when :inbound then
|
|
542
|
-
when :outbound then
|
|
543
|
-
else (
|
|
564
|
+
when :inbound then internals
|
|
565
|
+
when :outbound then externals
|
|
566
|
+
else (internals + externals)
|
|
544
567
|
end
|
|
545
568
|
|
|
546
|
-
|
|
569
|
+
cache[direction] = DeclaredFieldsCacheEntry.new(internal_field_configs: internals, external_field_configs: externals,
|
|
570
|
+
fields: configs.map(&:field).freeze)
|
|
571
|
+
cache[direction].fields
|
|
572
|
+
end
|
|
573
|
+
|
|
574
|
+
ModelFieldsCacheEntry = Data.define(:internal_field_configs, :value)
|
|
575
|
+
|
|
576
|
+
# Field => model options for every internal field carrying `model:`, cached per class: a pure
|
|
577
|
+
# function of `internal_field_configs`, rebuilt only when that array's identity changes (any
|
|
578
|
+
# redeclaration, subclass, or Mountable/Factory rebuild — see Redaction's doctrine comment).
|
|
579
|
+
# Moved here from the context facade instance (was rebuilt from scratch on every reader
|
|
580
|
+
# definition — O(fields defined × contract size) per action instance, since both the outbound
|
|
581
|
+
# Result facade and the inbound InternalContext facade instantiate one per call).
|
|
582
|
+
#
|
|
583
|
+
# Frozen before caching, same reasoning as `_declared_fields`: this is a public class method
|
|
584
|
+
# (so any caller can hold the live Hash), and the old per-call rebuild used to confine a
|
|
585
|
+
# caller mutation to that one read — reusing the same Hash across every future call would
|
|
586
|
+
# otherwise turn a mutation into permanent cross-call cache corruption.
|
|
587
|
+
def _model_fields
|
|
588
|
+
fields = internal_field_configs
|
|
589
|
+
cached = @_axn_model_fields
|
|
590
|
+
return cached.value if cached && cached.internal_field_configs.equal?(fields)
|
|
591
|
+
|
|
592
|
+
value = fields.each_with_object({}) { |config, hash| hash[config.field] = config.validations[:model] if config.validations.key?(:model) }
|
|
593
|
+
@_axn_model_fields = ModelFieldsCacheEntry.new(internal_field_configs: fields, value: value.freeze)
|
|
594
|
+
value
|
|
547
595
|
end
|
|
548
596
|
|
|
549
597
|
# Everything below is reached only with an implicit receiver, from here and from the other declaration
|
|
550
598
|
# modules extended onto the same class. It is private because an `_`-prefixed name in a module extended
|
|
551
599
|
# onto every action class otherwise lands there as a PUBLIC singleton method, so the convention and the
|
|
552
600
|
# surface disagree. `_declared_fields` stays public above: the context facade, the redaction slice and
|
|
553
|
-
# `Mountable`'s step passthrough call it on the action class from other files.
|
|
601
|
+
# `Mountable`'s step passthrough call it on the action class from other files. `_model_fields` stays
|
|
602
|
+
# public for the same reason: the context facade reads it from `facade.rb`.
|
|
554
603
|
private
|
|
555
604
|
|
|
556
605
|
# Reject `user_facing:` on any member of an `exposes` shape, at any depth. The block form
|
data/lib/axn/core/executor.rb
CHANGED
|
@@ -1053,8 +1053,9 @@ module Axn
|
|
|
1053
1053
|
return _validate_inbound! if direction == :inbound
|
|
1054
1054
|
|
|
1055
1055
|
failures = @action_class.send(:external_field_configs).filter_map do |config|
|
|
1056
|
-
|
|
1057
|
-
|
|
1056
|
+
validator_class = @action_class._cached_validator_class_for(config:, effective_validations: config.validations, coerce: false)
|
|
1057
|
+
errors = Axn::Validation::Fields.errors_for(validator_class, source: @action.result, validations: config.validations, action: @action,
|
|
1058
|
+
permit_method_call: true)
|
|
1058
1059
|
ContractFailure.new(config:, path: nil, errors:, stranded_at: nil) if errors.any?
|
|
1059
1060
|
end
|
|
1060
1061
|
raise OutboundValidationError, _aggregate_errors(failures, []) if failures.any?
|
|
@@ -1132,13 +1133,16 @@ module Axn
|
|
|
1132
1133
|
coerce_input_types = _coerce_input_types?
|
|
1133
1134
|
|
|
1134
1135
|
_inbound_configs.filter_map do |config|
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1136
|
+
effective_validations = coerce_input_types ? _with_effective_coerce(config.validations) : config.validations
|
|
1137
|
+
validator_class = @action_class._cached_validator_class_for(config:, effective_validations:, coerce: coerce_input_types)
|
|
1138
|
+
errors = Axn::Validation::Fields.errors_for(
|
|
1139
|
+
validator_class,
|
|
1138
1140
|
source: config.subfield? ? _resolved_parent_value(config) : @action.internal_context,
|
|
1141
|
+
validations: effective_validations,
|
|
1139
1142
|
action: @action,
|
|
1140
1143
|
reader: config.subfield? ? config.reader_as : nil,
|
|
1141
1144
|
config: config.subfield? ? config : nil,
|
|
1145
|
+
permit_method_call: true,
|
|
1142
1146
|
)
|
|
1143
1147
|
next if errors.empty?
|
|
1144
1148
|
|
|
@@ -8,7 +8,10 @@ module Axn
|
|
|
8
8
|
# THE one-off validator collector, for every declared config at every level: a top-level field
|
|
9
9
|
# validates against the context facade (which resolves model records and reads by wire key), a
|
|
10
10
|
# subfield against its canonically-resolved parent value. One (field, validations) pair per
|
|
11
|
-
# one-off class
|
|
11
|
+
# one-off class. The top-level/subfield path (Executor) and ShapeValidator both cache the compiled
|
|
12
|
+
# class and reuse it across calls/sources — see Contract::ValidatorClassCache and
|
|
13
|
+
# ShapeValidator#member_validator_classes respectively; raising/settling is the caller's concern
|
|
14
|
+
# (see Executor#_validate_inbound!).
|
|
12
15
|
class Fields < Base
|
|
13
16
|
def initialize(source)
|
|
14
17
|
super()
|
|
@@ -31,7 +34,8 @@ module Axn
|
|
|
31
34
|
# Two occupants reach here. A top-level/outbound FACADE read: its source is the framework's
|
|
32
35
|
# own context/result facade, whose per-field reader is a safe generated accessor — method
|
|
33
36
|
# dispatch is always permitted (it's not the caller-object dispatch the method_call gate
|
|
34
|
-
# targets), so the facade call site (
|
|
37
|
+
# targets), so the facade call site (Executor, via .errors_for) passes `permit_method_call:
|
|
38
|
+
# true`. A
|
|
35
39
|
# SHAPE MEMBER read (ShapeValidator): its source is a caller-supplied element, so it honors
|
|
36
40
|
# the member's own `method_call:` opt-in — the same gate as a subfield (PRO-2907). The
|
|
37
41
|
# permission is carried explicitly by each call site (NOT inferred from @action presence):
|
|
@@ -44,18 +48,11 @@ module Axn
|
|
|
44
48
|
end
|
|
45
49
|
end
|
|
46
50
|
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
51
|
-
#
|
|
52
|
-
def self.collect_errors(field:, validations:, source:, action: nil, reader: nil, config: nil)
|
|
53
|
-
errors_for(validator_class_for(field:, validations:), source:, validations:, action:, reader:, config:, permit_method_call: true)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
# Builds the one-off validator class for a (field, validations) pair. Callers that validate
|
|
57
|
-
# the same contract repeatedly (e.g. ShapeValidator over array elements) can build this once
|
|
58
|
-
# and reuse it across sources via .errors_for, avoiding per-call class compilation.
|
|
51
|
+
# Builds the one-off validator class for a (field, validations) pair. Every caller that validates
|
|
52
|
+
# the same contract repeatedly builds this ONCE and reuses it across sources via .errors_for:
|
|
53
|
+
# Contract::ValidatorClassCache for a top-level field/subfield (keyed on the FieldConfig's
|
|
54
|
+
# identity), ShapeValidator for a shape member (keyed on the member's field name, scoped to one
|
|
55
|
+
# compiled parent class).
|
|
59
56
|
def self.validator_class_for(field:, validations:)
|
|
60
57
|
Class.new(self) do
|
|
61
58
|
def self.name = "Axn::Validation::Fields::OneOff"
|
|
@@ -71,7 +68,7 @@ module Axn
|
|
|
71
68
|
|
|
72
69
|
# Runs a validator class against a source and returns its ActiveModel::Errors (empty if valid).
|
|
73
70
|
# `permit_method_call:` governs the dispatch gate in the else branch of
|
|
74
|
-
# read_attribute_for_validation: the facade call site (
|
|
71
|
+
# read_attribute_for_validation: the facade call site (Executor) passes `true`; a shape
|
|
75
72
|
# member passes its own `method_call:` opt-in (PRO-2907). It is deliberately independent of
|
|
76
73
|
# `action:` so the two can be threaded separately.
|
|
77
74
|
#
|
|
@@ -48,6 +48,13 @@ module Axn
|
|
|
48
48
|
return unless level
|
|
49
49
|
|
|
50
50
|
Axn::Extensions.best_effort(error_context, action: action_class) do
|
|
51
|
+
# Guarded by the same best_effort boundary as everything else in this block: a custom
|
|
52
|
+
# logger's severity predicate can itself raise, and that must be absorbed exactly like any
|
|
53
|
+
# other formatting failure here — some callers (call_async's invocation log, the
|
|
54
|
+
# enqueue-all completion log) invoke log_at_level with no other best_effort wrapping it, so
|
|
55
|
+
# checking the predicate outside this boundary could abort the call it only meant to log.
|
|
56
|
+
next unless would_log?(level)
|
|
57
|
+
|
|
51
58
|
# Prepare and format context if needed
|
|
52
59
|
context_str = if context_instance && context_direction
|
|
53
60
|
# Instance-level: use private inputs_for_logging / outputs_for_logging
|
|
@@ -91,6 +98,21 @@ module Axn
|
|
|
91
98
|
defined?(SemanticLogger::Logger) && Axn.config.logger.is_a?(SemanticLogger::Logger)
|
|
92
99
|
end
|
|
93
100
|
|
|
101
|
+
# Whether the configured logger would actually emit at `level`, read via the logger's OWN
|
|
102
|
+
# severity predicate (`debug?`/`info?`/`warn?`/`error?`/`fatal?` — the same query Ruby's stdlib
|
|
103
|
+
# `Logger` and `SemanticLogger::Logger` already expose). A logger that doesn't respond to the
|
|
104
|
+
# predicate is assumed to emit, which matches today's behavior exactly — a custom logger
|
|
105
|
+
# implementing only the plain level methods loses nothing. This is deliberately NOT a switch to
|
|
106
|
+
# block-form logging (`logger.info { msg }`): that would silently drop the message for a custom
|
|
107
|
+
# logger whose level methods take a positional argument only and ignore an unused block. Public:
|
|
108
|
+
# `log_at_level` is the only caller, from this same module, but kept alongside `semantic_logger?`
|
|
109
|
+
# for the same reason that one is public.
|
|
110
|
+
def would_log?(level)
|
|
111
|
+
logger = Axn.config.logger
|
|
112
|
+
predicate = :"#{level}?"
|
|
113
|
+
!logger.respond_to?(predicate) || logger.public_send(predicate)
|
|
114
|
+
end
|
|
115
|
+
|
|
94
116
|
private
|
|
95
117
|
|
|
96
118
|
# Labeled readable suffix for the plain line, each group rendered with the same
|
data/lib/axn/version.rb
CHANGED
data/lib/axn.rb
CHANGED
|
@@ -51,8 +51,15 @@ require "axn/util/execution_context"
|
|
|
51
51
|
require "axn/mountable"
|
|
52
52
|
require "axn/async"
|
|
53
53
|
|
|
54
|
-
# Rails integration
|
|
55
|
-
|
|
54
|
+
# Rails integration. The `defined?` check runs once and re-requiring axn is a no-op, so a host that
|
|
55
|
+
# loads axn before Rails -- the conventional RSpec layout does -- would otherwise never get the
|
|
56
|
+
# engine. `:before_configuration` fires from `Rails::Application#initialize`, before railties are
|
|
57
|
+
# collected, and runs immediately if Rails is already past that point.
|
|
58
|
+
if defined?(Rails) && Rails.const_defined?(:Engine)
|
|
59
|
+
require "axn/rails/engine"
|
|
60
|
+
else
|
|
61
|
+
ActiveSupport.on_load(:before_configuration) { require "axn/rails/engine" }
|
|
62
|
+
end
|
|
56
63
|
|
|
57
64
|
module Axn
|
|
58
65
|
def self.included(base)
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: axn
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.0.pre.alpha.5
|
|
4
|
+
version: 0.1.0.pre.alpha.5.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Kali Donovan
|
|
@@ -82,6 +82,7 @@ files:
|
|
|
82
82
|
- lib/axn/core/contract/redaction.rb
|
|
83
83
|
- lib/axn/core/contract/shape_declaration.rb
|
|
84
84
|
- lib/axn/core/contract/subfield_contradictions.rb
|
|
85
|
+
- lib/axn/core/contract/validator_class_cache.rb
|
|
85
86
|
- lib/axn/core/contract_for_subfields.rb
|
|
86
87
|
- lib/axn/core/default_call.rb
|
|
87
88
|
- lib/axn/core/executor.rb
|