Module: Studio::ProfileSections
- Defined in:
- lib/studio/profile_sections.rb
Constant Summary collapse
- DEFAULTS =
The page every consumer gets for free. Iteration one is deliberately two rows: the picture and the name. Everything else the account-standardization program lifts (email change, identities, preferences) arrives as further defaults, and a host that has declared its own list keeps working because it composes against
Studio.default_profile_sectionsrather than a literal. [ { key: :avatar, title: "Profile photo", partial: "studio/profiles/avatar_section", requires: :avatar, modals: true }, { key: :first_name, title: "Your name", partial: "studio/profiles/first_name_section", requires: :first_name }, # Gated on the app OFFERING Google, not merely on having the columns — # the same question app/views/sessions/new.html.erb:79 already asks before # drawing this identical button on the login page. { key: :google, title: "Google account", partial: "studio/profiles/google_section", requires: %i[provider uid], if: -> { Studio.auth_method?(:google) } } ].freeze
Class Method Summary collapse
-
.defaults ⇒ Object
A fresh, mutable copy every call.
-
.enabled?(condition, view) ⇒ Boolean
The app-capability gate.
-
.resolve(declared, view) ⇒ Object
Declared may be nil (the host has said nothing — it gets the defaults), an Array, or a callable receiving the view context.
-
.served_by?(user, requires) ⇒ Boolean
Can this host's user model serve the row? No requirement means yes — a row that reads nothing off the user (a static explainer, a link out) is always served.
- .symbolize(hash) ⇒ Object
Class Method Details
.defaults ⇒ Object
A fresh, mutable copy every call. Hosts compose with + and sometimes
reject, and handing out the frozen literal would let one host's edit
leak into the next request's page.
79 80 81 |
# File 'lib/studio/profile_sections.rb', line 79 def defaults DEFAULTS.map { |section| section.dup } end |
.enabled?(condition, view) ⇒ Boolean
The app-capability gate. No if: means always enabled. The callable takes
the view when it wants one and nothing when it does not, so a host can
write either -> { Studio.feature?(:x) } or ->(view) { view.admin? }.
106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/studio/profile_sections.rb', line 106 def enabled?(condition, view) return true if condition.nil? # A Symbol/String names a method on the VIEW — Rails' own # `before_action ..., if: :method_name` convention, and therefore the most # natural thing a host will write here. # # It is handled explicitly because the alternative FAILS OPEN: a Symbol # does not answer `call`, so the earlier `return !!condition` coerced # `:some_predicate` to true and rendered the row unconditionally — a gate # that silently does nothing, in the same permissive direction as the bug # this whole `if:` key was added to fix. A host would have had no signal. if condition.is_a?(Symbol) || condition.is_a?(String) return false unless view.respond_to?(condition) return !!view.public_send(condition) end return !!condition unless condition.respond_to?(:call) !!(condition.arity.zero? ? condition.call : condition.call(view)) end |
.resolve(declared, view) ⇒ Object
Declared may be nil (the host has said nothing — it gets the defaults), an Array, or a callable receiving the view context.
nil meaning "the defaults" rather than "no sections" is the load-bearing
choice here: it is what makes a brand-new app's profile page work with an
empty initializer, which is the entire point of standardizing this.
89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/studio/profile_sections.rb', line 89 def resolve(declared, view) sections = declared.respond_to?(:call) ? declared.call(view) : declared sections = defaults if sections.nil? admin = view.respond_to?(:admin?) && view.admin? user = view.respond_to?(:current_user) ? view.current_user : nil Array(sections) .map { |section| symbolize(section) } .reject { |section| section[:admin] && !admin } .select { |section| enabled?(section[:if], view) } .select { |section| served_by?(user, section[:requires]) } end |
.served_by?(user, requires) ⇒ Boolean
Can this host's user model serve the row? No requirement means yes — a row that reads nothing off the user (a static explainer, a link out) is always served. A nil user means we are rendering for nobody, and nothing is served.
132 133 134 135 136 137 138 |
# File 'lib/studio/profile_sections.rb', line 132 def served_by?(user, requires) needed = Array(requires).compact return true if needed.empty? return false if user.nil? needed.all? { |attribute| user.respond_to?(attribute) } end |
.symbolize(hash) ⇒ Object
140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/studio/profile_sections.rb', line 140 def symbolize(hash) out = hash.to_h.each_with_object({}) { |(key, value), acc| acc[key.to_sym] = value } # `key` is an IDENTIFIER, so its VALUE symbolizes too — unlike every other # field, whose value is data. A host composing by key writes # `reject { |s| s[:key] == :avatar }`, and that silently matches nothing if # a section declared with string keys kept `"avatar"` as a String. Removing # a standard row is the documented seam; it must not depend on which # spelling the host happened to use. out[:key] = out[:key].to_sym if out[:key].respond_to?(:to_sym) out end |