rage-rb 0.7.0 → 1.1.0
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 +4 -4
 - data/.yardopts +1 -1
 - data/CHANGELOG.md +26 -0
 - data/README.md +6 -2
 - data/lib/rage/all.rb +1 -0
 - data/lib/rage/configuration.rb +2 -2
 - data/lib/rage/fiber.rb +66 -7
 - data/lib/rage/fiber_scheduler.rb +20 -10
 - data/lib/rage/logger/json_formatter.rb +5 -3
 - data/lib/rage/logger/logger.rb +25 -14
 - data/lib/rage/logger/text_formatter.rb +5 -3
 - data/lib/rage/middleware/fiber_wrapper.rb +1 -1
 - data/lib/rage/rails.rb +15 -6
 - data/lib/rage/router/backend.rb +1 -19
 - data/lib/rage/router/handler_storage.rb +5 -4
 - data/lib/rage/router/util.rb +33 -0
 - data/lib/rage/rspec.rb +178 -0
 - data/lib/rage/version.rb +1 -1
 - data/rage.gemspec +1 -0
 - metadata +18 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: fd10469efc73f6134f72f79fe21871238b8860e958c085499c47b312adf91764
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 34990a4885df4a4acd55fcbabf8d3a67e57e0609db406ee55d5f71cab0655ffb
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: bc28afd194f122bf436c215cb6d88dfe3d923874f4e07c3c1db5dd3f33a4676354731fad3f6ffc29326423086b59dbb0d1860ecb5cb71cf4c7f0412a252af16f
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 58cd0cea6daba63d67f9285d3b616980d2e69c778620c54b14c7c5cb1eccf093bfbc0644dec081c325d4cdc5f6c032b7f399ccb8ea5b052c1da401dcde08ed6e
         
     | 
    
        data/.yardopts
    CHANGED
    
    | 
         @@ -1 +1 @@ 
     | 
|
| 
       1 
     | 
    
         
            -
            --exclude lib/rage/templates --markup markdown --no-private -o doc
         
     | 
| 
      
 1 
     | 
    
         
            +
            --exclude lib/rage/templates --exclude lib/rage/rspec --exclude lib/rage/rails --markup markdown --no-private -o doc
         
     | 
    
        data/CHANGELOG.md
    CHANGED
    
    | 
         @@ -1,5 +1,31 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ## [Unreleased]
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ## [1.1.0] - 2024-03-25
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            ### Changed
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            - Change the way controller names are logged (#72).
         
     | 
| 
      
 8 
     | 
    
         
            +
            - Use formatters in console (#71).
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            ### Fixed
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            - Fix Fiber.await behavior in RSpec (#70).
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            ## [1.0.0] - 2024-03-13
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            ### Added
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            - RSpec integration (#60).
         
     | 
| 
      
 19 
     | 
    
         
            +
            - Add DNS cache (#65).
         
     | 
| 
      
 20 
     | 
    
         
            +
            - Allow to disable the `FiberScheduler#io_write` hook (#63).
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
            ### Fixed
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            - Preload fiber ID (#62).
         
     | 
| 
      
 25 
     | 
    
         
            +
            - Release ActiveRecord connections on yield (#66).
         
     | 
| 
      
 26 
     | 
    
         
            +
            - Logger fixes (#64).
         
     | 
| 
      
 27 
     | 
    
         
            +
            - Fix publish calls in cluster mode (#67).
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
       3 
29 
     | 
    
         
             
            ## [0.7.0] - 2024-01-09
         
     | 
| 
       4 
30 
     | 
    
         | 
| 
       5 
31 
     | 
    
         
             
            - Add conditional GET using `stale?` by [@tonekk](https://github.com/tonekk) (#55).
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -53,8 +53,12 @@ Check out in-depth API docs for more information: 
     | 
|
| 
       53 
53 
     | 
    
         
             
            - [Fiber API](https://rage-rb.pages.dev/Fiber)
         
     | 
| 
       54 
54 
     | 
    
         
             
            - [Logger API](https://rage-rb.pages.dev/Rage/Logger)
         
     | 
| 
       55 
55 
     | 
    
         
             
            - [Configuration API](https://rage-rb.pages.dev/Rage/Configuration)
         
     | 
| 
      
 56 
     | 
    
         
            +
            - [CORS middleware](https://rage-rb.pages.dev/Rage/Cors)
         
     | 
| 
       56 
57 
     | 
    
         | 
| 
       57 
     | 
    
         
            -
            Also, see the  
     | 
| 
      
 58 
     | 
    
         
            +
            Also, see the following integration guides:
         
     | 
| 
      
 59 
     | 
    
         
            +
             
     | 
| 
      
 60 
     | 
    
         
            +
            - [Rails integration](https://github.com/rage-rb/rage/wiki/Rails-integration)
         
     | 
| 
      
 61 
     | 
    
         
            +
            - [RSpec integration](https://github.com/rage-rb/rage/wiki/RSpec-integration)
         
     | 
| 
       58 
62 
     | 
    
         | 
| 
       59 
63 
     | 
    
         
             
            ### Example
         
     | 
| 
       60 
64 
     | 
    
         | 
| 
         @@ -150,8 +154,8 @@ Status | Changes 
     | 
|
| 
       150 
154 
     | 
    
         
             
            :white_check_mark: | ~~Expose the `params` object.<br>Support header authentication with `authenticate_with_http_token`.<br>Router updates:<br> • add the `resources` route helper;<br> • add the `namespace` route helper;~~
         
     | 
| 
       151 
155 
     | 
    
         
             
            :white_check_mark:  | ~~Add request logging.~~
         
     | 
| 
       152 
156 
     | 
    
         
             
            :white_check_mark: | ~~Automatic code reloading in development with Zeitwerk.~~
         
     | 
| 
      
 157 
     | 
    
         
            +
            :white_check_mark: | ~~Support conditional get with `etag` and `last_modified`.~~
         
     | 
| 
       153 
158 
     | 
    
         
             
            ⏳ | Expose the `send_data` and `send_file` methods.
         
     | 
| 
       154 
     | 
    
         
            -
            ⏳ | Support conditional get with `etag` and `last_modified`.
         
     | 
| 
       155 
159 
     | 
    
         
             
            ⏳ | Expose the `cookies` and `session` objects.
         
     | 
| 
       156 
160 
     | 
    
         
             
            ⏳ | Implement Iodine-based equivalent of Action Cable.
         
     | 
| 
       157 
161 
     | 
    
         | 
    
        data/lib/rage/all.rb
    CHANGED
    
    
    
        data/lib/rage/configuration.rb
    CHANGED
    
    | 
         @@ -19,7 +19,7 @@ 
     | 
|
| 
       19 
19 
     | 
    
         
             
            #
         
     | 
| 
       20 
20 
     | 
    
         
             
            # • _config.middleware.use_
         
     | 
| 
       21 
21 
     | 
    
         
             
            #
         
     | 
| 
       22 
     | 
    
         
            -
            # > Adds a middleware to the top of the middleware stack. **This is the  
     | 
| 
      
 22 
     | 
    
         
            +
            # > Adds a middleware to the top of the middleware stack. **This is the recommended way of adding a middleware.**
         
     | 
| 
       23 
23 
     | 
    
         
             
            # > ```
         
     | 
| 
       24 
24 
     | 
    
         
             
            # config.middleware.use Rack::Cors do
         
     | 
| 
       25 
25 
     | 
    
         
             
            #   allow do
         
     | 
| 
         @@ -166,7 +166,7 @@ class Rage::Configuration 
     | 
|
| 
       166 
166 
     | 
    
         | 
| 
       167 
167 
     | 
    
         
             
              # @private
         
     | 
| 
       168 
168 
     | 
    
         
             
              class Internal
         
     | 
| 
       169 
     | 
    
         
            -
                attr_accessor :rails_mode 
     | 
| 
      
 169 
     | 
    
         
            +
                attr_accessor :rails_mode
         
     | 
| 
       170 
170 
     | 
    
         | 
| 
       171 
171 
     | 
    
         
             
                def inspect
         
     | 
| 
       172 
172 
     | 
    
         
             
                  "#<#{self.class.name}>"
         
     | 
    
        data/lib/rage/fiber.rb
    CHANGED
    
    | 
         @@ -1,6 +1,46 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
      
 3 
     | 
    
         
            +
            ##
         
     | 
| 
      
 4 
     | 
    
         
            +
            # Rage provides a simple and efficient API to wait on several instances of IO at the same time - {Fiber.await}.
         
     | 
| 
      
 5 
     | 
    
         
            +
            # 
         
     | 
| 
      
 6 
     | 
    
         
            +
            # Let's say we have the following controller:
         
     | 
| 
      
 7 
     | 
    
         
            +
            # ```ruby
         
     | 
| 
      
 8 
     | 
    
         
            +
            # class UsersController < RageController::API
         
     | 
| 
      
 9 
     | 
    
         
            +
            #   def show
         
     | 
| 
      
 10 
     | 
    
         
            +
            #     user = Net::HTTP.get(URI("http://users.service/users/#{params[:id]}"))
         
     | 
| 
      
 11 
     | 
    
         
            +
            #     bookings = Net::HTTP.get(URI("http://bookings.service/bookings?user_id=#{params[:id]}"))
         
     | 
| 
      
 12 
     | 
    
         
            +
            #     render json: { user: user, bookings: bookings }
         
     | 
| 
      
 13 
     | 
    
         
            +
            #   end
         
     | 
| 
      
 14 
     | 
    
         
            +
            # end
         
     | 
| 
      
 15 
     | 
    
         
            +
            # ```
         
     | 
| 
      
 16 
     | 
    
         
            +
            # This code will fire two consecutive HTTP requests. If each request takes 1 second to execute, the total execution time will be 2 seconds.<br>
         
     | 
| 
      
 17 
     | 
    
         
            +
            # With {Fiber.await}, we can significantly decrease the overall execution time by changing the code to fire the requests concurrently.
         
     | 
| 
      
 18 
     | 
    
         
            +
            #
         
     | 
| 
      
 19 
     | 
    
         
            +
            # To do this, we will need to:
         
     | 
| 
      
 20 
     | 
    
         
            +
            #
         
     | 
| 
      
 21 
     | 
    
         
            +
            # 1. Wrap every request in a separate fiber using {Fiber.schedule};
         
     | 
| 
      
 22 
     | 
    
         
            +
            # 2. Pass newly created fibers into {Fiber.await};
         
     | 
| 
      
 23 
     | 
    
         
            +
            #
         
     | 
| 
      
 24 
     | 
    
         
            +
            # ```ruby
         
     | 
| 
      
 25 
     | 
    
         
            +
            # class UsersController < RageController::API
         
     | 
| 
      
 26 
     | 
    
         
            +
            #   def show
         
     | 
| 
      
 27 
     | 
    
         
            +
            #     user, bookings = Fiber.await([
         
     | 
| 
      
 28 
     | 
    
         
            +
            #       Fiber.schedule { Net::HTTP.get(URI("http://users.service/users/#{params[:id]}")) },
         
     | 
| 
      
 29 
     | 
    
         
            +
            #       Fiber.schedule { Net::HTTP.get(URI("http://bookings.service/bookings?user_id=#{params[:id]}")) }
         
     | 
| 
      
 30 
     | 
    
         
            +
            #     ])
         
     | 
| 
      
 31 
     | 
    
         
            +
            #
         
     | 
| 
      
 32 
     | 
    
         
            +
            #     render json: { user: user, bookings: bookings }
         
     | 
| 
      
 33 
     | 
    
         
            +
            #   end
         
     | 
| 
      
 34 
     | 
    
         
            +
            # end
         
     | 
| 
      
 35 
     | 
    
         
            +
            # ```
         
     | 
| 
      
 36 
     | 
    
         
            +
            # With this change, if each request takes 1 second to execute, the total execution time will still be 1 second.
         
     | 
| 
      
 37 
     | 
    
         
            +
            # 
         
     | 
| 
      
 38 
     | 
    
         
            +
            # ## Creating fibers
         
     | 
| 
      
 39 
     | 
    
         
            +
            # Many developers see fibers as "lightweight threads" that should be used in conjunction with fiber pools, the same way we use thread pools for threads.<br>
         
     | 
| 
      
 40 
     | 
    
         
            +
            # Instead, it makes sense to think of fibers as regular Ruby objects. We don't use a pool of arrays when we need to create an array - we create a new object and let Ruby and the GC do their job.<br>
         
     | 
| 
      
 41 
     | 
    
         
            +
            # Same applies to fibers. Feel free to create as many fibers as you need on demand.
         
     | 
| 
       3 
42 
     | 
    
         
             
            class Fiber
         
     | 
| 
      
 43 
     | 
    
         
            +
              # @private
         
     | 
| 
       4 
44 
     | 
    
         
             
              AWAIT_ERROR_MESSAGE = "err"
         
     | 
| 
       5 
45 
     | 
    
         | 
| 
       6 
46 
     | 
    
         
             
              # @private
         
     | 
| 
         @@ -23,14 +63,14 @@ class Fiber 
     | 
|
| 
       23 
63 
     | 
    
         
             
                @__err
         
     | 
| 
       24 
64 
     | 
    
         
             
              end
         
     | 
| 
       25 
65 
     | 
    
         | 
| 
       26 
     | 
    
         
            -
             
     | 
| 
       27 
     | 
    
         
            -
              def  
     | 
| 
       28 
     | 
    
         
            -
                @__rage_id  
     | 
| 
      
 66 
     | 
    
         
            +
              # @private
         
     | 
| 
      
 67 
     | 
    
         
            +
              def __set_id
         
     | 
| 
      
 68 
     | 
    
         
            +
                @__rage_id = object_id.to_s
         
     | 
| 
       29 
69 
     | 
    
         
             
              end
         
     | 
| 
       30 
70 
     | 
    
         | 
| 
       31 
     | 
    
         
            -
             
     | 
| 
       32 
     | 
    
         
            -
              def  
     | 
| 
       33 
     | 
    
         
            -
                 
     | 
| 
      
 71 
     | 
    
         
            +
               # @private
         
     | 
| 
      
 72 
     | 
    
         
            +
              def __get_id
         
     | 
| 
      
 73 
     | 
    
         
            +
                @__rage_id
         
     | 
| 
       34 
74 
     | 
    
         
             
              end
         
     | 
| 
       35 
75 
     | 
    
         | 
| 
       36 
76 
     | 
    
         
             
              # @private
         
     | 
| 
         @@ -49,8 +89,15 @@ class Fiber 
     | 
|
| 
       49 
89 
     | 
    
         
             
                Fiber.yield
         
     | 
| 
       50 
90 
     | 
    
         
             
              end
         
     | 
| 
       51 
91 
     | 
    
         | 
| 
      
 92 
     | 
    
         
            +
              # @private
         
     | 
| 
      
 93 
     | 
    
         
            +
              # under normal circumstances, the method is a copy of `yield`, but it can be overriden to perform
         
     | 
| 
      
 94 
     | 
    
         
            +
              # additional steps on yielding, e.g. releasing AR connections; see "lib/rage/rails.rb"
         
     | 
| 
      
 95 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 96 
     | 
    
         
            +
                alias_method :defer, :yield
         
     | 
| 
      
 97 
     | 
    
         
            +
              end
         
     | 
| 
      
 98 
     | 
    
         
            +
             
     | 
| 
       52 
99 
     | 
    
         
             
              # Wait on several fibers at the same time. Calling this method will automatically pause the current fiber, allowing the
         
     | 
| 
       53 
     | 
    
         
            -
              # 
     | 
| 
      
 100 
     | 
    
         
            +
              # server to process other requests. Once all fibers have completed, the current fiber will be automatically resumed.
         
     | 
| 
       54 
101 
     | 
    
         
             
              #
         
     | 
| 
       55 
102 
     | 
    
         
             
              # @param fibers [Fiber, Array<Fiber>] one or several fibers to wait on. The fibers must be created using the `Fiber.schedule` call.
         
     | 
| 
       56 
103 
     | 
    
         
             
              # @example
         
     | 
| 
         @@ -101,4 +148,16 @@ class Fiber 
     | 
|
| 
       101 
148 
     | 
    
         
             
                  fibers.map!(&:__get_result)
         
     | 
| 
       102 
149 
     | 
    
         
             
                end
         
     | 
| 
       103 
150 
     | 
    
         
             
              end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
              # @!method self.schedule(&block)
         
     | 
| 
      
 153 
     | 
    
         
            +
              #   Create a non-blocking fiber. Should mostly be used in conjunction with `Fiber.await`.
         
     | 
| 
      
 154 
     | 
    
         
            +
              #   @example
         
     | 
| 
      
 155 
     | 
    
         
            +
              #     Fiber.await([
         
     | 
| 
      
 156 
     | 
    
         
            +
              #       Fiber.schedule { request_1 },
         
     | 
| 
      
 157 
     | 
    
         
            +
              #       Fiber.schedule { request_2 }
         
     | 
| 
      
 158 
     | 
    
         
            +
              #     ])
         
     | 
| 
      
 159 
     | 
    
         
            +
              #   @example
         
     | 
| 
      
 160 
     | 
    
         
            +
              #     fiber_1 = Fiber.schedule { request_1 }
         
     | 
| 
      
 161 
     | 
    
         
            +
              #     fiber_2 = Fiber.schedule { request_2 }
         
     | 
| 
      
 162 
     | 
    
         
            +
              #     Fiber.await([fiber_1, fiber_2])
         
     | 
| 
       104 
163 
     | 
    
         
             
            end
         
     | 
    
        data/lib/rage/fiber_scheduler.rb
    CHANGED
    
    | 
         @@ -7,13 +7,14 @@ class Rage::FiberScheduler 
     | 
|
| 
       7 
7 
     | 
    
         | 
| 
       8 
8 
     | 
    
         
             
              def initialize
         
     | 
| 
       9 
9 
     | 
    
         
             
                @root_fiber = Fiber.current
         
     | 
| 
      
 10 
     | 
    
         
            +
                @dns_cache = {}
         
     | 
| 
       10 
11 
     | 
    
         
             
              end
         
     | 
| 
       11 
12 
     | 
    
         | 
| 
       12 
13 
     | 
    
         
             
              def io_wait(io, events, timeout = nil)
         
     | 
| 
       13 
14 
     | 
    
         
             
                f = Fiber.current
         
     | 
| 
       14 
15 
     | 
    
         
             
                ::Iodine::Scheduler.attach(io.fileno, events, timeout&.ceil || 0) { |err| f.resume(err) }
         
     | 
| 
       15 
16 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                err = Fiber. 
     | 
| 
      
 17 
     | 
    
         
            +
                err = Fiber.defer
         
     | 
| 
       17 
18 
     | 
    
         
             
                if err == Errno::ETIMEDOUT::Errno
         
     | 
| 
       18 
19 
     | 
    
         
             
                  0
         
     | 
| 
       19 
20 
     | 
    
         
             
                else
         
     | 
| 
         @@ -49,13 +50,15 @@ class Rage::FiberScheduler 
     | 
|
| 
       49 
50 
     | 
    
         
             
                end
         
     | 
| 
       50 
51 
     | 
    
         
             
              end
         
     | 
| 
       51 
52 
     | 
    
         | 
| 
       52 
     | 
    
         
            -
               
     | 
| 
       53 
     | 
    
         
            -
                 
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
      
 53 
     | 
    
         
            +
              unless ENV["RAGE_DISABLE_IO_WRITE"]
         
     | 
| 
      
 54 
     | 
    
         
            +
                def io_write(io, buffer, length, offset = 0)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  bytes_to_write = length
         
     | 
| 
      
 56 
     | 
    
         
            +
                  bytes_to_write = buffer.size if length == 0
         
     | 
| 
       55 
57 
     | 
    
         | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
      
 58 
     | 
    
         
            +
                  ::Iodine::Scheduler.write(io.fileno, buffer.get_string, bytes_to_write, offset)
         
     | 
| 
       57 
59 
     | 
    
         | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
      
 60 
     | 
    
         
            +
                  bytes_to_write - offset
         
     | 
| 
      
 61 
     | 
    
         
            +
                end
         
     | 
| 
       59 
62 
     | 
    
         
             
              end
         
     | 
| 
       60 
63 
     | 
    
         | 
| 
       61 
64 
     | 
    
         
             
              def kernel_sleep(duration = nil)
         
     | 
| 
         @@ -77,7 +80,13 @@ class Rage::FiberScheduler 
     | 
|
| 
       77 
80 
     | 
    
         
             
              # end
         
     | 
| 
       78 
81 
     | 
    
         | 
| 
       79 
82 
     | 
    
         
             
              def address_resolve(hostname)
         
     | 
| 
       80 
     | 
    
         
            -
                 
     | 
| 
      
 83 
     | 
    
         
            +
                @dns_cache[hostname] ||= begin
         
     | 
| 
      
 84 
     | 
    
         
            +
                  ::Iodine.run_after(60_000) do
         
     | 
| 
      
 85 
     | 
    
         
            +
                    @dns_cache[hostname] = nil
         
     | 
| 
      
 86 
     | 
    
         
            +
                  end
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
                  Resolv.getaddresses(hostname)
         
     | 
| 
      
 89 
     | 
    
         
            +
                end
         
     | 
| 
       81 
90 
     | 
    
         
             
              end
         
     | 
| 
       82 
91 
     | 
    
         | 
| 
       83 
92 
     | 
    
         
             
              def block(_blocker, timeout = nil)
         
     | 
| 
         @@ -100,7 +109,7 @@ class Rage::FiberScheduler 
     | 
|
| 
       100 
109 
     | 
    
         
             
              end
         
     | 
| 
       101 
110 
     | 
    
         | 
| 
       102 
111 
     | 
    
         
             
              def unblock(_blocker, fiber)
         
     | 
| 
       103 
     | 
    
         
            -
                ::Iodine.publish(fiber.__block_channel, "")
         
     | 
| 
      
 112 
     | 
    
         
            +
                ::Iodine.publish(fiber.__block_channel, "", Iodine::PubSub::PROCESS)
         
     | 
| 
       104 
113 
     | 
    
         
             
              end
         
     | 
| 
       105 
114 
     | 
    
         | 
| 
       106 
115 
     | 
    
         
             
              def fiber(&block)
         
     | 
| 
         @@ -109,6 +118,7 @@ class Rage::FiberScheduler 
     | 
|
| 
       109 
118 
     | 
    
         
             
                fiber = if parent == @root_fiber
         
     | 
| 
       110 
119 
     | 
    
         
             
                  # the fiber to wrap a request in
         
     | 
| 
       111 
120 
     | 
    
         
             
                  Fiber.new(blocking: false) do
         
     | 
| 
      
 121 
     | 
    
         
            +
                    Fiber.current.__set_id
         
     | 
| 
       112 
122 
     | 
    
         
             
                    Fiber.current.__set_result(block.call)
         
     | 
| 
       113 
123 
     | 
    
         
             
                  end
         
     | 
| 
       114 
124 
     | 
    
         
             
                else
         
     | 
| 
         @@ -119,10 +129,10 @@ class Rage::FiberScheduler 
     | 
|
| 
       119 
129 
     | 
    
         
             
                    Thread.current[:rage_logger] = logger
         
     | 
| 
       120 
130 
     | 
    
         
             
                    Fiber.current.__set_result(block.call)
         
     | 
| 
       121 
131 
     | 
    
         
             
                    # send a message for `Fiber.await` to work
         
     | 
| 
       122 
     | 
    
         
            -
                    Iodine.publish("await:#{parent.object_id}", "") if parent.alive?
         
     | 
| 
      
 132 
     | 
    
         
            +
                    Iodine.publish("await:#{parent.object_id}", "", Iodine::PubSub::PROCESS) if parent.alive?
         
     | 
| 
       123 
133 
     | 
    
         
             
                  rescue Exception => e
         
     | 
| 
       124 
134 
     | 
    
         
             
                    Fiber.current.__set_err(e)
         
     | 
| 
       125 
     | 
    
         
            -
                    Iodine.publish("await:#{parent.object_id}", Fiber::AWAIT_ERROR_MESSAGE) if parent.alive?
         
     | 
| 
      
 135 
     | 
    
         
            +
                    Iodine.publish("await:#{parent.object_id}", Fiber::AWAIT_ERROR_MESSAGE, Iodine::PubSub::PROCESS) if parent.alive?
         
     | 
| 
       126 
136 
     | 
    
         
             
                  end
         
     | 
| 
       127 
137 
     | 
    
         
             
                end
         
     | 
| 
       128 
138 
     | 
    
         | 
| 
         @@ -7,7 +7,7 @@ class Rage::JSONFormatter 
     | 
|
| 
       7 
7 
     | 
    
         
             
              end
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
              def call(severity, timestamp, _, message)
         
     | 
| 
       10 
     | 
    
         
            -
                logger = Thread.current[:rage_logger]
         
     | 
| 
      
 10 
     | 
    
         
            +
                logger = Thread.current[:rage_logger] || { tags: [], context: {} }
         
     | 
| 
       11 
11 
     | 
    
         
             
                tags, context = logger[:tags], logger[:context]
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
                if !context.empty?
         
     | 
| 
         @@ -17,8 +17,8 @@ class Rage::JSONFormatter 
     | 
|
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                if final = logger[:final]
         
     | 
| 
       19 
19 
     | 
    
         
             
                  params, env = final[:params], final[:env]
         
     | 
| 
       20 
     | 
    
         
            -
                  if params
         
     | 
| 
       21 
     | 
    
         
            -
                    return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",\"controller\":\"#{params[:controller]}\",\"action\":\"#{params[:action]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
         
     | 
| 
      
 20 
     | 
    
         
            +
                  if params && params[:controller]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",\"controller\":\"#{Rage::Router::Util.path_to_name(params[:controller])}\",\"action\":\"#{params[:action]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
         
     | 
| 
       22 
22 
     | 
    
         
             
                  else
         
     | 
| 
       23 
23 
     | 
    
         
             
                    # no controller/action keys are written if there are no params
         
     | 
| 
       24 
24 
     | 
    
         
             
                    return "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"info\",\"method\":\"#{env["REQUEST_METHOD"]}\",\"path\":\"#{env["PATH_INFO"]}\",#{context_msg}\"status\":#{final[:response][0]},\"duration\":#{final[:duration]}}\n"
         
     | 
| 
         @@ -29,6 +29,8 @@ class Rage::JSONFormatter 
     | 
|
| 
       29 
29 
     | 
    
         
             
                  tags_msg = "{\"tags\":[\"#{tags[0]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
         
     | 
| 
       30 
30 
     | 
    
         
             
                elsif tags.length == 2
         
     | 
| 
       31 
31 
     | 
    
         
             
                  tags_msg = "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\"],\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
         
     | 
| 
      
 32 
     | 
    
         
            +
                elsif tags.length == 0
         
     | 
| 
      
 33 
     | 
    
         
            +
                  tags_msg = "{\"timestamp\":\"#{timestamp}\",\"pid\":\"#{@pid}\",\"level\":\"#{severity}\""
         
     | 
| 
       32 
34 
     | 
    
         
             
                else
         
     | 
| 
       33 
35 
     | 
    
         
             
                  tags_msg = "{\"tags\":[\"#{tags[0]}\",\"#{tags[1]}\""
         
     | 
| 
       34 
36 
     | 
    
         
             
                  i = 2
         
     | 
    
        data/lib/rage/logger/logger.rb
    CHANGED
    
    | 
         @@ -33,6 +33,23 @@ require "logger" 
     | 
|
| 
       33 
33 
     | 
    
         
             
            # Rage.logger.info("Initializing")
         
     | 
| 
       34 
34 
     | 
    
         
             
            # Rage.logger.debug { "This is a " + potentially + " expensive operation" }
         
     | 
| 
       35 
35 
     | 
    
         
             
            # ```
         
     | 
| 
      
 36 
     | 
    
         
            +
            #
         
     | 
| 
      
 37 
     | 
    
         
            +
            # ## Using the logger
         
     | 
| 
      
 38 
     | 
    
         
            +
            # The recommended approach to logging with Rage is to make sure your code always logs the same message no matter what the input is.
         
     | 
| 
      
 39 
     | 
    
         
            +
            # You can achieve this by using the {with_context} and {tagged} methods. So, a code like this:
         
     | 
| 
      
 40 
     | 
    
         
            +
            # ```ruby
         
     | 
| 
      
 41 
     | 
    
         
            +
            # def process_purchase(user_id:, product_id:)
         
     | 
| 
      
 42 
     | 
    
         
            +
            #   Rage.logger.info "processing purchase with user_id = #{user_id}; product_id = #{product_id}"
         
     | 
| 
      
 43 
     | 
    
         
            +
            # end
         
     | 
| 
      
 44 
     | 
    
         
            +
            # ```
         
     | 
| 
      
 45 
     | 
    
         
            +
            # turns into this:
         
     | 
| 
      
 46 
     | 
    
         
            +
            # ```ruby
         
     | 
| 
      
 47 
     | 
    
         
            +
            # def process_purchase(user_id:, product_id:)
         
     | 
| 
      
 48 
     | 
    
         
            +
            #   Rage.logger.with_context(user_id: user_id, product_id: product_id) do
         
     | 
| 
      
 49 
     | 
    
         
            +
            #     Rage.logger.info "processing purchase"
         
     | 
| 
      
 50 
     | 
    
         
            +
            #   end
         
     | 
| 
      
 51 
     | 
    
         
            +
            # end
         
     | 
| 
      
 52 
     | 
    
         
            +
            # ```
         
     | 
| 
       36 
53 
     | 
    
         
             
            class Rage::Logger
         
     | 
| 
       37 
54 
     | 
    
         
             
              METHODS_MAP = {
         
     | 
| 
       38 
55 
     | 
    
         
             
                "debug" => Logger::DEBUG,
         
     | 
| 
         @@ -83,7 +100,7 @@ class Rage::Logger 
     | 
|
| 
       83 
100 
     | 
    
         
             
              #     Rage.logger.info "cache miss"
         
     | 
| 
       84 
101 
     | 
    
         
             
              #   end
         
     | 
| 
       85 
102 
     | 
    
         
             
              def with_context(context)
         
     | 
| 
       86 
     | 
    
         
            -
                old_context = Thread.current[:rage_logger][:context]
         
     | 
| 
      
 103 
     | 
    
         
            +
                old_context = (Thread.current[:rage_logger] ||= { tags: [], context: {} })[:context]
         
     | 
| 
       87 
104 
     | 
    
         | 
| 
       88 
105 
     | 
    
         
             
                if old_context.empty? # there's nothing in the context yet
         
     | 
| 
       89 
106 
     | 
    
         
             
                  Thread.current[:rage_logger][:context] = context
         
     | 
| 
         @@ -92,8 +109,6 @@ class Rage::Logger 
     | 
|
| 
       92 
109 
     | 
    
         
             
                end
         
     | 
| 
       93 
110 
     | 
    
         | 
| 
       94 
111 
     | 
    
         
             
                yield(self)
         
     | 
| 
       95 
     | 
    
         
            -
                true
         
     | 
| 
       96 
     | 
    
         
            -
             
     | 
| 
       97 
112 
     | 
    
         
             
              ensure
         
     | 
| 
       98 
113 
     | 
    
         
             
                Thread.current[:rage_logger][:context] = old_context
         
     | 
| 
       99 
114 
     | 
    
         
             
              end
         
     | 
| 
         @@ -106,17 +121,20 @@ class Rage::Logger 
     | 
|
| 
       106 
121 
     | 
    
         
             
              #     Rage.logger.info "success"
         
     | 
| 
       107 
122 
     | 
    
         
             
              #   end
         
     | 
| 
       108 
123 
     | 
    
         
             
              def tagged(tag)
         
     | 
| 
       109 
     | 
    
         
            -
                Thread.current[:rage_logger][:tags] << tag
         
     | 
| 
       110 
     | 
    
         
            -
             
     | 
| 
      
 124 
     | 
    
         
            +
                (Thread.current[:rage_logger] ||= { tags: [], context: {} })[:tags] << tag
         
     | 
| 
       111 
125 
     | 
    
         
             
                yield(self)
         
     | 
| 
       112 
     | 
    
         
            -
                true
         
     | 
| 
       113 
     | 
    
         
            -
             
     | 
| 
       114 
126 
     | 
    
         
             
              ensure
         
     | 
| 
       115 
127 
     | 
    
         
             
                Thread.current[:rage_logger][:tags].pop
         
     | 
| 
       116 
128 
     | 
    
         
             
              end
         
     | 
| 
       117 
129 
     | 
    
         | 
| 
       118 
130 
     | 
    
         
             
              alias_method :with_tag, :tagged
         
     | 
| 
       119 
131 
     | 
    
         | 
| 
      
 132 
     | 
    
         
            +
              def debug? = @level <= Logger::DEBUG
         
     | 
| 
      
 133 
     | 
    
         
            +
              def error? = @level <= Logger::ERROR
         
     | 
| 
      
 134 
     | 
    
         
            +
              def fatal? = @level <= Logger::FATAL
         
     | 
| 
      
 135 
     | 
    
         
            +
              def info? = @level <= Logger::INFO
         
     | 
| 
      
 136 
     | 
    
         
            +
              def warn? = @level <= Logger::WARN
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
       120 
138 
     | 
    
         
             
              private
         
     | 
| 
       121 
139 
     | 
    
         | 
| 
       122 
140 
     | 
    
         
             
              def define_log_methods
         
     | 
| 
         @@ -128,13 +146,6 @@ class Rage::Logger 
     | 
|
| 
       128 
146 
     | 
    
         
             
                        false
         
     | 
| 
       129 
147 
     | 
    
         
             
                      end
         
     | 
| 
       130 
148 
     | 
    
         
             
                    RUBY
         
     | 
| 
       131 
     | 
    
         
            -
                  elsif (Rage.config.internal.rails_mode ? Rage.config.internal.rails_console : defined?(IRB))
         
     | 
| 
       132 
     | 
    
         
            -
                    # the call was made from the console - don't use the formatter
         
     | 
| 
       133 
     | 
    
         
            -
                    <<-RUBY
         
     | 
| 
       134 
     | 
    
         
            -
                      def #{level_name}(msg = nil)
         
     | 
| 
       135 
     | 
    
         
            -
                        @logdev.write((msg || yield) + "\n")
         
     | 
| 
       136 
     | 
    
         
            -
                      end
         
     | 
| 
       137 
     | 
    
         
            -
                    RUBY
         
     | 
| 
       138 
149 
     | 
    
         
             
                  elsif @formatter.class.name.start_with?("Rage::")
         
     | 
| 
       139 
150 
     | 
    
         
             
                    # the call was made from within the application and a built-in formatter is used;
         
     | 
| 
       140 
151 
     | 
    
         
             
                    # in such case we use the `gen_timestamp` method which is much faster than `Time.now.strftime`;
         
     | 
| 
         @@ -7,7 +7,7 @@ class Rage::TextFormatter 
     | 
|
| 
       7 
7 
     | 
    
         
             
              end
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
              def call(severity, timestamp, _, message)
         
     | 
| 
       10 
     | 
    
         
            -
                logger = Thread.current[:rage_logger]
         
     | 
| 
      
 10 
     | 
    
         
            +
                logger = Thread.current[:rage_logger] || { tags: [], context: {} }
         
     | 
| 
       11 
11 
     | 
    
         
             
                tags, context = logger[:tags], logger[:context]
         
     | 
| 
       12 
12 
     | 
    
         | 
| 
       13 
13 
     | 
    
         
             
                if !context.empty?
         
     | 
| 
         @@ -17,8 +17,8 @@ class Rage::TextFormatter 
     | 
|
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                if final = logger[:final]
         
     | 
| 
       19 
19 
     | 
    
         
             
                  params, env = final[:params], final[:env]
         
     | 
| 
       20 
     | 
    
         
            -
                  if params
         
     | 
| 
       21 
     | 
    
         
            -
                    return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{params[:controller]} action=#{params[:action]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
         
     | 
| 
      
 20 
     | 
    
         
            +
                  if params && params[:controller]
         
     | 
| 
      
 21 
     | 
    
         
            +
                    return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} controller=#{Rage::Router::Util.path_to_name(params[:controller])} action=#{params[:action]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
         
     | 
| 
       22 
22 
     | 
    
         
             
                  else
         
     | 
| 
       23 
23 
     | 
    
         
             
                    # no controller/action keys are written if there are no params
         
     | 
| 
       24 
24 
     | 
    
         
             
                    return "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=info method=#{env["REQUEST_METHOD"]} path=#{env["PATH_INFO"]} #{context_msg}status=#{final[:response][0]} duration=#{final[:duration]}\n"
         
     | 
| 
         @@ -29,6 +29,8 @@ class Rage::TextFormatter 
     | 
|
| 
       29 
29 
     | 
    
         
             
                  tags_msg = "[#{tags[0]}] timestamp=#{timestamp} pid=#{@pid} level=#{severity}"
         
     | 
| 
       30 
30 
     | 
    
         
             
                elsif tags.length == 2
         
     | 
| 
       31 
31 
     | 
    
         
             
                  tags_msg = "[#{tags[0]}][#{tags[1]}] timestamp=#{timestamp} pid=#{@pid} level=#{severity}"
         
     | 
| 
      
 32 
     | 
    
         
            +
                elsif tags.length == 0
         
     | 
| 
      
 33 
     | 
    
         
            +
                  tags_msg = "timestamp=#{timestamp} pid=#{@pid} level=#{severity}"
         
     | 
| 
       32 
34 
     | 
    
         
             
                else
         
     | 
| 
       33 
35 
     | 
    
         
             
                  tags_msg = "[#{tags[0]}][#{tags[1]}]"
         
     | 
| 
       34 
36 
     | 
    
         
             
                  i = 2
         
     | 
| 
         @@ -17,7 +17,7 @@ class Rage::FiberWrapper 
     | 
|
| 
       17 
17 
     | 
    
         
             
                  @app.call(env)
         
     | 
| 
       18 
18 
     | 
    
         
             
                ensure
         
     | 
| 
       19 
19 
     | 
    
         
             
                  # notify Iodine the request can now be resumed
         
     | 
| 
       20 
     | 
    
         
            -
                  Iodine.publish(Fiber.current.__get_id, "", Iodine::PubSub::PROCESS) 
     | 
| 
      
 20 
     | 
    
         
            +
                  Iodine.publish(Fiber.current.__get_id, "", Iodine::PubSub::PROCESS)
         
     | 
| 
       21 
21 
     | 
    
         
             
                end
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
23 
     | 
    
         
             
                # the fiber encountered blocking IO and yielded; instruct Iodine to pause the request
         
     | 
    
        data/lib/rage/rails.rb
    CHANGED
    
    | 
         @@ -11,12 +11,6 @@ Iodine.patch_rack 
     | 
|
| 
       11 
11 
     | 
    
         
             
            # configure the framework
         
     | 
| 
       12 
12 
     | 
    
         
             
            Rage.config.internal.rails_mode = true
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
       14 
     | 
    
         
            -
            # make sure log formatter is not used in console
         
     | 
| 
       15 
     | 
    
         
            -
            Rails.application.console do
         
     | 
| 
       16 
     | 
    
         
            -
              Rage.config.internal.rails_console = true
         
     | 
| 
       17 
     | 
    
         
            -
              Rage.logger.level = Rage.logger.level if Rage.logger # trigger redefining log methods
         
     | 
| 
       18 
     | 
    
         
            -
            end
         
     | 
| 
       19 
     | 
    
         
            -
             
     | 
| 
       20 
14 
     | 
    
         
             
            # patch ActiveRecord's connection pool
         
     | 
| 
       21 
15 
     | 
    
         
             
            if defined?(ActiveRecord)
         
     | 
| 
       22 
16 
     | 
    
         
             
              Rails.configuration.after_initialize do
         
     | 
| 
         @@ -30,6 +24,21 @@ if defined?(ActiveRecord) 
     | 
|
| 
       30 
24 
     | 
    
         
             
              end
         
     | 
| 
       31 
25 
     | 
    
         
             
            end
         
     | 
| 
       32 
26 
     | 
    
         | 
| 
      
 27 
     | 
    
         
            +
            # release ActiveRecord connections on yield
         
     | 
| 
      
 28 
     | 
    
         
            +
            if defined?(ActiveRecord)
         
     | 
| 
      
 29 
     | 
    
         
            +
              class Fiber
         
     | 
| 
      
 30 
     | 
    
         
            +
                def self.defer
         
     | 
| 
      
 31 
     | 
    
         
            +
                  res = Fiber.yield
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  if ActiveRecord::Base.connection_pool.active_connection?
         
     | 
| 
      
 34 
     | 
    
         
            +
                    ActiveRecord::Base.connection_handler.clear_active_connections!
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  res
         
     | 
| 
      
 38 
     | 
    
         
            +
                end
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
            end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
       33 
42 
     | 
    
         
             
            # plug into Rails' Zeitwerk instance to reload the code
         
     | 
| 
       34 
43 
     | 
    
         
             
            Rails.autoloaders.main.on_setup do
         
     | 
| 
       35 
44 
     | 
    
         
             
              if Iodine.running?
         
     | 
    
        data/lib/rage/router/backend.rb
    CHANGED
    
    | 
         @@ -67,7 +67,7 @@ class Rage::Router::Backend 
     | 
|
| 
       67 
67 
     | 
    
         
             
                if handler.is_a?(String)
         
     | 
| 
       68 
68 
     | 
    
         
             
                  raise "Invalid route handler format, expected to match the 'controller#action' pattern" unless handler =~ STRING_HANDLER_REGEXP
         
     | 
| 
       69 
69 
     | 
    
         | 
| 
       70 
     | 
    
         
            -
                  controller, action =  
     | 
| 
      
 70 
     | 
    
         
            +
                  controller, action = Rage::Router::Util.path_to_class($1), $2
         
     | 
| 
       71 
71 
     | 
    
         
             
                  run_action_method_name = controller.__register_action(action.to_sym)
         
     | 
| 
       72 
72 
     | 
    
         | 
| 
       73 
73 
     | 
    
         
             
                  meta[:controller] = $1
         
     | 
| 
         @@ -273,22 +273,4 @@ class Rage::Router::Backend 
     | 
|
| 
       273 
273 
     | 
    
         
             
                  end
         
     | 
| 
       274 
274 
     | 
    
         
             
                end
         
     | 
| 
       275 
275 
     | 
    
         
             
              end
         
     | 
| 
       276 
     | 
    
         
            -
             
     | 
| 
       277 
     | 
    
         
            -
              def to_controller_class(str)
         
     | 
| 
       278 
     | 
    
         
            -
                str.capitalize!
         
     | 
| 
       279 
     | 
    
         
            -
                str.gsub!(/([\/_])([a-zA-Z0-9]+)/) do
         
     | 
| 
       280 
     | 
    
         
            -
                  if $1 == "/"
         
     | 
| 
       281 
     | 
    
         
            -
                    "::#{$2.capitalize}"
         
     | 
| 
       282 
     | 
    
         
            -
                  else
         
     | 
| 
       283 
     | 
    
         
            -
                    $2.capitalize
         
     | 
| 
       284 
     | 
    
         
            -
                  end
         
     | 
| 
       285 
     | 
    
         
            -
                end
         
     | 
| 
       286 
     | 
    
         
            -
             
     | 
| 
       287 
     | 
    
         
            -
                klass = "#{str}Controller"
         
     | 
| 
       288 
     | 
    
         
            -
                if Object.const_defined?(klass)
         
     | 
| 
       289 
     | 
    
         
            -
                  Object.const_get(klass)
         
     | 
| 
       290 
     | 
    
         
            -
                else
         
     | 
| 
       291 
     | 
    
         
            -
                  raise Rage::Errors::RouterError, "Routing error: could not find the #{klass} class"
         
     | 
| 
       292 
     | 
    
         
            -
                end
         
     | 
| 
       293 
     | 
    
         
            -
              end
         
     | 
| 
       294 
276 
     | 
    
         
             
            end
         
     | 
| 
         @@ -48,10 +48,11 @@ class Rage::Router::HandlerStorage 
     | 
|
| 
       48 
48 
     | 
    
         
             
              private
         
     | 
| 
       49 
49 
     | 
    
         | 
| 
       50 
50 
     | 
    
         
             
              def compile_create_params_object(param_keys, defaults, meta)
         
     | 
| 
       51 
     | 
    
         
            -
                lines = [
         
     | 
| 
       52 
     | 
    
         
            -
                  ":controller => '#{meta[:controller]}'.freeze",
         
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
      
 51 
     | 
    
         
            +
                lines = if meta[:controller]
         
     | 
| 
      
 52 
     | 
    
         
            +
                  [":controller => '#{meta[:controller]}'.freeze", ":action => '#{meta[:action]}'.freeze"]
         
     | 
| 
      
 53 
     | 
    
         
            +
                else
         
     | 
| 
      
 54 
     | 
    
         
            +
                  []
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
       55 
56 
     | 
    
         | 
| 
       56 
57 
     | 
    
         
             
                param_keys.each_with_index do |key, i|
         
     | 
| 
       57 
58 
     | 
    
         
             
                  lines << ":#{key} => param_values[#{i}]"
         
     | 
| 
         @@ -0,0 +1,33 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class Rage::Router::Util
         
     | 
| 
      
 2 
     | 
    
         
            +
              class << self
         
     | 
| 
      
 3 
     | 
    
         
            +
                # converts controller name in a path form into a class
         
     | 
| 
      
 4 
     | 
    
         
            +
                # `api/v1/users` => `Api::V1::UsersController`
         
     | 
| 
      
 5 
     | 
    
         
            +
                def path_to_class(str)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  str = str.capitalize
         
     | 
| 
      
 7 
     | 
    
         
            +
                  str.gsub!(/([\/_])([a-zA-Z0-9]+)/) do
         
     | 
| 
      
 8 
     | 
    
         
            +
                    if $1 == "/"
         
     | 
| 
      
 9 
     | 
    
         
            +
                      "::#{$2.capitalize}"
         
     | 
| 
      
 10 
     | 
    
         
            +
                    else
         
     | 
| 
      
 11 
     | 
    
         
            +
                      $2.capitalize
         
     | 
| 
      
 12 
     | 
    
         
            +
                    end
         
     | 
| 
      
 13 
     | 
    
         
            +
                  end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                  klass = "#{str}Controller"
         
     | 
| 
      
 16 
     | 
    
         
            +
                  if Object.const_defined?(klass)
         
     | 
| 
      
 17 
     | 
    
         
            +
                    Object.const_get(klass)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  else
         
     | 
| 
      
 19 
     | 
    
         
            +
                    raise Rage::Errors::RouterError, "Routing error: could not find the #{klass} class"
         
     | 
| 
      
 20 
     | 
    
         
            +
                  end
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                @@names_map = {}
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
                # converts controller name in a path form into a string representation of a class
         
     | 
| 
      
 26 
     | 
    
         
            +
                # `api/v1/users` => `"Api::V1::UsersController"`
         
     | 
| 
      
 27 
     | 
    
         
            +
                def path_to_name(str)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @@names_map[str] || begin
         
     | 
| 
      
 29 
     | 
    
         
            +
                    @@names_map[str] = path_to_class(str).name
         
     | 
| 
      
 30 
     | 
    
         
            +
                  end
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
              end
         
     | 
| 
      
 33 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rage/rspec.rb
    ADDED
    
    | 
         @@ -0,0 +1,178 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require "rack/test"
         
     | 
| 
      
 4 
     | 
    
         
            +
            require "json"
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
            # set up environment
         
     | 
| 
      
 7 
     | 
    
         
            +
            ENV["RAGE_ENV"] ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "test"
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
            # load the app
         
     | 
| 
      
 10 
     | 
    
         
            +
            require "bundler/setup"
         
     | 
| 
      
 11 
     | 
    
         
            +
            require "rage"
         
     | 
| 
      
 12 
     | 
    
         
            +
            require_relative "#{Rage.root}/config/application"
         
     | 
| 
      
 13 
     | 
    
         
            +
             
     | 
| 
      
 14 
     | 
    
         
            +
            # verify the environment
         
     | 
| 
      
 15 
     | 
    
         
            +
            abort("The test suite is running in #{Rage.env} mode instead of 'test'!") unless Rage.env.test?
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
            # mock fiber methods as RSpec tests don't run concurrently
         
     | 
| 
      
 18 
     | 
    
         
            +
            class Fiber
         
     | 
| 
      
 19 
     | 
    
         
            +
              def self.schedule(&block)
         
     | 
| 
      
 20 
     | 
    
         
            +
                fiber = Fiber.new(blocking: true) do
         
     | 
| 
      
 21 
     | 
    
         
            +
                  Fiber.current.__set_id
         
     | 
| 
      
 22 
     | 
    
         
            +
                  Fiber.current.__set_result(block.call)
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                fiber.resume
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                fiber
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
              def self.await(fibers)
         
     | 
| 
      
 30 
     | 
    
         
            +
                Array(fibers).map(&:__get_result)
         
     | 
| 
      
 31 
     | 
    
         
            +
              end
         
     | 
| 
      
 32 
     | 
    
         
            +
            end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
            # define request helpers
         
     | 
| 
      
 35 
     | 
    
         
            +
            module RageRequestHelpers
         
     | 
| 
      
 36 
     | 
    
         
            +
              include Rack::Test::Methods
         
     | 
| 
      
 37 
     | 
    
         
            +
             
     | 
| 
      
 38 
     | 
    
         
            +
              alias_method :response, :last_response
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              APP = Rack::Builder.parse_file("#{Rage.root}/config.ru").yield_self do |app|
         
     | 
| 
      
 41 
     | 
    
         
            +
                app.is_a?(Array) ? app[0] : app
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              def app
         
     | 
| 
      
 45 
     | 
    
         
            +
                APP
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              %w(get options head).each do |method_name|
         
     | 
| 
      
 49 
     | 
    
         
            +
                class_eval <<~RUBY, __FILE__, __LINE__ + 1
         
     | 
| 
      
 50 
     | 
    
         
            +
                  def #{method_name}(path, params: {}, headers: {})
         
     | 
| 
      
 51 
     | 
    
         
            +
                    request("#{method_name.upcase}", path, params: params, headers: headers)
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                RUBY
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              %w(post put patch delete).each do |method_name|
         
     | 
| 
      
 57 
     | 
    
         
            +
                class_eval <<~RUBY, __FILE__, __LINE__ + 1
         
     | 
| 
      
 58 
     | 
    
         
            +
                  def #{method_name}(path, params: {}, headers: {}, as: nil)
         
     | 
| 
      
 59 
     | 
    
         
            +
                    if as == :json
         
     | 
| 
      
 60 
     | 
    
         
            +
                      params = params.to_json
         
     | 
| 
      
 61 
     | 
    
         
            +
                      headers["content-type"] = "application/json"
         
     | 
| 
      
 62 
     | 
    
         
            +
                    end
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                    request("#{method_name.upcase}", path, params: params, headers: headers.merge("IODINE_HAS_BODY" => !params.empty?))
         
     | 
| 
      
 65 
     | 
    
         
            +
                  end
         
     | 
| 
      
 66 
     | 
    
         
            +
                RUBY
         
     | 
| 
      
 67 
     | 
    
         
            +
              end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
              def request(method, path, params: {}, headers: {})
         
     | 
| 
      
 70 
     | 
    
         
            +
                if headers.any?
         
     | 
| 
      
 71 
     | 
    
         
            +
                  headers = headers.transform_keys do |k|
         
     | 
| 
      
 72 
     | 
    
         
            +
                    if k.downcase == "content-type"
         
     | 
| 
      
 73 
     | 
    
         
            +
                      "CONTENT_TYPE"
         
     | 
| 
      
 74 
     | 
    
         
            +
                    elsif k.downcase == "content-length"
         
     | 
| 
      
 75 
     | 
    
         
            +
                      "CONTENT_LENGTH"
         
     | 
| 
      
 76 
     | 
    
         
            +
                    elsif k.upcase == k
         
     | 
| 
      
 77 
     | 
    
         
            +
                      k
         
     | 
| 
      
 78 
     | 
    
         
            +
                    else
         
     | 
| 
      
 79 
     | 
    
         
            +
                      "HTTP_#{k.tr("-", "_").upcase! || k}"
         
     | 
| 
      
 80 
     | 
    
         
            +
                    end
         
     | 
| 
      
 81 
     | 
    
         
            +
                  end
         
     | 
| 
      
 82 
     | 
    
         
            +
                end
         
     | 
| 
      
 83 
     | 
    
         
            +
             
     | 
| 
      
 84 
     | 
    
         
            +
                custom_request(method, path, params, headers)
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              def host!(host)
         
     | 
| 
      
 88 
     | 
    
         
            +
                @__host = host
         
     | 
| 
      
 89 
     | 
    
         
            +
              end
         
     | 
| 
      
 90 
     | 
    
         
            +
             
     | 
| 
      
 91 
     | 
    
         
            +
              def default_host
         
     | 
| 
      
 92 
     | 
    
         
            +
                @__host || "example.org"
         
     | 
| 
      
 93 
     | 
    
         
            +
              end
         
     | 
| 
      
 94 
     | 
    
         
            +
            end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
            # include request helpers
         
     | 
| 
      
 97 
     | 
    
         
            +
            RSpec.configure do |config|
         
     | 
| 
      
 98 
     | 
    
         
            +
              config.include(RageRequestHelpers, type: :request)
         
     | 
| 
      
 99 
     | 
    
         
            +
            end
         
     | 
| 
      
 100 
     | 
    
         
            +
             
     | 
| 
      
 101 
     | 
    
         
            +
            # patch MockResponse class
         
     | 
| 
      
 102 
     | 
    
         
            +
            class Rack::MockResponse
         
     | 
| 
      
 103 
     | 
    
         
            +
              def parsed_body
         
     | 
| 
      
 104 
     | 
    
         
            +
                if headers["content-type"].start_with?("application/json")
         
     | 
| 
      
 105 
     | 
    
         
            +
                  JSON.parse(body)
         
     | 
| 
      
 106 
     | 
    
         
            +
                else
         
     | 
| 
      
 107 
     | 
    
         
            +
                  body
         
     | 
| 
      
 108 
     | 
    
         
            +
                end
         
     | 
| 
      
 109 
     | 
    
         
            +
              end
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
              def code
         
     | 
| 
      
 112 
     | 
    
         
            +
                status.to_s
         
     | 
| 
      
 113 
     | 
    
         
            +
              end
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
              alias_method :response_code, :status
         
     | 
| 
      
 116 
     | 
    
         
            +
            end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
            # define http status matcher
         
     | 
| 
      
 119 
     | 
    
         
            +
            RSpec::Matchers.matcher :have_http_status do |expected|
         
     | 
| 
      
 120 
     | 
    
         
            +
              codes = Rack::Utils::SYMBOL_TO_STATUS_CODE
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
              failure_message do |response|
         
     | 
| 
      
 123 
     | 
    
         
            +
                actual = response.status
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                if expected.is_a?(Integer)
         
     | 
| 
      
 126 
     | 
    
         
            +
                  "expected the response to have status code #{expected} but it was #{actual}"
         
     | 
| 
      
 127 
     | 
    
         
            +
                elsif expected == :success
         
     | 
| 
      
 128 
     | 
    
         
            +
                  "expected the response to have a success status code (2xx) but it was #{actual}"
         
     | 
| 
      
 129 
     | 
    
         
            +
                elsif expected == :error
         
     | 
| 
      
 130 
     | 
    
         
            +
                  "expected the response to have an error status code (5xx) but it was #{actual}"
         
     | 
| 
      
 131 
     | 
    
         
            +
                elsif expected == :missing
         
     | 
| 
      
 132 
     | 
    
         
            +
                  "expected the response to have a missing status code (404) but it was #{actual}"
         
     | 
| 
      
 133 
     | 
    
         
            +
                else
         
     | 
| 
      
 134 
     | 
    
         
            +
                  "expected the response to have status code :#{expected} (#{codes[expected]}) but it was :#{codes.key(actual)} (#{actual})"
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
              end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
              failure_message_when_negated do |response|
         
     | 
| 
      
 139 
     | 
    
         
            +
                actual = response.status
         
     | 
| 
      
 140 
     | 
    
         
            +
             
     | 
| 
      
 141 
     | 
    
         
            +
                if expected.is_a?(Integer)
         
     | 
| 
      
 142 
     | 
    
         
            +
                  "expected the response not to have status code #{expected} but it was #{actual}"
         
     | 
| 
      
 143 
     | 
    
         
            +
                elsif expected == :success
         
     | 
| 
      
 144 
     | 
    
         
            +
                  "expected the response not to have a success status code (2xx) but it was #{actual}"
         
     | 
| 
      
 145 
     | 
    
         
            +
                elsif expected == :error
         
     | 
| 
      
 146 
     | 
    
         
            +
                  "expected the response not to have an error status code (5xx) but it was #{actual}"
         
     | 
| 
      
 147 
     | 
    
         
            +
                elsif expected == :missing
         
     | 
| 
      
 148 
     | 
    
         
            +
                  "expected the response not to have a missing status code (404) but it was #{actual}"
         
     | 
| 
      
 149 
     | 
    
         
            +
                else
         
     | 
| 
      
 150 
     | 
    
         
            +
                  "expected the response not to have status code :#{expected} (#{codes[expected]}) but it was :#{codes.key(actual)} (#{actual})"
         
     | 
| 
      
 151 
     | 
    
         
            +
                end
         
     | 
| 
      
 152 
     | 
    
         
            +
              end
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
              match do |response|
         
     | 
| 
      
 155 
     | 
    
         
            +
                actual = response.status
         
     | 
| 
      
 156 
     | 
    
         
            +
             
     | 
| 
      
 157 
     | 
    
         
            +
                case expected
         
     | 
| 
      
 158 
     | 
    
         
            +
                when :success
         
     | 
| 
      
 159 
     | 
    
         
            +
                  actual >= 200 && actual < 300
         
     | 
| 
      
 160 
     | 
    
         
            +
                when :error
         
     | 
| 
      
 161 
     | 
    
         
            +
                  actual >= 500
         
     | 
| 
      
 162 
     | 
    
         
            +
                when :missing
         
     | 
| 
      
 163 
     | 
    
         
            +
                  actual == 404
         
     | 
| 
      
 164 
     | 
    
         
            +
                when Symbol
         
     | 
| 
      
 165 
     | 
    
         
            +
                  actual == codes.fetch(expected)
         
     | 
| 
      
 166 
     | 
    
         
            +
                else
         
     | 
| 
      
 167 
     | 
    
         
            +
                  actual == expected
         
     | 
| 
      
 168 
     | 
    
         
            +
                end
         
     | 
| 
      
 169 
     | 
    
         
            +
              end
         
     | 
| 
      
 170 
     | 
    
         
            +
            end
         
     | 
| 
      
 171 
     | 
    
         
            +
             
     | 
| 
      
 172 
     | 
    
         
            +
            if defined? RSpec::Rails::Matchers
         
     | 
| 
      
 173 
     | 
    
         
            +
              module RSpec::Rails::Matchers
         
     | 
| 
      
 174 
     | 
    
         
            +
                def have_http_status(_)
         
     | 
| 
      
 175 
     | 
    
         
            +
                  super
         
     | 
| 
      
 176 
     | 
    
         
            +
                end
         
     | 
| 
      
 177 
     | 
    
         
            +
              end
         
     | 
| 
      
 178 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/rage/version.rb
    CHANGED
    
    
    
        data/rage.gemspec
    CHANGED
    
    
    
        metadata
    CHANGED
    
    | 
         @@ -1,14 +1,14 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: rage-rb
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version:  
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.1.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: ruby
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Roman Samoilov
         
     | 
| 
       8 
8 
     | 
    
         
             
            autorequire:
         
     | 
| 
       9 
9 
     | 
    
         
             
            bindir: exe
         
     | 
| 
       10 
10 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       11 
     | 
    
         
            -
            date: 2024- 
     | 
| 
      
 11 
     | 
    
         
            +
            date: 2024-03-25 00:00:00.000000000 Z
         
     | 
| 
       12 
12 
     | 
    
         
             
            dependencies:
         
     | 
| 
       13 
13 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       14 
14 
     | 
    
         
             
              name: thor
         
     | 
| 
         @@ -66,6 +66,20 @@ dependencies: 
     | 
|
| 
       66 
66 
     | 
    
         
             
                - - "~>"
         
     | 
| 
       67 
67 
     | 
    
         
             
                  - !ruby/object:Gem::Version
         
     | 
| 
       68 
68 
     | 
    
         
             
                    version: '2.6'
         
     | 
| 
      
 69 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency
         
     | 
| 
      
 70 
     | 
    
         
            +
              name: rack-test
         
     | 
| 
      
 71 
     | 
    
         
            +
              requirement: !ruby/object:Gem::Requirement
         
     | 
| 
      
 72 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 73 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 74 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 75 
     | 
    
         
            +
                    version: '2.1'
         
     | 
| 
      
 76 
     | 
    
         
            +
              type: :runtime
         
     | 
| 
      
 77 
     | 
    
         
            +
              prerelease: false
         
     | 
| 
      
 78 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement
         
     | 
| 
      
 79 
     | 
    
         
            +
                requirements:
         
     | 
| 
      
 80 
     | 
    
         
            +
                - - "~>"
         
     | 
| 
      
 81 
     | 
    
         
            +
                  - !ruby/object:Gem::Version
         
     | 
| 
      
 82 
     | 
    
         
            +
                    version: '2.1'
         
     | 
| 
       69 
83 
     | 
    
         
             
            description:
         
     | 
| 
       70 
84 
     | 
    
         
             
            email:
         
     | 
| 
       71 
85 
     | 
    
         
             
            - rsamoi@icloud.com
         
     | 
| 
         @@ -112,6 +126,8 @@ files: 
     | 
|
| 
       112 
126 
     | 
    
         
             
            - lib/rage/router/handler_storage.rb
         
     | 
| 
       113 
127 
     | 
    
         
             
            - lib/rage/router/node.rb
         
     | 
| 
       114 
128 
     | 
    
         
             
            - lib/rage/router/strategies/host.rb
         
     | 
| 
      
 129 
     | 
    
         
            +
            - lib/rage/router/util.rb
         
     | 
| 
      
 130 
     | 
    
         
            +
            - lib/rage/rspec.rb
         
     | 
| 
       115 
131 
     | 
    
         
             
            - lib/rage/setup.rb
         
     | 
| 
       116 
132 
     | 
    
         
             
            - lib/rage/sidekiq_session.rb
         
     | 
| 
       117 
133 
     | 
    
         
             
            - lib/rage/templates/Gemfile
         
     |