|
32 | 32 | # source type. Mode is derived from link presence: a link means Linked, its |
33 | 33 | # absence means Independent. This is the shared seam FND-101/102 extend with |
34 | 34 | # further aspects, and the deferred resolution/copy/cycle work builds on top of. |
35 | | -module Type::ConfigurationLinkable |
36 | | - extend ActiveSupport::Concern |
| 35 | +class Type |
| 36 | + module ConfigurationLinkable |
| 37 | + extend ActiveSupport::Concern |
37 | 38 |
|
38 | | - included do |
39 | | - has_many :configuration_links, |
40 | | - class_name: "Type::ConfigurationLink", |
41 | | - dependent: :destroy |
42 | | - has_many :dependent_configuration_links, |
43 | | - class_name: "Type::ConfigurationLink", |
44 | | - foreign_key: :source_id, |
45 | | - inverse_of: :source, |
46 | | - dependent: :restrict_with_error |
| 39 | + included do |
| 40 | + has_many :configuration_links, |
| 41 | + class_name: "Type::ConfigurationLink", |
| 42 | + dependent: :destroy |
| 43 | + has_many :dependent_configuration_links, |
| 44 | + class_name: "Type::ConfigurationLink", |
| 45 | + foreign_key: :source_id, |
| 46 | + inverse_of: :source, |
| 47 | + dependent: :restrict_with_error |
47 | 48 |
|
48 | | - # A sub-type defaults to Linked-to-parent for every reusable aspect. |
49 | | - after_create :seed_default_configuration_links, if: :subtype? |
50 | | - end |
| 49 | + # A sub-type defaults to Linked-to-parent for every reusable aspect. |
| 50 | + after_create :seed_default_configuration_links, if: :subtype? |
| 51 | + end |
51 | 52 |
|
52 | | - def linked?(aspect) |
53 | | - configuration_links.exists?(aspect:) |
54 | | - end |
| 53 | + def linked?(aspect) |
| 54 | + configuration_links.exists?(aspect:) |
| 55 | + end |
55 | 56 |
|
56 | | - def source_for(aspect) |
57 | | - configuration_links.find_by(aspect:)&.source |
58 | | - end |
| 57 | + def source_for(aspect) |
| 58 | + configuration_links.find_by(aspect:)&.source |
| 59 | + end |
59 | 60 |
|
60 | | - def link!(aspect, source:) |
61 | | - configuration_links.find_or_initialize_by(aspect:).update!(source:) |
62 | | - end |
| 61 | + def link!(aspect, source:) |
| 62 | + configuration_links.find_or_initialize_by(aspect:).update!(source:) |
| 63 | + end |
63 | 64 |
|
64 | | - # Switch an aspect to Independent. When a source is given, its resolved |
65 | | - # configuration is copied onto this type once (adopt) before the link is severed. |
66 | | - def make_independent!(aspect, source: nil) |
67 | | - copy_configuration_from(source, aspect) if source && source != self |
68 | | - configuration_links.where(aspect:).destroy_all |
69 | | - end |
| 65 | + # Switch an aspect to Independent. When a source is given, its resolved |
| 66 | + # configuration is copied onto this type once (adopt) before the link is severed. |
| 67 | + def make_independent!(aspect, source: nil) |
| 68 | + copy_configuration_from(source, aspect) if source && source != self |
| 69 | + configuration_links.where(aspect:).destroy_all |
| 70 | + end |
70 | 71 |
|
71 | | - # Walks the link chain to the type that actually owns the aspect (Independent). |
72 | | - # The visited-set guard keeps it terminating even before transitive cycle |
73 | | - # prevention lands. |
74 | | - def effective_source_for(aspect) |
75 | | - node = self |
76 | | - seen = Set.new |
77 | | - node = node.source_for(aspect) while node.linked?(aspect) && seen.add?(node.id) |
78 | | - node |
79 | | - end |
| 72 | + # Walks the link chain to the type that actually owns the aspect (Independent). |
| 73 | + # The visited-set guard keeps it terminating even before transitive cycle |
| 74 | + # prevention lands. |
| 75 | + # |
| 76 | + # Guarded by the subtypes feature flag: with the flag off, links are ignored |
| 77 | + # and every type resolves to its own stored configuration. |
| 78 | + def effective_source_for(aspect) |
| 79 | + return self unless OpenProject::FeatureDecisions.subtypes_active? |
80 | 80 |
|
81 | | - def effective_patterns |
82 | | - effective_source_for(Type::ConfigurationLink::PATTERNS).patterns |
83 | | - end |
| 81 | + node = self |
| 82 | + seen = Set.new |
| 83 | + node = node.source_for(aspect) while node.linked?(aspect) && seen.add?(node.id) |
| 84 | + node |
| 85 | + end |
84 | 86 |
|
85 | | - def effective_pdf_export_templates |
86 | | - effective_source_for(Type::ConfigurationLink::PDF_EXPORT).pdf_export_templates |
87 | | - end |
| 87 | + def effective_patterns |
| 88 | + effective_source_for(Type::ConfigurationLink::PATTERNS).patterns |
| 89 | + end |
88 | 90 |
|
89 | | - # One-time adoption: copy the source's resolved configuration for one aspect |
90 | | - # onto this type. Used when switching to Independent from a chosen source. |
91 | | - # deep_dup keeps the copy from aliasing the source's stored value. |
92 | | - def copy_configuration_from(source, aspect) |
93 | | - owner = source.effective_source_for(aspect) |
94 | | - case aspect |
95 | | - when Type::ConfigurationLink::PATTERNS |
96 | | - update!(patterns: owner.patterns.deep_dup) |
97 | | - when Type::ConfigurationLink::PDF_EXPORT |
98 | | - update!(pdf_export_templates_config: owner.pdf_export_templates_config.deep_dup) |
| 91 | + def effective_pdf_export_templates |
| 92 | + effective_source_for(Type::ConfigurationLink::PDF_EXPORT).pdf_export_templates |
| 93 | + end |
| 94 | + |
| 95 | + # One-time adoption: copy the source's resolved configuration for one aspect |
| 96 | + # onto this type. Used when switching to Independent from a chosen source. |
| 97 | + # deep_dup keeps the copy from aliasing the source's stored value. |
| 98 | + def copy_configuration_from(source, aspect) |
| 99 | + owner = source.effective_source_for(aspect) |
| 100 | + case aspect |
| 101 | + when Type::ConfigurationLink::PATTERNS |
| 102 | + update!(patterns: owner.patterns.deep_dup) |
| 103 | + when Type::ConfigurationLink::PDF_EXPORT |
| 104 | + update!(pdf_export_templates_config: owner.pdf_export_templates_config.deep_dup) |
| 105 | + end |
99 | 106 | end |
100 | | - end |
101 | 107 |
|
102 | | - private |
| 108 | + private |
103 | 109 |
|
104 | | - def seed_default_configuration_links |
105 | | - Type::ConfigurationLink::ASPECTS.each { |aspect| link!(aspect, source: parent) } |
| 110 | + def seed_default_configuration_links |
| 111 | + Type::ConfigurationLink::ASPECTS.each { |aspect| link!(aspect, source: parent) } |
| 112 | + end |
106 | 113 | end |
107 | 114 | end |
0 commit comments