flame 5.0.0.rc4 → 5.0.0.rc7

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.
Files changed (64) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +929 -0
  3. data/LICENSE.txt +19 -0
  4. data/README.md +135 -0
  5. data/lib/flame/application.rb +47 -46
  6. data/lib/flame/config.rb +73 -0
  7. data/lib/flame/controller/actions.rb +122 -0
  8. data/lib/flame/{dispatcher → controller}/cookies.rb +8 -3
  9. data/lib/flame/controller/path_to.rb +34 -10
  10. data/lib/flame/controller.rb +45 -78
  11. data/lib/flame/dispatcher/request.rb +22 -6
  12. data/lib/flame/dispatcher/routes.rb +22 -14
  13. data/lib/flame/dispatcher/static.rb +13 -9
  14. data/lib/flame/dispatcher.rb +15 -18
  15. data/lib/flame/errors/argument_not_assigned_error.rb +3 -8
  16. data/lib/flame/errors/config_file_not_found_error.rb +17 -0
  17. data/lib/flame/errors/controller_not_found_error.rb +19 -0
  18. data/lib/flame/errors/route_arguments_order_error.rb +3 -10
  19. data/lib/flame/errors/route_extra_arguments_error.rb +7 -20
  20. data/lib/flame/errors/route_not_found_error.rb +4 -9
  21. data/lib/flame/errors/template_not_found_error.rb +2 -8
  22. data/lib/flame/path.rb +36 -18
  23. data/lib/flame/render.rb +13 -5
  24. data/lib/flame/router/controller_finder.rb +56 -0
  25. data/lib/flame/router/route.rb +9 -0
  26. data/lib/flame/router/routes.rb +56 -9
  27. data/lib/flame/router/routes_refine/mounting.rb +57 -0
  28. data/lib/flame/router/routes_refine.rb +144 -0
  29. data/lib/flame/router.rb +7 -157
  30. data/lib/flame/validators.rb +14 -10
  31. data/lib/flame/version.rb +1 -1
  32. data/lib/flame.rb +12 -5
  33. metadata +107 -96
  34. data/bin/flame +0 -16
  35. data/lib/flame/application/config.rb +0 -49
  36. data/template/.editorconfig +0 -15
  37. data/template/.gitignore +0 -28
  38. data/template/.rubocop.yml +0 -14
  39. data/template/Gemfile +0 -55
  40. data/template/Rakefile +0 -824
  41. data/template/application.rb.erb +0 -10
  42. data/template/config/config.rb.erb +0 -56
  43. data/template/config/database.example.yml +0 -5
  44. data/template/config/deploy.example.yml +0 -2
  45. data/template/config/puma.rb +0 -56
  46. data/template/config/sequel.rb.erb +0 -22
  47. data/template/config/server.example.yml +0 -32
  48. data/template/config/session.example.yml +0 -7
  49. data/template/config.ru.erb +0 -72
  50. data/template/controllers/_controller.rb.erb +0 -14
  51. data/template/controllers/site/_controller.rb.erb +0 -18
  52. data/template/controllers/site/index_controller.rb.erb +0 -12
  53. data/template/db/.keep +0 -0
  54. data/template/filewatchers.yml +0 -12
  55. data/template/helpers/.keep +0 -0
  56. data/template/lib/.keep +0 -0
  57. data/template/locales/en.yml +0 -0
  58. data/template/models/.keep +0 -0
  59. data/template/public/.keep +0 -0
  60. data/template/server +0 -200
  61. data/template/services/.keep +0 -0
  62. data/template/views/.keep +0 -0
  63. data/template/views/site/index.html.erb.erb +0 -1
  64. data/template/views/site/layout.html.erb.erb +0 -10
data/LICENSE.txt ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2015-2017 Alexander Popov (AlexWayfer)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"),
5
+ to deal in the Software without restriction, including without limitation
6
+ the rights to use, copy, modify, merge, publish, distribute, sublicense,
7
+ and/or sell copies of the Software, and to permit persons to whom
8
+ the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included
11
+ in all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
14
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
15
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
17
+ DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
18
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19
+ DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,135 @@
1
+ <p align="center">
2
+ <img
3
+ src="https://raw.githubusercontent.com/AlexWayfer/flame/main/public/favicon.ico"
4
+ height="150"
5
+ alt="Flame Logo"
6
+ title="Logo from open-source Elusive-Iconfont (https://github.com/reduxframework/elusive-iconfont)"
7
+ />
8
+ </p>
9
+
10
+ <h1 align="center">Flame</h1>
11
+
12
+ <p align="center">
13
+ <a href="https://cirrus-ci.com/github/AlexWayfer/flame/main"><img
14
+ src="https://api.cirrus-ci.com/github/AlexWayfer/flame.svg?branch=main"
15
+ alt="Cirrus CI"
16
+ /></a>
17
+ <a href="https://codecov.io/gh/AlexWayfer/flame"><img
18
+ src="https://img.shields.io/codecov/c/github/AlexWayfer/flame.svg?style=flat-square"
19
+ alt="Codecov"
20
+ /></a>
21
+ <a href="https://codeclimate.com/github/AlexWayfer/flame"><img
22
+ src="https://img.shields.io/codeclimate/maintainability/AlexWayfer/flame.svg?style=flat-square"
23
+ alt="Code Climate"
24
+ /></a>
25
+ <a href="https://depfu.com/repos/AlexWayfer/flame"><img
26
+ src="https://img.shields.io/depfu/AlexWayfer/flame.svg?style=flat-square"
27
+ alt="Depfu"
28
+ /></a>
29
+ <a href="http://inch-ci.org/github/AlexWayfer/flame"><img
30
+ src="http://inch-ci.org/github/AlexWayfer/flame.svg?branch=main&style=flat-square"
31
+ alt="Docs"
32
+ /></a>
33
+ <a href="https://rubygems.org/gems/flame"><img
34
+ src="https://img.shields.io/gem/v/flame.svg?include_prereleases&style=flat-square"
35
+ alt="Gem (including prereleases)"
36
+ /></a>
37
+ <a href="https://github.com/AlexWayfer/flame/blob/main/LICENSE.txt"><img
38
+ src="https://img.shields.io/github/license/AlexWayfer/flame.svg?style=flat-square"
39
+ alt="MIT license"
40
+ /></a>
41
+ </p>
42
+
43
+ Flame is a small Ruby web framework, built on [Rack](https://github.com/rack/rack),
44
+ inspired by [Gin](https://github.com/0jcasts/gin) (which follows class-controllers style),
45
+ designed as a replacement [Sinatra](https://github.com/sinatra/sinatra)
46
+ or maybe even [Rails](https://github.com/rails/rails).
47
+
48
+ ## Why?
49
+
50
+ I didn't like class methods, especially for controller's hooks — OOP is prettier without it.
51
+ And I found a way to implement controller's hooks without using class methods,
52
+ but with the inheritance (including the including of modules).
53
+ Moreover, with class methods an insufficiently obvious order of hooks (especially with inheritance)
54
+ and complicated implementation of conditions are obtained.
55
+ In this framework everything is Ruby-native as it can be.
56
+
57
+ ## Installation
58
+
59
+ Using the built-in `gem`:
60
+
61
+ ```bash
62
+ $ gem install flame
63
+ ```
64
+
65
+ or with [Bundler](http://bundler.io/):
66
+
67
+ ```ruby
68
+ # Gemfile
69
+ gem 'flame'
70
+ ```
71
+
72
+ ## Usage
73
+
74
+ The simplest example:
75
+
76
+ ```ruby
77
+ # index_controller.rb
78
+
79
+ class IndexController < Flame::Controller
80
+ def index
81
+ view :index # or just `view`, Symbol as method-name by default
82
+ end
83
+
84
+ def hello_world
85
+ "Hello World!"
86
+ end
87
+
88
+ def goodbye
89
+ "Goodbye World!"
90
+ end
91
+ end
92
+
93
+ # app.rb
94
+
95
+ class App < Flame::Application
96
+ mount IndexController do
97
+ # all methods will be mounted automatically, it's just an example of refinement
98
+ get '/hello', :hello_world
99
+ end
100
+ end
101
+
102
+ # config.ru
103
+
104
+ require_relative './index_controller'
105
+
106
+ require_relative './app'
107
+
108
+ run App.new # or `run App`
109
+ ```
110
+
111
+ More at [Wiki](https://github.com/AlexWayfer/flame/wiki).
112
+
113
+ ## Benchmark
114
+
115
+ The last benchmark can be viewed [here](https://github.com/luislavena/bench-micro).
116
+
117
+ ## Development
118
+
119
+ After checking out the repo, run `bundle install` to install dependencies.
120
+
121
+ Then, run `toys rspec` to run the tests.
122
+
123
+ To install this gem onto your local machine, run `toys gem install`.
124
+
125
+ To release a new version, run `toys gem release %version%`.
126
+ See how it works [here](https://github.com/AlexWayfer/gem_toys#release).
127
+
128
+ ## Contributing
129
+
130
+ Bug reports and pull requests are welcome on [GitHub](https://github.com/AlexWayfer/flame).
131
+
132
+ ## License
133
+
134
+ The gem is available as open source under the terms of the
135
+ [MIT License](https://opensource.org/licenses/MIT).
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'application/config'
3
+ require 'addressable'
4
+
4
5
  require_relative 'router'
5
6
  require_relative 'dispatcher'
6
7
 
@@ -8,15 +9,25 @@ module Flame
8
9
  ## Core class, like Framework::Application
9
10
  class Application
10
11
  class << self
11
- attr_accessor :config
12
+ include Memery
13
+
14
+ ## Remember root directory when inherited
15
+ def inherited(app)
16
+ super
17
+ app.root_dir = File.dirname caller(2..2).first.split(':')[0]
18
+ end
19
+
20
+ memoize def config
21
+ Flame::Config.new root_dir
22
+ end
12
23
 
13
24
  ## Router for routing
14
- def router
15
- @router ||= Flame::Router.new(self)
25
+ memoize def router
26
+ Flame::Router.new(self)
16
27
  end
17
28
 
18
- def cached_tilts
19
- @cached_tilts ||= {}
29
+ memoize def cached_tilts
30
+ {}
20
31
  end
21
32
 
22
33
  ## Require project directories, exclude executable files
@@ -25,34 +36,20 @@ module Flame
25
36
  ## Flame::Application.require_dirs(
26
37
  ## %w[config lib models helpers mailers services controllers]
27
38
  ## )
28
- def require_dirs(dirs)
29
- caller_dir = File.dirname caller_file
39
+ def require_dirs(dirs, ignore: [])
30
40
  dirs.each do |dir|
31
- Dir[File.join(caller_dir, dir, '**/*.rb')]
32
- .reject { |file| File.executable?(file) }
33
- .sort_by { |s| [File.basename(s)[0], s] }
34
- .each { |file| require File.expand_path(file) }
41
+ require_dir File.join(root_dir, dir), ignore: ignore
35
42
  end
36
43
  end
37
44
 
38
- ## Generating application config when inherited
39
- def inherited(app)
40
- app.config = Config.new(
41
- app,
42
- default_config_dirs(
43
- root_dir: File.dirname(caller_file)
44
- ).merge(
45
- environment: ENV['RACK_ENV'] || 'development'
46
- )
47
- )
48
- end
49
-
50
45
  ## Make available `run Application` without `.new` for `rackup`
51
46
  def call(env)
52
47
  @app ||= new
53
48
  @app.call env
54
49
  end
55
50
 
51
+ using GorillaPatch::DeepDup
52
+
56
53
  ## Build a path to the given controller and action
57
54
  ##
58
55
  ## @param ctrl [Flame::Controller] class of controller
@@ -61,32 +58,45 @@ module Flame
61
58
  ## @return [String] path for requested method, controller and parameters
62
59
  ## @example Path for `show(id)` method of `ArticlesController`
63
60
  ## path_to ArticlesController, :show, id: 2 # => "/articles/show/2"
64
- ## @example Path for `new` method of `ArticlesController` with params
65
- ## path_to ArticlesController, :new, params: { author_id: 1 }
61
+ ## @example Path for `new` method of `ArticlesController` with query
62
+ ## path_to ArticlesController, :new, author_id: 1
66
63
  ## # => "/articles/new?author_id=1"
67
64
  def path_to(ctrl, action = :index, args = {})
68
65
  path = router.path_of(ctrl, action)
66
+
69
67
  raise Errors::RouteNotFoundError.new(ctrl, action) unless path
70
- query = Rack::Utils.build_nested_query args.delete(:params)
71
- query = nil if query&.empty?
68
+
69
+ args = args.deep_dup
72
70
  path = path.assign_arguments(args)
73
71
  path = '/' if path.empty?
72
+ query = Rack::Utils.build_nested_query args unless args.empty?
74
73
  Addressable::URI.new(path: path, query: query).to_s
75
74
  end
76
75
 
76
+ protected
77
+
78
+ attr_accessor :root_dir
79
+
77
80
  private
78
81
 
79
- ## Get filename from caller of method
80
- ## @return [String] filename of caller
81
- def caller_file
82
- caller(2..2).first.split(':')[0]
82
+ def require_dir(dir, ignore: [])
83
+ files =
84
+ Dir[File.join(dir, '**/*.rb')]
85
+ .reject do |file|
86
+ File.executable?(file) ||
87
+ ignore.any? { |regexp| regexp.match?(file) }
88
+ end
89
+ files.sort_by! do |file|
90
+ [File.basename(file).start_with?('_') ? 1 : 2, file]
91
+ end
92
+ files.each { |file| require File.expand_path(file) }
83
93
  end
84
94
 
85
95
  ## Mount controller in application class
86
96
  ## @param controller [Symbol] the snake-cased name of mounted controller
87
97
  ## (without `Controller` or `::IndexController` for namespaces)
88
98
  ## @param path [String, nil] root path for the mounted controller
89
- ## @yield refine defaults pathes for a methods of the mounted controller
99
+ ## @yield refine defaults paths for a methods of the mounted controller
90
100
  ## @example Mount controller with defaults
91
101
  ## mount :articles # ArticlesController
92
102
  ## @example Mount controller with specific path
@@ -101,38 +111,29 @@ module Flame
101
111
  ## mount :cabinet do # Cabinet::IndexController
102
112
  ## mount :articles # Cabinet::ArticlesController
103
113
  ## end
104
- def mount(controller_name, path = nil, &block)
114
+ def mount(controller, path = nil, nested: true, &block)
105
115
  ## Add routes from controller to glob array
106
116
  router.add Router::RoutesRefine.new(
107
- router, namespace, controller_name, path, &block
117
+ namespace_name, controller, path, nested: nested, &block
108
118
  )
109
119
  end
110
120
 
111
121
  using GorillaPatch::Namespace
112
122
 
113
- def namespace
123
+ def namespace_name
114
124
  namespace = self
115
125
  while namespace.name.nil? && namespace.superclass != Flame::Application
116
126
  namespace = superclass
117
127
  end
118
128
  namespace.deconstantize
119
129
  end
120
-
121
- ## Initialize default for config directories
122
- def default_config_dirs(root_dir:)
123
- result = { root_dir: File.realpath(root_dir) }
124
- %i[public views config tmp].each do |key|
125
- result[:"#{key}_dir"] = proc { File.join(config[:root_dir], key.to_s) }
126
- end
127
- result
128
- end
129
130
  end
130
131
 
131
132
  def initialize(app = nil)
132
133
  @app = app
133
134
  end
134
135
 
135
- ## Request recieving method
136
+ ## Request receiving method
136
137
  def call(env)
137
138
  @app.call(env) if @app.respond_to? :call
138
139
  Flame::Dispatcher.new(self.class, env).run!
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'errors/config_file_not_found_error'
4
+
5
+ module Flame
6
+ ## Class for application configuration
7
+ class Config < Hash
8
+ DEFAULT_DIRS =
9
+ %i[config log public tmp views].each_with_object({}) do |key, result|
10
+ result[:"#{key}_dir"] = proc { File.join(self[:root_dir], key.to_s) }
11
+ end.freeze
12
+
13
+ ## Create an instance of application config
14
+ ## @param app [Flame::Application] application
15
+ ## @param hash [Hash] config content
16
+ def initialize(root_dir)
17
+ super()
18
+ replace DEFAULT_DIRS.merge(
19
+ root_dir: File.realpath(root_dir),
20
+ environment: ENV.fetch('RACK_ENV', 'development')
21
+ )
22
+ end
23
+
24
+ ## Get config value by key
25
+ ## @param key [Symbol] config key
26
+ ## @return [Object] config value
27
+ def [](key)
28
+ result = super(key)
29
+ result = instance_exec(&result) if result.class <= Proc && result.parameters.empty?
30
+ result
31
+ end
32
+
33
+ ## Method for loading YAML-files from config directory
34
+ ## @param file [String, Symbol]
35
+ ## file name (typecast to String with '.yaml')
36
+ ## @param key [Symbol, String, nil]
37
+ ## key for allocating YAML in config Hash (typecast to Symbol)
38
+ ## @param set [Boolean] allocating YAML in Config Hash
39
+ ## @param require [Boolean] don't raise an error if file not found and not required
40
+ ## @example Load SMTP file from `config/smtp.yaml' to config[]
41
+ ## config.load_yaml('smtp.yaml')
42
+ ## @example Load SMTP file without extension, by Symbol
43
+ ## config.load_yaml(:smtp)
44
+ ## @example Load SMTP file with other key to config[:mail]
45
+ ## config.load_yaml('smtp.yaml', key: :mail)
46
+ ## @example Load SMTP file without allocating in config[]
47
+ ## config.load_yaml('smtp.yaml', set: false)
48
+ ## @example Try to load nonexistent SMTP file without raising an error
49
+ ## config.load_yaml('smtp.yaml', require: false)
50
+ def load_yaml(file, key: nil, set: true, required: true)
51
+ file = "#{file}.y{a,}ml" if file.is_a? Symbol
52
+
53
+ file_path = find_config_file file, required: required
54
+ return unless file_path
55
+
56
+ yaml = YAML.load_file(file_path, aliases: true)
57
+ key ||= File.basename(file, '.*')
58
+ self[key.to_sym] = yaml if set
59
+ yaml
60
+ end
61
+
62
+ private
63
+
64
+ def find_config_file(filename, required:)
65
+ file_path = Dir[File.join(self[:config_dir], filename)].first
66
+ return file_path if file_path || !required
67
+
68
+ raise Errors::ConfigFileNotFoundError.new(
69
+ filename, self[:config_dir].sub(self[:root_dir], '')
70
+ )
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,122 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'gorilla_patch/slice'
4
+
5
+ module Flame
6
+ class Controller
7
+ ## Module for work with actions
8
+ module Actions
9
+ include Memery
10
+ using GorillaPatch::Slice
11
+
12
+ ## Shortcut for not-inherited public methods: actions
13
+ ## @return [Array<Symbol>] array of actions (public instance methods)
14
+ def actions
15
+ public_instance_methods(false)
16
+ end
17
+
18
+ ## Re-define public instance methods (actions) from parent
19
+ ## @param actions [Array<Symbol>] Actions for inheritance
20
+ ## @param exclude [Array<Symbol>] Actions for excluding from inheritance
21
+ ## @param from [Module]
22
+ ## Module (or Class) from which actions will be inherited
23
+ ## @example Inherit all parent actions
24
+ ## class MyController < BaseController
25
+ ## inherit_actions
26
+ ## end
27
+ ## @example Inherit certain parent actions
28
+ ## class MyController < BaseController
29
+ ## inherit_actions %i[index show]
30
+ ## end
31
+ ## @example Inherit all parent actions exclude certain
32
+ ## class MyController < BaseController
33
+ ## inherit_actions exclude: %i[edit update]
34
+ ## end
35
+ ## @example Inherit certain actions from specific module
36
+ ## class MyController < BaseController
37
+ ## inherit_actions %i[index show], from: ModuleWithActions
38
+ ## end
39
+ def inherit_actions(actions = nil, exclude: [], from: superclass)
40
+ actions = from.actions if actions.nil?
41
+ actions -= exclude
42
+
43
+ actions.each do |action|
44
+ define_method action, from.public_instance_method(action)
45
+ end
46
+
47
+ return unless from.respond_to?(:refined_http_methods)
48
+
49
+ refined_http_methods.merge!(
50
+ from.refined_http_methods.slice(*actions)
51
+ )
52
+ end
53
+
54
+ ## Re-define public instance method from module
55
+ ## @param mod [Module] Module for including to controller
56
+ ## @param exclude [Array<Symbol>] Actions for excluding
57
+ ## from module public instance methods
58
+ ## @param only [Array<Symbol>] Actions for re-defining
59
+ ## from module public instance methods
60
+ ## @example Define actions from module in controller
61
+ ## class MyController < BaseController
62
+ ## include with_actions Module1
63
+ ## include with_actions Module2
64
+ ## ....
65
+ ## end
66
+ ## @example Define actions from module exclude some actions in controller
67
+ ## class MyController < BaseController
68
+ ## include with_actions Module1, exclude: %i[action1 action2 ...]
69
+ ## include with_actions Module2, exclude: %i[action1 action2 ...]
70
+ ## ....
71
+ ## end
72
+ ## @example Define actions from module according list in controller
73
+ ## class MyController < BaseController
74
+ ## include with_actions Module1, only: %i[action1 action2 ...]
75
+ ## include with_actions Module2, only: %i[action1 action2 ...]
76
+ ## ....
77
+ ## end
78
+ def with_actions(mod, exclude: [], only: nil)
79
+ Module.new do
80
+ @mod = mod
81
+ @actions = only || (@mod.public_instance_methods(false) - exclude)
82
+
83
+ extend ModuleWithActions
84
+ end
85
+ end
86
+
87
+ memoize def refined_http_methods
88
+ {}
89
+ end
90
+
91
+ private
92
+
93
+ Flame::Router::HTTP_METHODS.each do |http_method|
94
+ downcased_http_method = http_method.downcase
95
+ define_method(
96
+ downcased_http_method
97
+ ) do |action_or_action_path, action = nil|
98
+ action, action_path =
99
+ if action
100
+ [action, action_or_action_path]
101
+ else
102
+ [action_or_action_path, nil]
103
+ end
104
+ refined_http_methods[action] = [downcased_http_method, action_path]
105
+ end
106
+ end
107
+
108
+ ## Base module for module `with_actions`
109
+ module ModuleWithActions
110
+ using GorillaPatch::Slice
111
+
112
+ def included(ctrl)
113
+ ctrl.include @mod
114
+
115
+ ctrl.inherit_actions @actions, from: @mod
116
+ end
117
+ end
118
+
119
+ private_constant :ModuleWithActions
120
+ end
121
+ end
122
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Flame
4
- class Dispatcher
4
+ class Controller
5
5
  ## Helper class for cookies
6
6
  class Cookies
7
7
  ## Create an instance of Cookies
@@ -22,14 +22,19 @@ module Flame
22
22
 
23
23
  ## Set (or delete) cookies for response
24
24
  ## @param key [String, Symbol] name of cookie
25
- ## @param new_value [Object, nil] value of cookie
25
+ ## @param new_value [Object, Hash, nil] value of cookie or Hash with `:value` and options
26
26
  ## @example Set new value to `cat` cookie
27
27
  ## cookies['cat'] = 'nice cat'
28
+ ## @example Set new value to `cat` cookie with `Max-Age` 60 seconds
29
+ ## cookies['cat'] = { value: 'nice cat', max_age: 60 }
28
30
  ## @example Delete `cat` cookie
29
31
  ## cookies['cat'] = nil
30
32
  def []=(key, new_value)
31
- if new_value.nil?
33
+ case new_value
34
+ when NilClass
32
35
  @response.delete_cookie(key.to_s, path: '/')
36
+ when Hash
37
+ @response.set_cookie(key.to_s, new_value)
33
38
  else
34
39
  @response.set_cookie(key.to_s, value: new_value, path: '/')
35
40
  end
@@ -4,24 +4,19 @@ module Flame
4
4
  class Controller
5
5
  ## Module with methods for path or URL building
6
6
  module PathTo
7
+ include Memery
8
+
7
9
  ## Look documentation at {Flame::Dispatcher#path_to}
8
10
  def path_to(*args)
9
11
  add_controller_class(args)
12
+ add_controller_arguments(args)
10
13
  @dispatcher.path_to(*args)
11
14
  end
12
15
 
13
16
  ## Build a URI to the given controller and action, or path
14
17
  def url_to(*args, **options)
15
- first_arg = args.first
16
- path =
17
- if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
18
- find_static(first_arg).path(with_version: options[:version])
19
- else
20
- path_to(*args, **options)
21
- end
22
- Addressable::URI.new(
23
- scheme: request.scheme, host: request.host_with_port, path: path
24
- ).to_s
18
+ path = build_path_for_url(*args, **options)
19
+ Addressable::URI.join(request.base_url, path).to_s
25
20
  end
26
21
 
27
22
  using GorillaPatch::Namespace
@@ -31,9 +26,38 @@ module Flame
31
26
  def path_to_back
32
27
  back_path = request.referer
33
28
  return back_path if back_path && back_path != request.url
29
+
34
30
  return path_to :index if self.class.actions.include?(:index)
31
+
35
32
  '/'
36
33
  end
34
+
35
+ private
36
+
37
+ def add_controller_class(args)
38
+ args.unshift(self.class) if args[0].is_a?(Symbol)
39
+ args.insert(1, :index) if args[0].is_a?(Class) && !args[1].is_a?(Symbol)
40
+ end
41
+
42
+ def add_controller_arguments(args)
43
+ if args[-1].is_a?(Hash)
44
+ args[-1] = controller_arguments.merge args[-1]
45
+ else
46
+ args.push(controller_arguments)
47
+ end
48
+ end
49
+
50
+ def build_path_for_url(*args, **options)
51
+ first_arg = args.first
52
+ if first_arg.is_a?(String) || first_arg.is_a?(Flame::Path)
53
+ find_static(first_arg).path(with_version: options[:version])
54
+ else
55
+ path_to(*args, **options)
56
+ end
57
+ end
58
+
59
+ memoize :build_path_for_url,
60
+ condition: -> { config[:environment] == 'production' }
37
61
  end
38
62
  end
39
63
  end