taksi 0.1.0 → 0.2.2

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: e57c4a5876a120b859b3642f790bf13d7f45a691ca74e2a3967d094589b2605a
4
+ data.tar.gz: da96e2c711489b11c4d24f88cb8e3ae738774c484772e3a9c5e0106ab13f5573
5
5
  SHA512:
6
- metadata.gz: 9bab30c33f37c2aceb47b04d6c249030f71a2fd16762569e604c371bba86322a822575d84330e7d69143b46bd3096b5bf5752e79cedd8c7f3d8ead7fe30fc53a
7
- data.tar.gz: 3ef033f144438741084fc36291923cd04a001fb802b9d5091fea7f4fc43169da7d987e14d68954627890134b642daeba0e84523330d0b0dd530080a9cfb5f8e2
6
+ metadata.gz: 1f52db226be934f1c70c127f2c75b105c1cdc65efd8443a76e338cb62769c95f2511e3b282521d753d6e34cc8791282889b7ef8d05bc471e3ad6b0fc87f8c316
7
+ data.tar.gz: 229d3fbd97e55eaa3e70a3dd735e2e3b6f4947d9b3b91a962d73a2680c8bccd0c9d04af2a63bff3fcd37810519e65af3c52adb37111c801b8098d2ebdec793e9
data/README.md CHANGED
@@ -0,0 +1,115 @@
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
+ profile_kind Taksi::Static, 'resume'
27
+
28
+ details do
29
+ age Taksi::Dynamic
30
+ email Taksi::Dynamic
31
+ end
32
+ end
33
+ end
34
+ ```
35
+
36
+ Defining a new interface (in this example a interface interface):
37
+
38
+ ```ruby
39
+ class Interfaces::UserProfile
40
+ include Taksi::Interface.new('user_profile')
41
+
42
+ add Components::Users::ProfileResume, with: :profile_data
43
+
44
+ attr_accessor :user
45
+
46
+ def profile_data
47
+ {
48
+ name: user.name,
49
+ details: {
50
+ age: user.age,
51
+ email: user.email,
52
+ }
53
+ }
54
+ end
55
+ end
56
+ ```
57
+
58
+ From those definitions you can set up the skeleton or strip the data:
59
+
60
+ ```ruby
61
+ user_profile = Interfaces::UserProfile.new
62
+ user_profile.skeleton.as_json
63
+ ```
64
+
65
+ Which provide us:
66
+
67
+ ```json
68
+ {
69
+ "components": [
70
+ {
71
+ "name": "users/profile_resume",
72
+ "identifier": "component$0",
73
+ "requires_data": true,
74
+ "content": {
75
+ "name": null,
76
+ "profile_kind": "resume",
77
+ "details": {
78
+ "age": null,
79
+ "email": null
80
+ }
81
+ }
82
+ }
83
+ ]
84
+ }
85
+ ```
86
+
87
+ Then, you can strip the data off:
88
+
89
+ ```ruby
90
+ user_profile.user = User.find(logged_user_id)
91
+ user_profile.data.as_json
92
+ ```
93
+
94
+ ```json
95
+ {
96
+ "interface_data": [
97
+ {
98
+ "identifier": "component$0",
99
+ "content": {
100
+ "name": "Israel Trindade",
101
+ "details": {
102
+ "age": 29,
103
+ "email": "irto@outlook.com",
104
+ }
105
+ }
106
+ }
107
+ ]
108
+ }
109
+ ```
110
+
111
+ ## Supported Ruby versions
112
+
113
+ This library officially supports the following Ruby versions:
114
+
115
+ * MRI `>= 2.7`
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ # A custom module to turn a class into a component on taksi protocol
5
+ #
6
+ # ```ruby
7
+ # class CustomComponent
8
+ # include Taksi::Component.new('customs/component_name')
9
+ #
10
+ # content do
11
+ # field_name Taksi::Static
12
+ # end
13
+ # end
14
+ # ```
15
+ #
16
+ class Component < ::Module
17
+ attr_reader :identifier
18
+
19
+ def initialize(identifier)
20
+ @identifier = identifier
21
+ super()
22
+ end
23
+
24
+ def included(klass)
25
+ klass.extend(ClassMethods)
26
+ klass.include(InstanceMethods)
27
+
28
+ klass.definition = self
29
+ end
30
+
31
+ module ClassMethods
32
+ attr_reader :content_builder
33
+
34
+ def definition=(component_definition)
35
+ @component_definition = component_definition
36
+ end
37
+
38
+ def identifier
39
+ @component_definition.identifier
40
+ end
41
+
42
+ def content(&block)
43
+ @content_builder = block
44
+ self
45
+ end
46
+ end
47
+
48
+ module InstanceMethods
49
+ attr_reader :interface_definition, :datasource, :skeleton
50
+
51
+ def initialize(interface_definition, with: nil)
52
+ @interface_definition = interface_definition
53
+ @datasource = with
54
+ @skeleton = @interface_definition.skeleton.create_component(self.class.identifier,
55
+ &self.class.content_builder)
56
+ super()
57
+ end
58
+
59
+ def id
60
+ @skeleton.id
61
+ end
62
+
63
+ def content_for(interface)
64
+ data = interface.send(datasource)
65
+
66
+ skeleton.fields.each_with_object({}) do |field, obj|
67
+ load_data_from_key_to_object(data, field, obj)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def load_data_from_key_to_object(data, field, obj)
74
+ splitted_full_path = field.key.to_s.split('.')
75
+ setter_key = splitted_full_path.pop
76
+ splitted_full_path.shift # remove content root key, as it makes no sense in data object
77
+
78
+ relative_object = splitted_full_path.reduce(obj) do |memo, path_part|
79
+ memo[path_part.to_sym] ||= {}
80
+ end
81
+
82
+ relative_object[setter_key.to_sym] = field.fetch_from(data)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,92 @@
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
+ raise <<~MESSAGE unless args.size.positive? || block_given?
14
+ You must provide a value or a block definition to build field
15
+ MESSAGE
16
+
17
+ @value = args.shift.new(skeleton, name, *args) if args.size.positive?
18
+ @nested_fields = []
19
+
20
+ instance_exec(&block) if block_given?
21
+ @defined = true
22
+ end
23
+
24
+ def key
25
+ return name if parent.nil? || parent.root?
26
+
27
+ "#{parent.name}.#{name}"
28
+ end
29
+
30
+ # Fetches the data for in `data` for the current field
31
+ # @return any
32
+ def fetch_from(data)
33
+ return value.as_json if value.static?
34
+
35
+ return data[name] if parent.nil? || parent.root?
36
+
37
+ parent.fetch_from(data)[name]
38
+ rescue NoMethodError
39
+ raise NameError, "Couldn't fetch #{key.inspect} from data: #{data.inspect}"
40
+ end
41
+
42
+ # Turns the field into his json representation
43
+ # The returned hash is compatible with the skeleton json specification
44
+ # @return Hash
45
+ def as_json
46
+ return {name => @nested_fields.map(&:as_json).inject({}, &:merge)} if nested?
47
+
48
+ {name => value.as_json}
49
+ end
50
+
51
+ # Builds up a interator over all fields included nested ones
52
+ # @returns Enumerable
53
+ def fields
54
+ Enumerator.new do |yielder|
55
+ @nested_fields.each do |field|
56
+ if field.nested?
57
+ field.fields.each(&yielder)
58
+ else
59
+ yielder << field
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ def nested?
66
+ @value.nil?
67
+ end
68
+
69
+ def root?
70
+ @parent.nil?
71
+ end
72
+
73
+ def dynamic?
74
+ return @nested_fields.any?(&:dynamic?) if @value.nil?
75
+
76
+ @value.dynamic?
77
+ end
78
+
79
+ def method_missing(name, *args, &block)
80
+ return super if @defined
81
+
82
+ @nested_fields << self.class.new(skeleton, name, *args, parent: self, &block)
83
+ end
84
+
85
+ def respond_to_missing?(name, *)
86
+ return super if @defined
87
+
88
+ true
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,40 @@
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
+ raise 'To build a component you need to provide a `content` block' unless block_given?
13
+
14
+ @content = ::Taksi::Components::Field.new(self, :content, &block)
15
+ end
16
+
17
+ def id
18
+ parent.id_of(self)
19
+ end
20
+
21
+ def fields
22
+ content.fields
23
+ end
24
+
25
+ def dynamic?
26
+ @content.dynamic?
27
+ end
28
+
29
+ def as_json
30
+ {
31
+ name: name,
32
+ identifier: id,
33
+ requires_data: dynamic?
34
+ }.tap do |json|
35
+ json.merge!(content.as_json)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Taksi
4
+ # A custom module that turns a class into a interface on taksi protocol
5
+ #
6
+ class Interface < ::Module
7
+ attr_reader :interface_name, :version_pattern, :alternatives
8
+
9
+ # Finds for a interface by its name and the current version
10
+ # @param name [String]
11
+ # @param version [String] just like '0.2.1'
12
+ # @param alternatives: [Array]
13
+ # @raises`::Taksi::Registry::InterfaceNotFoundError`
14
+ # @return Class the class of interface
15
+ def self.find(name, version, alternative: nil)
16
+ ::Taksi::Registry.find(name, version, alternative)
17
+ end
18
+
19
+ def initialize(name, version_pattern = nil, alternatives: nil)
20
+ @interface_name = name
21
+ @version_pattern = ::Gem::Requirement.new(version_pattern)
22
+ @alternatives = alternatives
23
+ super()
24
+ end
25
+
26
+ def included(klass)
27
+ klass.extend(ClassMethods)
28
+ klass.include(InstanceMethods)
29
+
30
+ klass.initiate(self)
31
+
32
+ ::Taksi::Registry.add(klass, interface_name)
33
+ end
34
+
35
+ module ClassMethods
36
+ attr_reader :skeleton
37
+
38
+ def find(version, alternative = nil)
39
+ ::Taksi::Registry.find(@interface_definition.interface_name, version, alternative)
40
+ end
41
+
42
+ def initiate(interface_definition)
43
+ @components = []
44
+ @interface_definition = interface_definition
45
+ @skeleton = ::Taksi::Interfaces::Skeleton.new
46
+ end
47
+
48
+ def add(component_class, with: nil)
49
+ @components << component_class.new(self, with: with)
50
+ end
51
+
52
+ def components
53
+ @components.each
54
+ end
55
+
56
+ def version_pattern
57
+ @interface_definition.version_pattern
58
+ end
59
+
60
+ def alternatives
61
+ @interface_definition.alternatives
62
+ end
63
+ end
64
+
65
+ module InstanceMethods
66
+ def skeleton
67
+ self.class.skeleton
68
+ end
69
+
70
+ def data
71
+ self.class.components.map do |component|
72
+ {
73
+ identifier: component.id,
74
+ content: component.content_for(self)
75
+ }
76
+ end
77
+ end
78
+ end
79
+ end
80
+ 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
@@ -1,6 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'forwardable'
4
3
  module Taksi
5
4
  class Registry
6
5
  include ::Singleton
@@ -11,8 +10,8 @@ module Taksi
11
10
  def_delegators :instance, :find, :add, :clear!
12
11
  end
13
12
 
14
- class ScreenNotFoundError < ::StandardError; end
15
- class ScreenAltenativeNotFoundError < ::StandardError; end
13
+ class InterfaceNotFoundError < ::StandardError; end
14
+ class InterfaceAltenativeNotFoundError < ::StandardError; end
16
15
 
17
16
  def initialize
18
17
  clear!
@@ -21,36 +20,36 @@ module Taksi
21
20
  def add(klass, name)
22
21
  sym_name = name.to_sym
23
22
 
24
- @screens[sym_name] ||= []
25
- @screens[sym_name] << klass
23
+ @interfaces[sym_name] ||= []
24
+ @interfaces[sym_name] << klass
26
25
  end
27
26
 
28
27
  def clear!
29
- @screens = {}
28
+ @interfaces = {}
30
29
  end
31
30
 
32
31
  def find(name, version, alternative = nil)
33
- screens_from_name = @screens[name.to_sym]
32
+ interfaces_from_name = @interfaces[name.to_sym]
34
33
 
35
- raise ScreenNotFoundError if screens_from_name.blank?
34
+ raise InterfaceNotFoundError if interfaces_from_name.nil?
36
35
 
37
36
  parsed_version = ::Gem::Version.new(version)
38
37
 
39
- screen = screens_from_name.find do |screen|
40
- next false unless screen.version_pattern.satisfied_by?(parsed_version)
38
+ found_interface = interfaces_from_name.find do |interface|
39
+ next false unless interface.version_pattern.satisfied_by?(parsed_version)
41
40
 
42
- next true if alternative.blank?
41
+ next true if alternative.nil?
43
42
 
44
- next true if screen.alternatives.blank?
43
+ next true if interface.alternatives.nil?
45
44
 
46
- next true if screen.alternatives.include?(alternative)
45
+ next true if interface.alternatives.include?(alternative)
47
46
 
48
47
  false
49
48
  end
50
49
 
51
- raise ScreenNotFoundError if screen.blank?
50
+ raise InterfaceNotFoundError if found_interface.nil?
52
51
 
53
- screen
52
+ found_interface
54
53
  end
55
54
  end
56
55
  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.2'
5
5
  end
data/lib/taksi.rb CHANGED
@@ -1,20 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'singleton'
4
+ require 'forwardable'
4
5
 
5
6
  require 'taksi/version'
6
7
 
7
8
  require 'taksi/values/dynamic'
8
9
  require 'taksi/values/static'
9
10
 
10
- require 'taksi/widget'
11
- require 'taksi/widgets/skeleton'
12
- require 'taksi/widgets/content_key'
11
+ require 'taksi/component'
12
+ require 'taksi/components/skeleton'
13
+ require 'taksi/components/field'
13
14
 
14
15
  require 'taksi/registry'
15
16
 
16
- require 'taksi/screen'
17
- require 'taksi/screens/skeleton'
17
+ require 'taksi/interface'
18
+ require 'taksi/interfaces/skeleton'
18
19
 
19
20
  module Taksi
20
21
  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.2
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-12 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