ruby_routes 0.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: d9f05718781115fc2cc1d8851ddea52d0af3fa46f7a937335398b518ea0116e9
4
+ data.tar.gz: 12b51d5138dbbdec4b46d66f20489e167a721f67f58ed80652add4a2e9aa606b
5
+ SHA512:
6
+ metadata.gz: 32c7fab4ab209b6c2c1a5d50531d9f248f8413fb1409c6ebec9173eb538149c2a5f358e6bad85c0ed6993ed6b91a62a46f8d3551881ffaba4551b80df79c13a2
7
+ data.tar.gz: dd96665fc956cf1a76444e75287c2661b3629c0d0cc5546f44ed464533a50b8ff830e354d1caa83782c53855446f6e0043039ae09489fef8b5f33eeaa73329c0
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Router Gem Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,305 @@
1
+ # Ruby Routes Gem
2
+
3
+ A lightweight, flexible routing system for Ruby that provides a Rails-like DSL for defining and matching HTTP routes.
4
+
5
+ ## Features
6
+
7
+ - **Rails-like DSL**: Familiar syntax for defining routes
8
+ - **HTTP Method Support**: GET, POST, PUT, PATCH, DELETE, and custom methods
9
+ - **RESTful Resources**: Automatic generation of RESTful routes
10
+ - **Nested Routes**: Support for nested resources and namespaces
11
+ - **Route Constraints**: Add constraints to routes (regex, etc.)
12
+ - **Named Routes**: Generate URLs from route names
13
+ - **Path Generation**: Build URLs with parameters
14
+ - **Scope Support**: Group routes with common options
15
+ - **Concerns**: Reusable route groups
16
+ - **Lightweight**: Minimal dependencies, fast performance
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'ruby_routes'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ ```bash
29
+ bundle install
30
+ ```
31
+
32
+ Or install it yourself as:
33
+
34
+ ```bash
35
+ gem install ruby_routes
36
+ ```
37
+
38
+ ## Basic Usage
39
+
40
+ ### Simple Routes
41
+
42
+ ```ruby
43
+ require 'ruby_routes'
44
+
45
+ router = RubyRoutes.draw do
46
+ get '/', to: 'home#index'
47
+ get '/about', to: 'pages#about'
48
+ post '/users', to: 'users#create'
49
+ put '/users/:id', to: 'users#update'
50
+ delete '/users/:id', to: 'users#destroy'
51
+ end
52
+ ```
53
+
54
+ ### RESTful Resources
55
+
56
+ ```ruby
57
+ router = RubyRoutes.draw do
58
+ resources :users
59
+ resources :posts
60
+ resources :comments
61
+ end
62
+ ```
63
+
64
+ This creates the following routes:
65
+ - `GET /users` → `users#index`
66
+ - `GET /users/new` → `users#new`
67
+ - `POST /users` → `users#create`
68
+ - `GET /users/:id` → `users#show`
69
+ - `GET /users/:id/edit` → `users#edit`
70
+ - `PUT /users/:id` → `users#update`
71
+ - `PATCH /users/:id` → `users#update`
72
+ - `DELETE /users/:id` → `users#destroy`
73
+
74
+ ### Named Routes
75
+
76
+ ```ruby
77
+ router = RubyRoutes.draw do
78
+ get '/users', as: :users, to: 'users#index'
79
+ get '/users/:id', as: :user, to: 'users#show'
80
+ post '/users', as: :create_user, to: 'users#create'
81
+ end
82
+
83
+ # Generate paths
84
+ router.route_set.generate_path(:users) # => "/users"
85
+ router.route_set.generate_path(:user, id: '123') # => "/users/123"
86
+ ```
87
+
88
+ ### Namespaces
89
+
90
+ ```ruby
91
+ router = RubyRoutes.draw do
92
+ namespace :admin do
93
+ resources :users
94
+ resources :posts
95
+ end
96
+ end
97
+
98
+ # Creates routes like:
99
+ # GET /admin/users → admin/users#index
100
+ # GET /admin/users/:id → admin/users#show
101
+ # etc.
102
+ ```
103
+
104
+ ### Nested Resources
105
+
106
+ ```ruby
107
+ router = RubyRoutes.draw do
108
+ resources :categories do
109
+ resources :products
110
+ end
111
+ end
112
+
113
+ # Creates routes like:
114
+ # GET /categories/:category_id/products → products#index
115
+ # GET /categories/:category_id/products/:id → products#show
116
+ # etc.
117
+ ```
118
+
119
+ ### Scopes and Constraints
120
+
121
+ ```ruby
122
+ router = RubyRoutes.draw do
123
+ scope constraints: { id: /\d+/ } do
124
+ get '/users/:id', to: 'users#show'
125
+ end
126
+
127
+ scope defaults: { format: 'html' } do
128
+ get '/posts', to: 'posts#index'
129
+ end
130
+ end
131
+ ```
132
+
133
+ ### Concerns
134
+
135
+ ```ruby
136
+ router = RubyRoutes.draw do
137
+ concern :commentable do
138
+ resources :comments
139
+ end
140
+
141
+ resources :posts do
142
+ concerns :commentable
143
+ end
144
+
145
+ resources :articles do
146
+ concerns :commentable
147
+ end
148
+ end
149
+ ```
150
+
151
+ ### Root Route
152
+
153
+ ```ruby
154
+ router = RubyRoutes.draw do
155
+ root to: 'home#index'
156
+ end
157
+ ```
158
+
159
+ ## Route Matching
160
+
161
+ ```ruby
162
+ router = RubyRoutes.draw do
163
+ get '/users/:id', to: 'users#show'
164
+ post '/users', to: 'users#create'
165
+ end
166
+
167
+ # Match a request
168
+ result = router.route_set.match('GET', '/users/123')
169
+ if result
170
+ puts "Controller: #{result[:controller]}"
171
+ puts "Action: #{result[:action]}"
172
+ puts "Params: #{result[:params]}"
173
+ # => Controller: users
174
+ # => Action: show
175
+ # => Params: {"id"=>"123"}
176
+ end
177
+ ```
178
+
179
+ ## Path Generation
180
+
181
+ ```ruby
182
+ router = RubyRoutes.draw do
183
+ get '/users/:id', as: :user, to: 'users#show'
184
+ get '/posts/:id/comments/:comment_id', as: :post_comment, to: 'comments#show'
185
+ end
186
+
187
+ # Generate paths
188
+ router.route_set.generate_path(:user, id: '123')
189
+ # => "/users/123"
190
+
191
+ router.route_set.generate_path(:post_comment, id: '456', comment_id: '789')
192
+ # => "/posts/456/comments/789"
193
+ ```
194
+
195
+ ## Integration with Rack
196
+
197
+ ```ruby
198
+ require 'rack'
199
+ require 'ruby_routes'
200
+
201
+ # Define routes
202
+ router = RubyRoutes.draw do
203
+ get '/', to: 'home#index'
204
+ get '/users', to: 'users#index'
205
+ get '/users/:id', to: 'users#show'
206
+ end
207
+
208
+ # Create Rack app
209
+ class RubyRoutesApp
210
+ def initialize(router)
211
+ @router = router
212
+ end
213
+
214
+ def call(env)
215
+ request_method = env['REQUEST_METHOD']
216
+ request_path = env['PATH_INFO']
217
+
218
+ route_info = @router.route_set.match(request_method, request_path)
219
+
220
+ if route_info
221
+ # Handle the request
222
+ controller = route_info[:controller]
223
+ action = route_info[:action]
224
+ params = route_info[:params]
225
+
226
+ # Your controller logic here
227
+ [200, {'Content-Type' => 'text/html'}, ["Hello from #{controller}##{action}"]]
228
+ else
229
+ [404, {'Content-Type' => 'text/html'}, ['Not Found']]
230
+ end
231
+ end
232
+ end
233
+
234
+ # Run the app
235
+ app = RubyRoutesApp.new(router)
236
+ Rack::Handler::WEBrick.run app, Port: 9292
237
+ ```
238
+
239
+ ## API Reference
240
+
241
+ ### RubyRoutes.draw(&block)
242
+
243
+ Creates a new router instance and yields to the block for route definition.
244
+
245
+ ### HTTP Methods
246
+
247
+ - `get(path, options = {})`
248
+ - `post(path, options = {})`
249
+ - `put(path, options = {})`
250
+ - `patch(path, options = {})`
251
+ - `delete(path, options = {})`
252
+ - `match(path, options = {})`
253
+
254
+ ### Resource Methods
255
+
256
+ - `resources(name, options = {})` - Creates RESTful routes for a collection
257
+ - `resource(name, options = {})` - Creates RESTful routes for a singular resource
258
+
259
+ ### Options
260
+
261
+ - `to: 'controller#action'` - Specifies controller and action
262
+ - `controller: 'name'` - Specifies controller name
263
+ - `action: 'name'` - Specifies action name
264
+ - `as: :name` - Names the route for path generation
265
+ - `via: :method` - Specifies HTTP method(s)
266
+ - `constraints: {}` - Adds route constraints
267
+ - `defaults: {}` - Sets default parameters
268
+
269
+ ### RouteSet Methods
270
+
271
+ - `match(method, path)` - Matches a request to a route
272
+ - `generate_path(name, params = {})` - Generates path from named route
273
+ - `find_route(method, path)` - Finds a specific route
274
+ - `find_named_route(name)` - Finds a named route
275
+
276
+ ## Examples
277
+
278
+ See the `examples/` directory for more detailed examples:
279
+
280
+ - `examples/basic_usage.rb` - Basic routing examples
281
+ - `examples/rack_integration.rb` - Full Rack application example
282
+
283
+ ## Testing
284
+
285
+ Run the test suite:
286
+
287
+ ```bash
288
+ bundle exec rspec
289
+ ```
290
+
291
+ ## Contributing
292
+
293
+ 1. Fork the repository
294
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
295
+ 3. Commit your changes (`git commit -am 'Add some amazing feature'`)
296
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
297
+ 5. Create a new Pull Request
298
+
299
+ ## License
300
+
301
+ This gem is available as open source under the terms of the [MIT License](LICENSE).
302
+
303
+ ## Acknowledgments
304
+
305
+ This gem was inspired by Rails routing and aims to provide a lightweight alternative for Ruby applications that need flexible routing without the full Rails framework.
@@ -0,0 +1,96 @@
1
+ module RubyRoutes
2
+ class Route
3
+ attr_reader :path, :methods, :controller, :action, :name, :constraints, :defaults
4
+
5
+ def initialize(path, options = {})
6
+ @path = normalize_path(path)
7
+ @methods = Array(options[:via] || :get).map(&:to_s).map(&:upcase)
8
+ @controller = extract_controller(options)
9
+ @action = options[:action] || extract_action(options[:to])
10
+ @name = options[:as]
11
+ @constraints = options[:constraints] || {}
12
+ @defaults = options[:defaults] || {}
13
+
14
+ validate_route!
15
+ end
16
+
17
+ def match?(request_method, request_path)
18
+ return false unless methods.include?(request_method.to_s.upcase)
19
+
20
+ path_params = extract_path_params(request_path)
21
+ path_params != nil
22
+ end
23
+
24
+ def extract_params(request_path)
25
+ path_params = extract_path_params(request_path)
26
+ return {} unless path_params
27
+
28
+ params = path_params.dup
29
+ # Convert symbol keys to string keys for consistency
30
+ string_defaults = defaults.transform_keys(&:to_s)
31
+ params.merge!(string_defaults)
32
+ params
33
+ end
34
+
35
+ def named?
36
+ !name.nil?
37
+ end
38
+
39
+ def resource?
40
+ path.match?(/\/:id$/) || path.match?(/\/:id\./)
41
+ end
42
+
43
+ def collection?
44
+ !resource?
45
+ end
46
+
47
+ private
48
+
49
+ def normalize_path(path)
50
+ path = "/#{path}" unless path.start_with?('/')
51
+ # Remove trailing slash unless it's the root path
52
+ path = path.chomp('/') unless path == '/'
53
+ path
54
+ end
55
+
56
+ def extract_controller(options)
57
+ if options[:to]
58
+ options[:to].to_s.split('#').first
59
+ else
60
+ options[:controller]
61
+ end
62
+ end
63
+
64
+ def extract_action(to)
65
+ return nil unless to
66
+ to.to_s.split('#').last
67
+ end
68
+
69
+ def extract_path_params(request_path)
70
+ route_parts = path.split('/')
71
+ request_parts = request_path.split('/')
72
+
73
+ return nil if route_parts.length != request_parts.length
74
+
75
+ params = {}
76
+ route_parts.each_with_index do |route_part, index|
77
+ request_part = request_parts[index]
78
+
79
+ if route_part.start_with?(':')
80
+ param_name = route_part[1..-1]
81
+ params[param_name] = request_part
82
+ elsif route_part != request_part
83
+ return nil
84
+ end
85
+ end
86
+
87
+ params
88
+ end
89
+
90
+ def validate_route!
91
+ raise InvalidRoute, "Controller is required" if controller.nil?
92
+ raise InvalidRoute, "Action is required" if action.nil?
93
+ raise InvalidRoute, "Invalid HTTP method: #{methods}" if methods.empty?
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,80 @@
1
+ module RubyRoutes
2
+ class RouteSet
3
+ attr_reader :routes
4
+
5
+ def initialize
6
+ @routes = []
7
+ @named_routes = {}
8
+ end
9
+
10
+ def add_route(route)
11
+ @routes << route
12
+ @named_routes[route.name] = route if route.named?
13
+ route
14
+ end
15
+
16
+ def find_route(request_method, request_path)
17
+ @routes.find { |route| route.match?(request_method, request_path) }
18
+ end
19
+
20
+ def find_named_route(name)
21
+ @named_routes[name] or raise RouteNotFound, "No route named '#{name}'"
22
+ end
23
+
24
+ def match(request_method, request_path)
25
+ route = find_route(request_method, request_path)
26
+ return nil unless route
27
+
28
+ {
29
+ route: route,
30
+ params: route.extract_params(request_path),
31
+ controller: route.controller,
32
+ action: route.action
33
+ }
34
+ end
35
+
36
+ def recognize_path(path, method = :get)
37
+ match(method, path)
38
+ end
39
+
40
+ def generate_path(name, params = {})
41
+ route = find_named_route(name)
42
+ generate_path_from_route(route, params)
43
+ end
44
+
45
+ def generate_path_from_route(route, params = {})
46
+ path = route.path.dup
47
+
48
+ params.each do |key, value|
49
+ path.gsub!(":#{key}", value.to_s)
50
+ end
51
+
52
+ # Remove any remaining :param placeholders
53
+ path.gsub!(/\/:[^\/]+/, '')
54
+ path.gsub!(/\/$/, '') if path != '/'
55
+
56
+ path
57
+ end
58
+
59
+ def clear!
60
+ @routes.clear
61
+ @named_routes.clear
62
+ end
63
+
64
+ def size
65
+ @routes.size
66
+ end
67
+
68
+ def empty?
69
+ @routes.empty?
70
+ end
71
+
72
+ def each(&block)
73
+ @routes.each(&block)
74
+ end
75
+
76
+ def include?(route)
77
+ @routes.include?(route)
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,205 @@
1
+ module RubyRoutes
2
+ class Router
3
+ attr_reader :route_set
4
+
5
+ def initialize(&block)
6
+ @route_set = RouteSet.new
7
+ @scope_stack = []
8
+ instance_eval(&block) if block_given?
9
+ end
10
+
11
+ # Basic route definition
12
+ def get(path, options = {})
13
+ add_route(path, options.merge(via: :get))
14
+ end
15
+
16
+ def post(path, options = {})
17
+ add_route(path, options.merge(via: :post))
18
+ end
19
+
20
+ def put(path, options = {})
21
+ add_route(path, options.merge(via: :put))
22
+ end
23
+
24
+ def patch(path, options = {})
25
+ add_route(path, options.merge(via: :patch))
26
+ end
27
+
28
+ def delete(path, options = {})
29
+ add_route(path, options.merge(via: :delete))
30
+ end
31
+
32
+ def match(path, options = {})
33
+ add_route(path, options)
34
+ end
35
+
36
+ # Resources routing (Rails-like)
37
+ def resources(name, options = {}, &block)
38
+ singular = name.to_s.singularize
39
+ plural = name.to_s.pluralize
40
+
41
+ # Collection routes
42
+ get "/#{plural}", options.merge(to: "#{plural}#index")
43
+ get "/#{plural}/new", options.merge(to: "#{plural}#new")
44
+ post "/#{plural}", options.merge(to: "#{plural}#create")
45
+
46
+ # Member routes
47
+ get "/#{plural}/:id", options.merge(to: "#{plural}#show")
48
+ get "/#{plural}/:id/edit", options.merge(to: "#{plural}#edit")
49
+ put "/#{plural}/:id", options.merge(to: "#{plural}#update")
50
+ patch "/#{plural}/:id", options.merge(to: "#{plural}#update")
51
+ delete "/#{plural}/:id", options.merge(to: "#{plural}#destroy")
52
+
53
+ # Nested resources if specified
54
+ if options[:nested]
55
+ nested_name = options[:nested]
56
+ nested_singular = nested_name.to_s.singularize
57
+ nested_plural = nested_name.to_s.pluralize
58
+
59
+ get "/#{plural}/:id/#{nested_plural}", options.merge(to: "#{nested_plural}#index")
60
+ get "/#{plural}/:id/#{nested_plural}/new", options.merge(to: "#{nested_plural}#new")
61
+ post "/#{plural}/:id/#{nested_plural}", options.merge(to: "#{nested_plural}#create")
62
+ get "/#{plural}/:id/#{nested_plural}/:nested_id", options.merge(to: "#{nested_plural}#show")
63
+ get "/#{plural}/:id/#{nested_plural}/:nested_id/edit", options.merge(to: "#{nested_plural}#edit")
64
+ put "/#{plural}/:id/#{nested_plural}/:nested_id", options.merge(to: "#{nested_plural}#update")
65
+ patch "/#{plural}/:id/#{nested_plural}/:nested_id", options.merge(to: "#{nested_plural}#update")
66
+ delete "/#{plural}/:id/#{nested_plural}/:nested_id", options.merge(to: "#{nested_plural}#update")
67
+ delete "/#{plural}/:id/#{nested_plural}/:nested_id", options.merge(to: "#{nested_plural}#destroy")
68
+ end
69
+
70
+ # Handle concerns if block is given
71
+ if block_given?
72
+ # Push a scope for nested resources
73
+ @scope_stack.push({ path: "/#{plural}/:id" })
74
+ # Execute the block in the context of this router instance
75
+ instance_eval(&block)
76
+ @scope_stack.pop
77
+ end
78
+ end
79
+
80
+ def resource(name, options = {})
81
+ singular = name.to_s.singularize
82
+
83
+ get "/#{singular}", options.merge(to: "#{singular}#show")
84
+ get "/#{singular}/new", options.merge(to: "#{singular}#new")
85
+ post "/#{singular}", options.merge(to: "#{singular}#create")
86
+ get "/#{singular}/edit", options.merge(to: "#{singular}#edit")
87
+ put "/#{singular}", options.merge(to: "#{singular}#update")
88
+ patch "/#{singular}", options.merge(to: "#{singular}#update")
89
+ delete "/#{singular}", options.merge(to: "#{singular}#destroy")
90
+ end
91
+
92
+ # Namespace support
93
+ def namespace(name, options = {}, &block)
94
+ @scope_stack.push({ path: "/#{name}", module: name })
95
+
96
+ if block_given?
97
+ instance_eval(&block)
98
+ end
99
+
100
+ @scope_stack.pop
101
+ end
102
+
103
+ # Scope support
104
+ def scope(options = {}, &block)
105
+ @scope_stack.push(options)
106
+
107
+ if block_given?
108
+ instance_eval(&block)
109
+ end
110
+
111
+ @scope_stack.pop
112
+ end
113
+
114
+ # Root route
115
+ def root(options = {})
116
+ add_route("/", options.merge(via: :get))
117
+ end
118
+
119
+ # Concerns (reusable route groups)
120
+ def concerns(*names, &block)
121
+ names.each do |name|
122
+ concern = @concerns[name]
123
+ raise "Concern '#{name}' not found" unless concern
124
+
125
+ instance_eval(&concern)
126
+ end
127
+
128
+ if block_given?
129
+ instance_eval(&block)
130
+ end
131
+ end
132
+
133
+ def concern(name, &block)
134
+ @concerns ||= {}
135
+ @concerns[name] = block
136
+ end
137
+
138
+ # Route constraints
139
+ def constraints(constraints = {}, &block)
140
+ @scope_stack.push({ constraints: constraints })
141
+
142
+ if block_given?
143
+ instance_eval(&block)
144
+ end
145
+
146
+ @scope_stack.pop
147
+ end
148
+
149
+ # Defaults
150
+ def defaults(defaults = {}, &block)
151
+ @scope_stack.push({ defaults: defaults })
152
+
153
+ if block_given?
154
+ instance_eval(&block)
155
+ end
156
+
157
+ @scope_stack.pop
158
+ end
159
+
160
+ # Mount other applications
161
+ def mount(app, at: nil)
162
+ path = at || "/#{app}"
163
+ add_route("#{path}/*path", to: app, via: :all)
164
+ end
165
+
166
+ private
167
+
168
+ def add_route(path, options = {})
169
+ # Apply current scope
170
+ scoped_options = apply_scope(path, options)
171
+
172
+ # Create and add the route
173
+ route = Route.new(scoped_options[:path], scoped_options)
174
+ @route_set.add_route(route)
175
+ route
176
+ end
177
+
178
+ def apply_scope(path, options)
179
+ scoped_options = options.dup
180
+ scoped_path = path
181
+
182
+ @scope_stack.reverse_each do |scope|
183
+ if scope[:path]
184
+ scoped_path = "#{scope[:path]}#{scoped_path}"
185
+ end
186
+
187
+ if scope[:module] && scoped_options[:to]
188
+ controller = scoped_options[:to].to_s.split('#').first
189
+ scoped_options[:to] = "#{scope[:module]}/#{controller}##{scoped_options[:to].to_s.split('#').last}"
190
+ end
191
+
192
+ if scope[:constraints]
193
+ scoped_options[:constraints] = (scoped_options[:constraints] || {}).merge(scope[:constraints])
194
+ end
195
+
196
+ if scope[:defaults]
197
+ scoped_options[:defaults] = (scoped_options[:defaults] || {}).merge(scope[:defaults])
198
+ end
199
+ end
200
+
201
+ scoped_options[:path] = scoped_path
202
+ scoped_options
203
+ end
204
+ end
205
+ end
@@ -0,0 +1,26 @@
1
+ class String
2
+ def singularize
3
+ case self
4
+ when /ies$/
5
+ self.sub(/ies$/, 'y')
6
+ when /s$/
7
+ self.sub(/s$/, '')
8
+ else
9
+ self
10
+ end
11
+ end
12
+
13
+ def pluralize
14
+ case self
15
+ when /y$/
16
+ self.sub(/y$/, 'ies')
17
+ when /sh$/, /ch$/, /x$/, /z$/
18
+ self + 'es'
19
+ when /s$/
20
+ # Words ending in 's' are already plural
21
+ self
22
+ else
23
+ self + 's'
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,55 @@
1
+ module RubyRoutes
2
+ module UrlHelpers
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ def url_helpers
9
+ @url_helpers ||= Module.new
10
+ end
11
+
12
+ def add_url_helper(name, route)
13
+ url_helpers.define_method(name) do |*args|
14
+ params = args.first || {}
15
+ route_set.generate_path_from_route(route, params)
16
+ end
17
+ end
18
+ end
19
+
20
+ def url_helpers
21
+ self.class.url_helpers
22
+ end
23
+
24
+ def path_to(name, params = {})
25
+ route = route_set.find_named_route(name)
26
+ route_set.generate_path_from_route(route, params)
27
+ end
28
+
29
+ def url_to(name, params = {})
30
+ path = path_to(name, params)
31
+ "http://localhost#{path}"
32
+ end
33
+
34
+ def link_to(name, text, params = {})
35
+ path = path_to(name, params)
36
+ "<a href=\"#{path}\">#{text}</a>"
37
+ end
38
+
39
+ def button_to(name, text, params = {})
40
+ path = path_to(name, params)
41
+ method = params.delete(:method) || :post
42
+
43
+ html = "<form action=\"#{path}\" method=\"#{method}\">"
44
+ html += "<input type=\"hidden\" name=\"_method\" value=\"#{method}\">" if method != :get
45
+ html += "<button type=\"submit\">#{text}</button>"
46
+ html += "</form>"
47
+ html
48
+ end
49
+
50
+ def redirect_to(name, params = {})
51
+ path = path_to(name, params)
52
+ { status: 302, location: path }
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,3 @@
1
+ module RubyRoutes
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,22 @@
1
+ require_relative "ruby_routes/version"
2
+ require_relative "ruby_routes/string_extensions"
3
+ require_relative "ruby_routes/route"
4
+ require_relative "ruby_routes/route_set"
5
+ require_relative "ruby_routes/url_helpers"
6
+ require_relative "ruby_routes/router"
7
+
8
+ module RubyRoutes
9
+ class Error < StandardError; end
10
+ class RouteNotFound < Error; end
11
+ class InvalidRoute < Error; end
12
+
13
+ # Create a new router instance
14
+ def self.new(&block)
15
+ RubyRoutes::Router.new(&block)
16
+ end
17
+
18
+ # Define the routes using a block
19
+ def self.draw(&block)
20
+ RubyRoutes::Router.new(&block)
21
+ end
22
+ end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ruby_routes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Yosef Benny Widyokarsono
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rspec
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '3.12'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '3.12'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rake
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '13.0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '13.0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: simplecov
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '0.22'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '0.22'
54
+ description: A lightweight, flexible routing system that provides a Rails-like DSL
55
+ for defining and matching HTTP routes
56
+ email:
57
+ - yosefbennywidyo@gmail.com
58
+ executables: []
59
+ extensions: []
60
+ extra_rdoc_files: []
61
+ files:
62
+ - LICENSE
63
+ - README.md
64
+ - lib/ruby_routes.rb
65
+ - lib/ruby_routes/route.rb
66
+ - lib/ruby_routes/route_set.rb
67
+ - lib/ruby_routes/router.rb
68
+ - lib/ruby_routes/string_extensions.rb
69
+ - lib/ruby_routes/url_helpers.rb
70
+ - lib/ruby_routes/version.rb
71
+ homepage: https://github.com/yosefbennywidyo/ruby_routes
72
+ licenses:
73
+ - MIT
74
+ metadata: {}
75
+ rdoc_options: []
76
+ require_paths:
77
+ - lib
78
+ required_ruby_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: 3.4.2
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ requirements:
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubygems_version: 3.7.1
90
+ specification_version: 4
91
+ summary: A Rails-like routing system for Ruby
92
+ test_files: []