welltreat-store-framework 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/README.md +25 -0
  2. data/lib/generators/flexi_model/install/install_generator.rb +20 -0
  3. data/lib/generators/flexi_model/install/templates/create_flexi_model_collections.rb +17 -0
  4. data/lib/generators/flexi_model/install/templates/create_flexi_model_collections_fields.rb +11 -0
  5. data/lib/generators/flexi_model/install/templates/create_flexi_model_fields.rb +18 -0
  6. data/lib/generators/flexi_model/install/templates/create_flexi_model_records.rb +12 -0
  7. data/lib/generators/flexi_model/install/templates/create_flexi_model_values.rb +18 -0
  8. data/lib/welltreat_store_framework/configuration.rb +17 -0
  9. data/lib/welltreat_store_framework/controller.rb +131 -0
  10. data/lib/welltreat_store_framework/core.rb +66 -0
  11. data/lib/welltreat_store_framework/haml_renderer/lorem_helper.rb +29 -0
  12. data/lib/welltreat_store_framework/haml_renderer/partial.rb +31 -0
  13. data/lib/welltreat_store_framework/haml_renderer/paths.rb +51 -0
  14. data/lib/welltreat_store_framework/haml_renderer/tags_helper.rb +74 -0
  15. data/lib/welltreat_store_framework/haml_renderer.rb +163 -0
  16. data/lib/welltreat_store_framework/rack_server.rb +56 -0
  17. data/lib/welltreat_store_framework/store_app.rb +282 -0
  18. data/lib/welltreat_store_framework.rb +9 -0
  19. data/sample/hello-store/assets/javascripts/app.js +1 -0
  20. data/sample/hello-store/controllers/home.rb +15 -0
  21. data/sample/hello-store/controllers/products.rb +18 -0
  22. data/sample/hello-store/models/product.rb +9 -0
  23. data/sample/hello-store/mount.rb +0 -0
  24. data/sample/hello-store/views/home/index.haml +14 -0
  25. data/sample/hello-store/views/layouts/default.html.haml +10 -0
  26. data/sample/hello-store/views/products/_product.html.haml +1 -0
  27. data/sample/hello-store/views/products/index.haml +4 -0
  28. data/sample/hello-store/views/products/show.html.haml +10 -0
  29. data/spec/fixtures/schema.rb +78 -0
  30. data/spec/lib/welltreat_store_framework/configuration_spec.rb +5 -0
  31. data/spec/lib/welltreat_store_framework/core_spec.rb +190 -0
  32. data/spec/lib/welltreat_store_framework/haml_renderer/context_spec.rb +73 -0
  33. data/spec/lib/welltreat_store_framework/store_app_spec.rb +85 -0
  34. data/spec/lib/welltreat_store_framework_spec.rb +5 -0
  35. data/spec/sample/controllers/products_spec.rb +140 -0
  36. data/spec/spec_helper/active_record.rb +8 -0
  37. data/spec/spec_helper/models.rb +0 -0
  38. data/spec/spec_helper/rspec.rb +3 -0
  39. data/spec/spec_helper.rb +19 -0
  40. metadata +129 -0
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ Well Treat Store Framework
2
+ ===========
3
+
4
+ Build store just like your another web site building project
5
+
6
+ Features
7
+ ===========
8
+
9
+ Micro framework
10
+
11
+ Relation
12
+
13
+ Filter
14
+
15
+ Dynamic Filter
16
+
17
+ Dynamic Attributes
18
+
19
+ Validations
20
+
21
+ TODOS
22
+ =====
23
+
24
+ * Abstract persistent api
25
+ * Expose
@@ -0,0 +1,20 @@
1
+ module FlexiModel
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ include Rails::Generators::Migration
5
+ source_root File.expand_path('../templates', __FILE__)
6
+
7
+ # Implement the required interface for Rails::Generators::Migration.
8
+ def self.next_migration_number(dirname) #:nodoc:
9
+ next_migration_number = current_migration_number(dirname) + 1
10
+ ActiveRecord::Migration.next_migration_number(next_migration_number)
11
+ end
12
+
13
+ def generate_install
14
+ Dir.glob(File.join(File.dirname(__FILE__), 'templates/*.rb')).each do |file|
15
+ migration_template file, "db/migrate/#{file.split('/').last}"
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,17 @@
1
+ class CreateFlexiModelCollections < ActiveRecord::Migration
2
+ def change
3
+ create_table "flexi_model_collections" do |t|
4
+ t.string 'name'
5
+ t.string 'singular_label'
6
+ t.string 'plural_label'
7
+ t.string 'namespace'
8
+ t.integer 'partition_id'
9
+
10
+ t.datetime "created_at"
11
+ t.datetime "updated_at"
12
+ end
13
+
14
+ add_index "flexi_model_collections", [:namespace, :name]
15
+ add_index "flexi_model_collections", [:namespace, :name, :partition_id], name: 'index_ns_name_pi'
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ class CreateFlexiModelCollectionsFields < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_collections_fields', :force => true do |t|
4
+ t.integer 'collection_id'
5
+ t.integer 'field_id'
6
+ end
7
+
8
+ add_index 'flexi_model_collections_fields', [:collection_id]
9
+ add_index 'flexi_model_collections_fields', [:field_id]
10
+ end
11
+ end
@@ -0,0 +1,18 @@
1
+ class CreateFlexiModelFields < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_fields', :force => true do |t|
4
+ t.string 'name'
5
+ t.string 'singular_label'
6
+ t.string 'plural_label'
7
+ t.string 'namespace'
8
+ t.integer 'partition_id'
9
+
10
+ t.string 'field_type'
11
+ t.text 'default_value'
12
+ end
13
+
14
+ add_index 'flexi_model_fields', [:namespace, :name]
15
+ add_index 'flexi_model_fields', [:namespace, :name, :field_type]
16
+ add_index 'flexi_model_fields', [:namespace, :name, :field_type, :partition_id], name: 'index_ns_name_ft_pi'
17
+ end
18
+ end
@@ -0,0 +1,12 @@
1
+ class CreateFlexiModelRecords < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_records', :force => true do |t|
4
+ t.integer 'collection_id'
5
+ t.string 'namespace'
6
+ t.datetime "created_at"
7
+ t.datetime "updated_at"
8
+ end
9
+
10
+ add_index 'flexi_model_records', [:namespace, :collection_id]
11
+ end
12
+ end
@@ -0,0 +1,18 @@
1
+ class CreateFlexiModelValues < ActiveRecord::Migration
2
+ def change
3
+ create_table 'flexi_model_values', :force => true do |t|
4
+ t.integer 'record_id'
5
+ t.integer 'field_id'
6
+ t.boolean 'bool_value'
7
+ t.integer 'int_value'
8
+ t.decimal 'dec_value'
9
+ t.string 'str_value'
10
+ t.text 'txt_value'
11
+ t.datetime 'dt_value'
12
+ end
13
+
14
+ add_index 'flexi_model_values', [:record_id]
15
+ add_index 'flexi_model_values', [:field_id]
16
+
17
+ end
18
+ end
@@ -0,0 +1,17 @@
1
+ module WelltreatStoreFramework
2
+ class Configuration
3
+ attr_accessor :store_path, :haml_options, :database_config,
4
+ :database_schema_file, :auto_reload, :sprockets_enabled
5
+
6
+ def initialize
7
+ @auto_reload = false
8
+ @store_path = nil
9
+ @sprockets_enabled = false
10
+ @haml_options = {
11
+ encoding: 'utf-8',
12
+ format: :html5
13
+ }
14
+ @database_connection = nil
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,131 @@
1
+ module WelltreatStoreFramework
2
+ module Controller
3
+ extend ActiveSupport::Concern
4
+
5
+ attr_reader :app
6
+
7
+ def initialize(app)
8
+ @app = app
9
+ end
10
+
11
+ def serve(request)
12
+ end
13
+
14
+ [:notice, :alert, :success].map(&:to_s).each do |_kind|
15
+ module_eval <<-CODE
16
+ def set_flash_#{_kind}(session, msg)
17
+ _flash_from(session)[:#{_kind}] = msg
18
+ end
19
+
20
+ def get_flash_#{_kind}(session)
21
+ _flash_from(session)[:#{_kind}]
22
+ end
23
+ CODE
24
+ end
25
+
26
+ def _flash_from(session)
27
+ session[:flash] ||= { }
28
+ end
29
+
30
+ class Request
31
+ attr_accessor :path, :params, :headers, :rack_request, :env
32
+
33
+ def initialize(attrs = { })
34
+ @headers = { }
35
+ @params = { }
36
+ @path = nil
37
+ @rack_request = nil
38
+ attrs.each do |k, v|
39
+ self.send(:"#{k.to_s}=", v)
40
+ end
41
+ end
42
+
43
+ def get_header(k)
44
+ self.headers[k]
45
+ end
46
+
47
+ def [](k)
48
+ self.params[k]
49
+ end
50
+
51
+ class << self
52
+ def initialize_from_env(env)
53
+ request = Rack::Request.new(env)
54
+ self.new(
55
+ :params => request.params,
56
+ :headers => env,
57
+ :rack_request => request,
58
+ :path => request.path,
59
+ :env => env
60
+ )
61
+ end
62
+ end
63
+
64
+ private
65
+
66
+ def set_param(k, v)
67
+ self.params[k] = v
68
+ end
69
+ end
70
+
71
+ class Response
72
+
73
+ STATUS_OK = 200
74
+ attr_accessor :status, :headers, :content, :values,
75
+ :controller_name, :template, :layout
76
+
77
+ def initialize
78
+ @status = STATUS_OK
79
+ @headers = { 'Content-Type' => 'text/html' }
80
+ @content = nil
81
+ @template = nil
82
+ @layout = :default
83
+ @values = { }
84
+ end
85
+
86
+ def add_header(k, v)
87
+ case k.to_s
88
+ when 'content_type', 'format'
89
+ @headers['Content-Type'] = v
90
+ else
91
+ @headers[k] = v
92
+ end
93
+ end
94
+
95
+ def get_header(k)
96
+ @headers[k]
97
+ end
98
+
99
+ def set(k, v)
100
+ @values[k] = v
101
+ end
102
+
103
+ def get(k)
104
+ @values[k]
105
+ end
106
+
107
+ def [](k)
108
+ self.get(k)
109
+ end
110
+
111
+ def exists?(k)
112
+ @values.include?(k)
113
+ end
114
+
115
+ def present?(k)
116
+ exists?(k) && get(k).present?
117
+ end
118
+
119
+ def redirect_to(path, status = 302)
120
+ self.status = status
121
+ self.add_header("Location", path)
122
+ self.content = ''
123
+ end
124
+
125
+ def redirect?
126
+ [301, 302, 303, 307].include? self.status
127
+ end
128
+
129
+ end
130
+ end
131
+ end
@@ -0,0 +1,66 @@
1
+ module WelltreatStoreFramework
2
+ class Core
3
+ @@configuration = WelltreatStoreFramework::Configuration.new
4
+ cattr_accessor :configuration
5
+
6
+ @@stores = nil
7
+ cattr_accessor :stores
8
+
9
+ class << self
10
+
11
+ # Reset internal cached data
12
+ def reset!
13
+ self.stores = nil
14
+ self.configuration = WelltreatStoreFramework::Configuration.new
15
+ end
16
+
17
+ # Configure through passing block. block will be yield with
18
+ # configuration instance
19
+ def setup(&block)
20
+ block.call(self.configuration)
21
+ end
22
+
23
+ # Find all stores from the configured path
24
+ # return an array of instance StoreApp
25
+ def find_stores(partition_object = nil)
26
+ self.stores ||= _detect_stores(partition_object)
27
+ end
28
+
29
+ # Find store by the given name
30
+ # Return StoreApp instance
31
+ def find_store_by_name(name, partition_object = nil)
32
+ find_stores(partition_object)[name]
33
+ end
34
+
35
+ # Establish database connection
36
+ def connect_database!
37
+ ActiveRecord::Base.establish_connection configuration.database_config
38
+
39
+ if configuration.database_schema_file.present?
40
+ puts "Importing database schema - "
41
+ require configuration.database_schema_file
42
+ end
43
+ end
44
+
45
+ private
46
+ def _detect_stores(partition_object = nil)
47
+ _stores = { }
48
+ Dir.glob(File.join(configuration.store_path, '*')).each do |_path|
49
+ _name = _path.split('/').last
50
+ _stores[_name] = StoreApp.new.tap do |inst|
51
+ inst.partition_object = partition_object
52
+ inst.path = _path
53
+ inst.name = _name
54
+ inst.config_path = File.join(_path, 'config')
55
+ inst.controllers_path = File.join(_path, 'controllers')
56
+ inst.models_path = File.join(_path, 'models')
57
+ inst.views_path = File.join(_path, 'views')
58
+ inst.assets_path = File.join(_path, 'assets')
59
+ end
60
+ end
61
+
62
+ _stores
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,29 @@
1
+ module WelltreatStoreFramework
2
+ module HamlRenderer
3
+ module LoremHelper
4
+ extend ActiveSupport::Concern
5
+
6
+ def lorem
7
+ Lorem
8
+ end
9
+
10
+ class Lorem
11
+ TEXTS = %{Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Nam liber tempor cum soluta nobis eleifend option congue nihil imperdiet doming id quod mazim placerat facer possim assum. Typi non habent claritatem insitam; est usus legentis in iis qui facit eorum claritatem. Investigationes demonstraverunt lectores legere me lius quod ii legunt saepius. Claritas est etiam processus dynamicus, qui sequitur mutationem consuetudium lectorum. Mirum est notare quam littera gothica, quam nunc putamus parum claram, anteposuerit litterarum formas humanitatis per seacula quarta decima et quinta decima. Eodem modo typi, qui nunc nobis videntur parum clari, fiant sollemnes in futurum.}
12
+
13
+ class << self
14
+ def name
15
+ TEXTS.split(',').shuffle.first[0..20]
16
+ end
17
+
18
+ def sentence
19
+ TEXTS.split(',').shuffle.first
20
+ end
21
+
22
+ def paragraph
23
+ TEXTS
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ module WelltreatStoreFramework
2
+ module HamlRenderer
3
+ module Partial
4
+ extend ActiveSupport::Concern
5
+
6
+ # Render partial template
7
+ # template - Template file name
8
+ # By default look for relative path to current action path
9
+ # Otherwise check by relative path to views path
10
+ #
11
+ # variables - Define local variables
12
+ #
13
+ # Return string content
14
+ def partial(template, variables = { })
15
+ _template_key = [
16
+ self.response.controller_name,
17
+ self.response.layout.to_s,
18
+ self.response.template.to_s,
19
+ template.to_s
20
+ ].join('_')
21
+ _file = self.app._full_partial_template_path(self.response, template)
22
+
23
+ # Set local variables
24
+ variables.each { |k, v| self.set_local(k, v) } if variables.present?
25
+
26
+ # Render partial
27
+ self.app._singleton_haml_instance(_template_key, _file).render(self)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,51 @@
1
+ module WelltreatStoreFramework
2
+ module HamlRenderer
3
+ module Paths
4
+ extend ActiveSupport::Concern
5
+
6
+ def asset_path(*_paths)
7
+ UrlJoin.new('/assets').join(_paths)
8
+ end
9
+
10
+ def image_path(_path)
11
+ if _path.to_s.match(/^\//)
12
+ _path
13
+ elsif sprockets_enabled?
14
+ asset_path _path
15
+ else
16
+ asset_path 'images', _path
17
+ end
18
+ end
19
+
20
+ def stylesheet_path(_path)
21
+ _path = _path.to_s
22
+ _path << '.css' unless _path.to_s.match(/\.css$/)
23
+
24
+ if _path.to_s.match(/^\//)
25
+ _path
26
+ elsif sprockets_enabled?
27
+ asset_path _path
28
+ else
29
+ asset_path 'stylesheets', _path
30
+ end
31
+ end
32
+
33
+ def javascript_path(_path)
34
+ _path = _path.to_s
35
+ _path << '.js' unless _path.to_s.match(/\.js$/)
36
+
37
+ if _path.to_s.match(/^\//)
38
+ _path
39
+ elsif sprockets_enabled?
40
+ asset_path _path
41
+ else
42
+ asset_path 'javascripts', _path
43
+ end
44
+ end
45
+
46
+ def sprockets_enabled?
47
+ WelltreatStoreFramework::Core.configuration.sprockets_enabled
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,74 @@
1
+ module WelltreatStoreFramework
2
+ module HamlRenderer
3
+ module TagsHelper
4
+ extend ActiveSupport::Concern
5
+
6
+ # Render image tag
7
+ def image_tag(_path, attrs = { })
8
+ attrs[:src] = self.image_path(_path.to_s)
9
+ content_tag 'img', nil, attrs
10
+ end
11
+
12
+ # Render link tag
13
+ def link_to(label, link, attrs = { })
14
+ attrs[:href] = link
15
+ content_tag 'a', label, attrs
16
+ end
17
+
18
+ # Render stylesheet tags
19
+ def stylesheet_link_tag(*_files)
20
+ _files.map { |_f| stylesheet_path(_f) }.map do |_css_path|
21
+ content_tag('link', nil, {
22
+ rel: 'stylesheet',
23
+ type: 'text/css',
24
+ href: _css_path
25
+ })
26
+ end.join('')
27
+ end
28
+
29
+ # Render stylesheet tags
30
+ def javascript_include_tag(*_files)
31
+ _files.map { |_f| javascript_path(_f) }.map do |_js_path|
32
+ content_tag('script', '', {
33
+ type: 'text/javascript',
34
+ src: _js_path
35
+ })
36
+ end.join('')
37
+ end
38
+
39
+ # Truncate text
40
+ def truncate(text, options = {})
41
+ length = options[:length] || 100
42
+ text[0..length - 1]
43
+ end
44
+
45
+ def strip_tags(text, options = {})
46
+ text.gsub(/<\/?[\w\d\s\-_'="]+>/, '')
47
+ end
48
+
49
+ def content_tag(tag_name, content, attrs = { })
50
+ html = "<#{tag_name} #{_generate_attributes(attrs)}"
51
+
52
+ if content
53
+ html << ">"
54
+ html << ERB::Util.html_escape(content)
55
+ html << "</#{tag_name}>"
56
+
57
+ elsif block_given?
58
+ html << ">"
59
+ html << ERB::Util.html_escape(yield)
60
+ html << "</#{tag_name}>"
61
+ else
62
+ html << ' />'
63
+ end
64
+
65
+ html
66
+ end
67
+
68
+ private
69
+ def _generate_attributes(_attrs)
70
+ _attrs.map { |k, v| "#{k}='#{v}'" }.join(' ')
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,163 @@
1
+ require 'welltreat_store_framework/haml_renderer/paths'
2
+ require 'welltreat_store_framework/haml_renderer/partial'
3
+ require 'welltreat_store_framework/haml_renderer/tags_helper'
4
+ require 'welltreat_store_framework/haml_renderer/lorem_helper'
5
+
6
+ module WelltreatStoreFramework
7
+ module HamlRenderer
8
+ extend ActiveSupport::Concern
9
+
10
+ # Render HAML template and set content in response.content variable
11
+ def render!(request, response, options)
12
+ return if response.redirect?
13
+ return if response.content.present?
14
+
15
+ _layout_key = response.layout.to_s
16
+ _template_key = [
17
+ response.controller_name,
18
+ response.layout.to_s,
19
+ response.template.to_s,
20
+ ].join('_')
21
+ _template_file = _full_template_path(response)
22
+ _layout_file = _full_layout_path(response)
23
+
24
+ raise StoreApp::TemplateNotFound.new(_template_key) if _template_file.nil?
25
+
26
+ # Render view with layout
27
+ context = Context.new(self, request, response, options)
28
+
29
+ # Render sub view
30
+ context.set :body, _singleton_haml_instance(_template_key, _template_file).render(context)
31
+
32
+ # Render layout
33
+ response.content = _singleton_haml_instance(_layout_key, _layout_file).render(context)
34
+ end
35
+
36
+ def _singleton_haml_instance(_key, _file)
37
+ raise StoreApp::TemplateNotFound.new(_file) if _file.nil? || !File.exist?(_file)
38
+
39
+ if WelltreatStoreFramework::Core.configuration.auto_reload
40
+ Haml::Engine.new(File.read(_file), filename: _file)
41
+ else
42
+ _haml_engines[_key] ||=
43
+ Haml::Engine.new(File.read(_file), filename: _file)
44
+ end
45
+ end
46
+
47
+ def _haml_engines
48
+ @_haml_engines ||= { }
49
+ end
50
+
51
+ def _full_layout_path(response)
52
+ Dir.glob(File.join(self.views_path,
53
+ 'layouts',
54
+ "#{response.layout.to_s}.*"
55
+ )).first
56
+ end
57
+
58
+ def _full_template_path(response)
59
+ Dir.glob(File.join(self.views_path,
60
+ response.controller_name.to_s.underscore,
61
+ "#{response.template.to_s}.*"
62
+ )).first
63
+ end
64
+
65
+ def _full_partial_template_path(response, _partial)
66
+ Dir.glob(File.join(self.views_path,
67
+ response.controller_name.to_s.underscore,
68
+ "#{_partial.to_s}.*"
69
+ )).first ||
70
+ Dir.glob(File.join(self.views_path,
71
+ "#{_partial.to_s}.*"
72
+ )).first
73
+ end
74
+
75
+ class Context
76
+ include Paths, Partial, TagsHelper, LoremHelper
77
+
78
+ attr_accessor :request, :response, :options
79
+
80
+ def initialize(app, request, response, options)
81
+ @app = app
82
+ @request = request
83
+ @response = response
84
+ @options = options || { }
85
+ @locals = { }
86
+ end
87
+
88
+ def app;
89
+ @app
90
+ end
91
+
92
+ def set_local(k, v)
93
+ @locals[k] = v
94
+ end
95
+
96
+ def get_local(k)
97
+ @locals[k]
98
+ end
99
+
100
+ def local_exists?(k)
101
+ @locals.include?(k)
102
+ end
103
+
104
+ def local_present?(k)
105
+ local_exists?(k) && get_local(k).present?
106
+ end
107
+
108
+ def session
109
+ options[:session]
110
+ end
111
+
112
+ def notice
113
+ get_flash :notice
114
+ end
115
+
116
+ def alert
117
+ get_flash :alert
118
+ end
119
+
120
+ def success
121
+ get_flash :success
122
+ end
123
+
124
+ def flash_exist?
125
+ notice || alert || success
126
+ end
127
+
128
+ def get_flash(k)
129
+ @flash_values ||= { }
130
+ @flash_values[k] ||= flash[k]
131
+ flash[k] = nil if @flash_values[k].present?
132
+
133
+ @flash_values[k]
134
+ end
135
+
136
+ def set_flash(k, v)
137
+ flash[k] = v
138
+ end
139
+
140
+ def flash
141
+ options[:session][:flash] ||= { }
142
+ end
143
+
144
+ delegate :exists?, :present?, :set, :get, :to => :response
145
+ end
146
+
147
+ class UrlJoin
148
+ def initialize(*_base_paths)
149
+ @paths = []
150
+ join(_base_paths)
151
+ end
152
+
153
+ def join(*_paths)
154
+ @paths += _paths
155
+ self
156
+ end
157
+
158
+ def to_s
159
+ @paths.join('/')
160
+ end
161
+ end
162
+ end
163
+ end