flame 5.0.0.rc4 → 5.0.0.rc7

Sign up to get free protection for your applications and to get access to all the features.
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