Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 15 additions & 37 deletions lib/tram/page.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,35 @@
class Tram::Page
extend ::Dry::Initializer

require_relative "page/section"

class << self
attr_accessor :i18n_scope

def section(name, options = {})
@__sections ||= []

n = name.to_sym
if @__sections.map(&:first).include?(n)
raise "Section #{n} already exists"
end
name = name.to_sym
raise "Section #{name} already exists" if sections.key? name

n = define_section_method(n, options)
@__sections << [n, options]
section = Section.new(name, options)
sections[name] = section
define_method(name, &section.block) if section.block
end

def url_helper(name)
raise "Rails url_helpers module is not defined" unless defined?(Rails)
delegate name, to: :"Rails.application.routes.url_helpers"
end

private

def define_section_method(n, options)
return n unless options[:value]
define_method(n) { instance_exec(&options[:value]) }
n
def sections
@sections ||= {}
end
end

def to_h(options = {})
data = page_methods(options).map do |(name, opts)|
value = public_send(opts[:method] || name)
[name, value]
end
Hash[data]
def to_h(except: nil, only: nil, **)
obj = self.class.sections.values.map { |s| s.call(self) }.reduce({}, :merge)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is somewhat not optimal to calculate all values and then throw out some or most of them.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahh! Yes, you are right
It would be better to filter out sections, not calculated values

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, it is worth to use merge! to reduce the number of allocations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

obj = obj.reject { |k, _| Array(except).map(&:to_sym).include? k } if except
obj = obj.select { |k, _| Array(only).map(&:to_sym).include? k } if only
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reject! and select! too may help to reduce memory footprint.

obj
end

private
Expand All @@ -47,21 +41,5 @@ def t(key)
I18n.t key, scope: [Tram::Page.i18n_scope, self.class.name.underscore]
end

def page_methods(options)
methods = self.class.instance_variable_get(:"@__sections") || []
except = Array(options[:except])
only = Array(options[:only])
methods.reject do |(name, opts)|
(except.any? && except.include?(name)) ||
(only.any? && !only.include?(name)) ||
__hide?(opts)
end
end

def __hide?(opts)
black, white = opts.values_at(:unless, :if)
(black && public_send(black)) || (white && !public_send(white))
end
self.i18n_scope = "pages"
end

Tram::Page.i18n_scope = "pages"
36 changes: 36 additions & 0 deletions lib/tram/page/section.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# frozen_string_literal: true

class Tram::Page
#
# Contains class-level definition (name and options) for a section
# with a method [#call] that extracts the section part of hash
# from an instance of [Tram::Page]
#
class Section
extend Dry::Initializer
param :name, proc(&:to_sym)
option :method, proc(&:to_s), default: -> { name }, as: :method_name
option :value, proc(&:to_proc), optional: true, as: :block
option :if, proc(&:to_s), optional: true, as: :positive
option :unless, proc(&:to_s), optional: true, as: :negative
option :skip, true.method(:&), optional: true

# @param [Tram::Page] page
# @return [Hash] a part of the section
def call(page)
skip_on?(page) ? {} : { name => value_at(page) }
end

private

def skip_on?(page)
return true if skip
return true if positive && !page.public_send(positive)
return true if negative && page.public_send(negative)
end

def value_at(page)
block ? page.instance_exec(&block) : page.public_send(method_name)
end
end
end