taksi 0.1.0 → 0.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f068520d9474471f8de3dd72b934a8416da6acfeadfda8b6ccf6c943222ea15
4
- data.tar.gz: 0ac983ba246c7a12f7c44c2b51852f47b5d2568a78b7a351e798d4ff9af024e2
3
+ metadata.gz: c176b6593b9e570169fa5ec6103bfd635c8bae751342ce7b8c986ec3fa43df3d
4
+ data.tar.gz: e84fe608e7b15263084b9f3f745eba7e9cf47d8460ecb02032ad930fe85e1c14
5
5
  SHA512:
6
- metadata.gz: 9bab30c33f37c2aceb47b04d6c249030f71a2fd16762569e604c371bba86322a822575d84330e7d69143b46bd3096b5bf5752e79cedd8c7f3d8ead7fe30fc53a
7
- data.tar.gz: 3ef033f144438741084fc36291923cd04a001fb802b9d5091fea7f4fc43169da7d987e14d68954627890134b642daeba0e84523330d0b0dd530080a9cfb5f8e2
6
+ metadata.gz: b6b421c3efe426c48c934ccfc9ba2ae385a74fd97cc63fc0c4fbca14070f547ab0003d09abc6fe724ea6dd064d8d49d00a08bf268193850337566b945760be7a
7
+ data.tar.gz: 45fa852395860e6b3ade5d413518d2cca268cefbd9faeb21aa83d269e8c74d5d3d5ed9ba2c7fc27906d9d55222acea5d6f53396f0945ae6e54374704a2c14e05
data/README.md CHANGED
@@ -0,0 +1,112 @@
1
+ # Taksi [![Gem Version](https://badge.fury.io/rb/taksi.svg)](https://badge.fury.io/rb/taksi) [![CI](https://github.com/taksi-br/taksi-ruby/actions/workflows/ci.yml/badge.svg)](https://github.com/taksi-br/taksi-ruby/actions/workflows/ci.yml) [![Codacy Badge](https://app.codacy.com/project/badge/Coverage/c3b7b1b64129408a946ce2c99a5b2706)](https://app.codacy.com/gh/taksi-br/taksi-ruby/dashboard?utm_source=gh&utm_medium=referral&utm_content=&utm_campaign=Badge_coverage)
2
+
3
+ Application framework to build a backend driven UI in ruby.
4
+
5
+ ## Considerations
6
+
7
+ This repository are in its **very early days** and **not ready for production** yet. If you want to help or understand what it is, get a look over our inspirations on the links below:
8
+ - https://medium.com/movile-tech/backend-driven-development-ios-d1c726f2913b
9
+ - https://engineering.q42.nl/server-driven-ui-at-primephonic/
10
+ - https://www.youtube.com/watch?v=vuCfKjOwZdU
11
+
12
+ Also, we're working in create a protocol documentation to explain the comunication details between frontend and backend.
13
+
14
+ ## Usage
15
+
16
+ In Taksi, every interface are composed by 1 to many components, those components are feed by data provided from the interface definition.
17
+
18
+ Defining a new component:
19
+
20
+ ```ruby
21
+ class Components::Users::ProfileResume
22
+ include Taksi::Component.new('users/profile_resume')
23
+
24
+ content do
25
+ name Taksi::Dynamic
26
+
27
+ details do
28
+ age Taksi::Dynamic
29
+ email Taksi::Dynamic
30
+ end
31
+ end
32
+ end
33
+ ```
34
+
35
+ Defining a new interface (in this example a interface interface):
36
+
37
+ ```ruby
38
+ class Interfaces::UserProfile
39
+ include Taksi::Interface.new('user_profile')
40
+
41
+ add Components::Users::ProfileResume, with: :profile_data
42
+
43
+ attr_accessor :user
44
+
45
+ def profile_data
46
+ {
47
+ name: user.name,
48
+ details: {
49
+ age: user.age,
50
+ email: user.email,
51
+ }
52
+ }
53
+ end
54
+ end
55
+ ```
56
+
57
+ From those definitions you can set up the skeleton or strip the data:
58
+
59
+ ```ruby
60
+ user_profile = Interfaces::UserProfile.new
61
+ user_profile.skeleton.as_json
62
+ ```
63
+
64
+ Which provide us:
65
+
66
+ ```json
67
+ {
68
+ "components": [
69
+ {
70
+ "name": "users/profile_resume",
71
+ "identifier": "component$0",
72
+ "content": {
73
+ "name": null,
74
+ "details": {
75
+ "age": null,
76
+ "email": null
77
+ }
78
+ }
79
+ }
80
+ ]
81
+ }
82
+ ```
83
+
84
+ Then, you can strip the data off:
85
+
86
+ ```ruby
87
+ user_profile.user = User.find(logged_user_id)
88
+ user_profile.data.as_json
89
+ ```
90
+
91
+ ```json
92
+ {
93
+ "interface_data": [
94
+ {
95
+ "identifier": "component$0",
96
+ "content": {
97
+ "name": "Israel Trindade",
98
+ "details": {
99
+ "age": 29,
100
+ "email": "irto@outlook.com",
101
+ }
102
+ }
103
+ }
104
+ ]
105
+ }
106
+ ```
107
+
108
+ ## Supported Ruby versions
109
+
110
+ This library officially supports the following Ruby versions:
111
+
112
+ * MRI `>= 2.7`
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ class Component < ::Module
5
+ attr_reader :identifier
6
+
7
+ def initialize(identifier)
8
+ @identifier = identifier
9
+ super()
10
+ end
11
+
12
+ def included(klass)
13
+ klass.extend(ClassMethods)
14
+ klass.include(InstanceMethods)
15
+
16
+ klass.definition = self
17
+ end
18
+
19
+ module ClassMethods
20
+ attr_reader :content_builder
21
+
22
+ def definition=(component_definition)
23
+ @component_definition = component_definition
24
+ end
25
+
26
+ def identifier
27
+ @component_definition.identifier
28
+ end
29
+
30
+ def content(&block)
31
+ @content_builder = block
32
+ self
33
+ end
34
+ end
35
+
36
+ module InstanceMethods
37
+ attr_reader :interface_definition, :datasource, :skeleton
38
+
39
+ def initialize(interface_definition, with: nil)
40
+ @interface_definition = interface_definition
41
+ @datasource = with
42
+ @skeleton = @interface_definition.skeleton.create_component(self.class.identifier,
43
+ &self.class.content_builder)
44
+ super()
45
+ end
46
+
47
+ def id
48
+ @skeleton.id
49
+ end
50
+
51
+ def content_for(interface)
52
+ data = interface.send(datasource)
53
+
54
+ skeleton.fields.each_with_object({}) do |field, obj|
55
+ load_data_from_key_to_object(data, field, obj)
56
+ end
57
+ end
58
+
59
+ private
60
+
61
+ def load_data_from_key_to_object(data, field, obj)
62
+ splitted_full_path = field.key.to_s.split('.')
63
+ setter_key = splitted_full_path.pop
64
+ splitted_full_path.shift # remove content root key, as it makes no sense in data object
65
+
66
+ relative_object = splitted_full_path.reduce(obj) do |memo, path_part|
67
+ memo[path_part.to_sym] ||= {}
68
+ end
69
+
70
+ relative_object[setter_key.to_sym] = field.fetch_from(data)
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ module Components
5
+ class Field
6
+ attr_reader :skeleton, :name, :value, :parent
7
+
8
+ def initialize(skeleton, name, *args, parent: nil, &block)
9
+ @skeleton = skeleton
10
+ @name = name.to_sym
11
+ @parent = parent
12
+
13
+ @value = args.shift.new(skeleton, name, *args) if args.size.positive?
14
+ @nested_fields = []
15
+
16
+ instance_exec(&block) if block_given?
17
+ @defined = true
18
+ end
19
+
20
+ def key
21
+ return name if parent.nil? || parent.root?
22
+
23
+ "#{parent.name}.#{name}"
24
+ end
25
+
26
+ def fetch_from(data)
27
+ return value.as_json if value.static?
28
+
29
+ return data[name] if parent.nil? || parent.root?
30
+
31
+ parent.fetch_from(data)[name]
32
+ rescue NoMethodError
33
+ raise NameError, "Couldn't fetch #{key.inspect} from data: #{data.inspect}"
34
+ end
35
+
36
+ def as_json
37
+ return {name => @nested_fields.map(&:as_json).inject({}, &:merge)} if nested?
38
+
39
+ {name => value.as_json}
40
+ end
41
+
42
+ def fields
43
+ Enumerator.new do |yielder|
44
+ @nested_fields.each do |field|
45
+ if field.nested?
46
+ field.fields.each(&yielder)
47
+ else
48
+ yielder << field
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ def nested?
55
+ @value.nil?
56
+ end
57
+
58
+ def root?
59
+ @parent.nil?
60
+ end
61
+
62
+ def method_missing(name, *args, &block)
63
+ return super if @defined
64
+
65
+ @nested_fields << self.class.new(skeleton, name, *args, parent: self, &block)
66
+ end
67
+
68
+ def respond_to_missing?(name)
69
+ return super if @defined
70
+
71
+ true
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ module Components
5
+ class Skeleton
6
+ attr_reader :parent, :name, :content
7
+
8
+ def initialize(parent, name, &block)
9
+ @parent = parent
10
+ @name = name
11
+
12
+ @content = ::Taksi::Components::Field.new(self, :content, &block)
13
+ end
14
+
15
+ def id
16
+ parent.id_of(self)
17
+ end
18
+
19
+ def fields
20
+ content.fields
21
+ end
22
+
23
+ def as_json
24
+ {
25
+ name: name,
26
+ identifier: id
27
+ }.tap do |json|
28
+ json.merge!(content.as_json)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ class Interface < ::Module
5
+ attr_reader :interface_name, :version_pattern, :alternatives
6
+
7
+ def self.find(name, version, alternative: nil)
8
+ ::Taksi::Registry.find(name, version, alternative)
9
+ end
10
+
11
+ def initialize(name, version_pattern = nil, alternatives: nil)
12
+ @interface_name = name
13
+ @version_pattern = ::Gem::Requirement.new(version_pattern)
14
+ @alternatives = alternatives
15
+ super()
16
+ end
17
+
18
+ def included(klass)
19
+ klass.extend(ClassMethods)
20
+ klass.include(InstanceMethods)
21
+
22
+ klass.initiate(self)
23
+
24
+ ::Taksi::Registry.add(klass, interface_name)
25
+ end
26
+
27
+ module ClassMethods
28
+ attr_reader :skeleton
29
+
30
+ def find(version, alternative = nil)
31
+ ::Taksi::Registry.find(interface_name, version, alternative)
32
+ end
33
+
34
+ def initiate(interface_definition)
35
+ @components = []
36
+ @interface_definition = interface_definition
37
+ @skeleton = ::Taksi::Interfaces::Skeleton.new
38
+ end
39
+
40
+ def add(component_class, with: nil)
41
+ @components << component_class.new(self, with: with)
42
+ end
43
+
44
+ def components
45
+ @components.each
46
+ end
47
+
48
+ def version_pattern
49
+ @interface_definition.version_pattern
50
+ end
51
+
52
+ def alternatives
53
+ @interface_definition.alternatives
54
+ end
55
+ end
56
+
57
+ module InstanceMethods
58
+ def skeleton
59
+ self.class.skeleton
60
+ end
61
+
62
+ def data
63
+ self.class.components.map do |component|
64
+ {
65
+ identifier: component.id,
66
+ content: component.content_for(self)
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ module Interfaces
5
+ class Skeleton
6
+ class ComponentNotFoundError < StandardError; end
7
+
8
+ def initialize
9
+ @component_skeletons = []
10
+ end
11
+
12
+ def create_component(identifier, &block)
13
+ ::Taksi::Components::Skeleton.new(self, identifier, &block).tap do |component|
14
+ add(component)
15
+ end
16
+ end
17
+
18
+ def add(component_skeleton)
19
+ @component_skeletons << component_skeleton
20
+ self
21
+ end
22
+
23
+ def id_of(component)
24
+ index = @component_skeletons.index(component)
25
+
26
+ raise ComponentNotFoundError unless index
27
+
28
+ "component$#{index}"
29
+ end
30
+
31
+ def as_json(*)
32
+ {components: @component_skeletons.map(&:as_json)}
33
+ end
34
+ end
35
+ end
36
+ end
@@ -11,8 +11,8 @@ module Taksi
11
11
  def_delegators :instance, :find, :add, :clear!
12
12
  end
13
13
 
14
- class ScreenNotFoundError < ::StandardError; end
15
- class ScreenAltenativeNotFoundError < ::StandardError; end
14
+ class InterfaceNotFoundError < ::StandardError; end
15
+ class InterfaceAltenativeNotFoundError < ::StandardError; end
16
16
 
17
17
  def initialize
18
18
  clear!
@@ -21,36 +21,36 @@ module Taksi
21
21
  def add(klass, name)
22
22
  sym_name = name.to_sym
23
23
 
24
- @screens[sym_name] ||= []
25
- @screens[sym_name] << klass
24
+ @interfaces[sym_name] ||= []
25
+ @interfaces[sym_name] << klass
26
26
  end
27
27
 
28
28
  def clear!
29
- @screens = {}
29
+ @interfaces = {}
30
30
  end
31
31
 
32
32
  def find(name, version, alternative = nil)
33
- screens_from_name = @screens[name.to_sym]
33
+ interfaces_from_name = @interfaces[name.to_sym]
34
34
 
35
- raise ScreenNotFoundError if screens_from_name.blank?
35
+ raise InterfaceNotFoundError if interfaces_from_name.nil?
36
36
 
37
37
  parsed_version = ::Gem::Version.new(version)
38
38
 
39
- screen = screens_from_name.find do |screen|
40
- next false unless screen.version_pattern.satisfied_by?(parsed_version)
39
+ found_interface = interfaces_from_name.find do |interface|
40
+ next false unless interface.version_pattern.satisfied_by?(parsed_version)
41
41
 
42
- next true if alternative.blank?
42
+ next true if alternative.nil?
43
43
 
44
- next true if screen.alternatives.blank?
44
+ next true if interface.alternatives.blank?
45
45
 
46
- next true if screen.alternatives.include?(alternative)
46
+ next true if interface.alternatives.include?(alternative)
47
47
 
48
48
  false
49
49
  end
50
50
 
51
- raise ScreenNotFoundError if screen.blank?
51
+ raise InterfaceNotFoundError if found_interface.nil?
52
52
 
53
- screen
53
+ found_interface
54
54
  end
55
55
  end
56
56
  end
@@ -3,27 +3,31 @@
3
3
  module Taksi
4
4
  module Values
5
5
  class Dynamic
6
- attr_reader :widget, :name, :content_key
6
+ attr_reader :component, :name, :field
7
7
 
8
- def initialize(widget, name, content_key = nil)
9
- @widget = widget
8
+ def initialize(component, name, field = nil)
9
+ @component = component
10
10
  @name = name
11
- @content_key = content_key
11
+ @field = field
12
12
  end
13
13
 
14
14
  def path
15
- return content_key if content_key
15
+ return field if field
16
16
 
17
- "#{widget.id}.#{name}"
17
+ "#{component.id}.#{name}"
18
18
  end
19
19
 
20
20
  def as_json
21
- {type: 'dynamic', value: path}
21
+ nil
22
22
  end
23
23
 
24
24
  def dynamic?
25
25
  true
26
26
  end
27
+
28
+ def static?
29
+ false
30
+ end
27
31
  end
28
32
  end
29
33
 
@@ -3,21 +3,25 @@
3
3
  module Taksi
4
4
  module Values
5
5
  class Static
6
- attr_reader :widget, :name, :value
6
+ attr_reader :component, :name, :value
7
7
 
8
- def initialize(widget, name, value)
9
- @widget = widget
8
+ def initialize(component, name, value)
9
+ @component = component
10
10
  @name = name
11
11
  @value = value
12
12
  end
13
13
 
14
14
  def as_json
15
- {type: 'static', value: value}
15
+ value
16
16
  end
17
17
 
18
18
  def dynamic?
19
19
  false
20
20
  end
21
+
22
+ def static?
23
+ true
24
+ end
21
25
  end
22
26
  end
23
27
 
data/lib/taksi/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Taksi
4
- VERSION = '0.1.0'
4
+ VERSION = '0.2.1'
5
5
  end
data/lib/taksi.rb CHANGED
@@ -7,14 +7,14 @@ require 'taksi/version'
7
7
  require 'taksi/values/dynamic'
8
8
  require 'taksi/values/static'
9
9
 
10
- require 'taksi/widget'
11
- require 'taksi/widgets/skeleton'
12
- require 'taksi/widgets/content_key'
10
+ require 'taksi/component'
11
+ require 'taksi/components/skeleton'
12
+ require 'taksi/components/field'
13
13
 
14
14
  require 'taksi/registry'
15
15
 
16
- require 'taksi/screen'
17
- require 'taksi/screens/skeleton'
16
+ require 'taksi/interface'
17
+ require 'taksi/interfaces/skeleton'
18
18
 
19
19
  module Taksi
20
20
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: taksi
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Israel Trindade
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-07-07 00:00:00.000000000 Z
11
+ date: 2023-07-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rspec
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -38,8 +52,8 @@ dependencies:
38
52
  - - ">="
39
53
  - !ruby/object:Gem::Version
40
54
  version: '0'
41
- description: Useful tool to help build a backend over the concept of Backend For Frontend,
42
- or backend-driven frontends.
55
+ description: Useful toolset to build a backend over the concept of backend-driven
56
+ UI (aka. backend for frontend).
43
57
  email:
44
58
  - irto@outlook.com
45
59
  executables: []
@@ -50,15 +64,15 @@ files:
50
64
  - README.md
51
65
  - Rakefile
52
66
  - lib/taksi.rb
67
+ - lib/taksi/component.rb
68
+ - lib/taksi/components/field.rb
69
+ - lib/taksi/components/skeleton.rb
70
+ - lib/taksi/interface.rb
71
+ - lib/taksi/interfaces/skeleton.rb
53
72
  - lib/taksi/registry.rb
54
- - lib/taksi/screen.rb
55
- - lib/taksi/screens/skeleton.rb
56
73
  - lib/taksi/values/dynamic.rb
57
74
  - lib/taksi/values/static.rb
58
75
  - lib/taksi/version.rb
59
- - lib/taksi/widget.rb
60
- - lib/taksi/widgets/content_key.rb
61
- - lib/taksi/widgets/skeleton.rb
62
76
  homepage:
63
77
  licenses: []
64
78
  metadata:
@@ -84,5 +98,5 @@ requirements: []
84
98
  rubygems_version: 3.3.26
85
99
  signing_key:
86
100
  specification_version: 4
87
- summary: A tool to design backend driver for frontend
101
+ summary: Application framework to build a backend driven UI in ruby
88
102
  test_files: []
data/lib/taksi/screen.rb DELETED
@@ -1,68 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Taksi
4
- class Screen < ::Module
5
- attr_reader :page_name, :version_pattern, :alternatives
6
-
7
- def self.find(name, version, alternative: nil)
8
- ::Taksi::Registry.find(name, version, alternative)
9
- end
10
-
11
- def initialize(name, version_pattern = nil, alternatives: nil)
12
- @page_name = name
13
- @version_pattern = ::Gem::Requirement.new(version_pattern)
14
- @alternatives = alternatives
15
- end
16
-
17
- def included(klass)
18
- klass.extend(ClassMethods)
19
- klass.include(InstanceMethods)
20
-
21
- klass.initiate(self)
22
-
23
- ::Taksi::Registry.add(klass, page_name)
24
- end
25
-
26
- module ClassMethods
27
- attr_reader :widgets, :skeleton
28
-
29
- def find(version, alternative = nil)
30
- ::Taksi::Registry.find(page_name, version, alternative)
31
- end
32
-
33
- def initiate(screen_definition)
34
- @widgets = []
35
- @screen_definition = screen_definition
36
- @skeleton = ::Taksi::Screens::Skeleton.new
37
- end
38
-
39
- def add(widget_class, with: nil)
40
- @widgets << widget_class.new(self, with: with)
41
- end
42
-
43
- def widgets
44
- @widgets.each
45
- end
46
-
47
- def version_pattern
48
- @screen_definition.version_pattern
49
- end
50
-
51
- def alternatives
52
- @screen_definition.alternatives
53
- end
54
- end
55
-
56
- module InstanceMethods
57
- def skeleton
58
- self.class.skeleton
59
- end
60
-
61
- def data
62
- self.class.widgets.each_with_object({}) do |widget, obj|
63
- obj.merge!(widget.data_for(self).as_json)
64
- end
65
- end
66
- end
67
- end
68
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Taksi
4
- module Screens
5
- class Skeleton
6
- class WidgetNotFoundError < StandardError; end
7
-
8
- def initialize
9
- @widget_skeletons = []
10
- end
11
-
12
- def create_widget(identifier, &block)
13
- ::Taksi::Widgets::Skeleton.new(self, identifier, &block).tap do |widget|
14
- add(widget)
15
- end
16
- end
17
-
18
- def add(widget_skeleton)
19
- @widget_skeletons << widget_skeleton
20
- self
21
- end
22
-
23
- def id_of(widget)
24
- index = @widget_skeletons.index(widget)
25
-
26
- raise WidgetNotFoundError unless index
27
-
28
- "widget$#{index}"
29
- end
30
-
31
- def as_json(*)
32
- {widgets: @widget_skeletons.map(&:as_json)}
33
- end
34
- end
35
- end
36
- end
data/lib/taksi/widget.rb DELETED
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Taksi
4
- class Widget < ::Module
5
- attr_reader :identifier
6
-
7
- def initialize(identifier)
8
- @identifier = identifier
9
- end
10
-
11
- def included(klass)
12
- klass.extend(ClassMethods)
13
- klass.include(InstanceMethods)
14
-
15
- klass.definition = self
16
- end
17
-
18
- module ClassMethods
19
- attr_reader :content_builder
20
-
21
- def definition=(widget_definition)
22
- @widget_definition = widget_definition
23
- end
24
-
25
- def identifier
26
- @widget_definition.identifier
27
- end
28
-
29
- def content(&block)
30
- @content_builder = block
31
- self
32
- end
33
- end
34
-
35
- module InstanceMethods
36
- attr_reader :page_definition, :datasource, :skeleton
37
-
38
- def initialize(page_definition, with: nil)
39
- @page_definition = page_definition
40
- @datasource = with
41
- @skeleton = @page_definition.skeleton.create_widget(self.class.identifier,
42
- &self.class.content_builder)
43
- end
44
-
45
- def data_for(page_instance)
46
- data = page_instance.send(datasource)
47
-
48
- skeleton.keys.each_with_object({}) do |content_key, obj|
49
- next unless content_key.value.dynamic?
50
-
51
- load_data_from_key_to_object(data, content_key, obj)
52
- end
53
- end
54
-
55
- private
56
-
57
- def load_data_from_key_to_object(data, content_key, obj)
58
- splitted_full_path = content_key.value.path.split('.')
59
- setter_key = splitted_full_path.pop
60
-
61
- relative_object = splitted_full_path.reduce(obj) do |memo, path_part|
62
- memo[path_part] ||= {}
63
- end
64
-
65
- relative_object[setter_key] = content_key.fetch_from(data)
66
- end
67
- end
68
- end
69
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Taksi
4
- module Widgets
5
- class ContentKey
6
- attr_reader :skeleton, :key, :value, :parent
7
-
8
- def initialize(skeleton, key, *args, parent: nil, &block)
9
- @skeleton = skeleton
10
- @key = key.to_sym
11
- @parent = parent
12
-
13
- @value = args.shift.new(skeleton, full_key, *args) if args.size.positive?
14
- @nested_keys = []
15
-
16
- instance_exec(&block) if block_given?
17
- @defined = true
18
- end
19
-
20
- def full_key
21
- return key if parent.nil? || parent.root?
22
-
23
- "#{parent.full_key}.#{key}"
24
- end
25
-
26
- def fetch_from(data)
27
- return data[key] if parent.nil? || parent.root?
28
-
29
- parent.fetch_from(data)[key]
30
- rescue NoMethodError
31
- raise NameError, "Couldn't fetch #{key.inspect} from data: #{data.inspect}"
32
- end
33
-
34
- def as_json
35
- return {key => @nested_keys.map(&:as_json).inject({}, &:merge)} if nested?
36
-
37
- {key => value.as_json}
38
- end
39
-
40
- def keys
41
- Enumerator.new do |yielder|
42
- @nested_keys.each do |key|
43
- if key.nested?
44
- key.keys.each(&yielder)
45
- else
46
- yielder << key
47
- end
48
- end
49
- end
50
- end
51
-
52
- def nested?
53
- @value.nil?
54
- end
55
-
56
- def root?
57
- @parent.nil?
58
- end
59
-
60
- def method_missing(name, *args, &block)
61
- return super if @defined
62
-
63
- @nested_keys << self.class.new(skeleton, name, *args, parent: self, &block)
64
- end
65
- end
66
- end
67
- end
@@ -1,30 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Taksi
4
- module Widgets
5
- class Skeleton
6
- attr_reader :parent, :identifier, :content
7
-
8
- def initialize(parent, identifier, &block)
9
- @parent = parent
10
- @identifier = identifier
11
-
12
- @content = ::Taksi::Widgets::ContentKey.new(self, :content, &block)
13
- end
14
-
15
- def id
16
- parent.id_of(self)
17
- end
18
-
19
- def keys
20
- content.keys
21
- end
22
-
23
- def as_json
24
- {identifier: identifier}.tap do |json|
25
- json.merge!(content.as_json)
26
- end
27
- end
28
- end
29
- end
30
- end