webmate 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.
- data/.gitignore +19 -0
- data/GUIDE.md +115 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +47 -0
- data/Rakefile +1 -0
- data/bin/webmate +70 -0
- data/lib/webmate/application.rb +138 -0
- data/lib/webmate/config.rb +23 -0
- data/lib/webmate/decorators/base.rb +38 -0
- data/lib/webmate/env.rb +17 -0
- data/lib/webmate/logger.rb +20 -0
- data/lib/webmate/observers/base.rb +24 -0
- data/lib/webmate/presenters/base.rb +69 -0
- data/lib/webmate/presenters/base_presenter.rb +115 -0
- data/lib/webmate/presenters/scoped.rb +30 -0
- data/lib/webmate/responders/abstract.rb +127 -0
- data/lib/webmate/responders/base.rb +21 -0
- data/lib/webmate/responders/callbacks.rb +79 -0
- data/lib/webmate/responders/exceptions.rb +4 -0
- data/lib/webmate/responders/response.rb +36 -0
- data/lib/webmate/responders/templates.rb +65 -0
- data/lib/webmate/route_helpers/route.rb +91 -0
- data/lib/webmate/route_helpers/routes_collection.rb +273 -0
- data/lib/webmate/socket.io/actions/connection.rb +14 -0
- data/lib/webmate/socket.io/actions/handshake.rb +34 -0
- data/lib/webmate/socket.io/packets/ack.rb +5 -0
- data/lib/webmate/socket.io/packets/base.rb +156 -0
- data/lib/webmate/socket.io/packets/connect.rb +5 -0
- data/lib/webmate/socket.io/packets/disconnect.rb +5 -0
- data/lib/webmate/socket.io/packets/error.rb +5 -0
- data/lib/webmate/socket.io/packets/event.rb +5 -0
- data/lib/webmate/socket.io/packets/heartbeat.rb +5 -0
- data/lib/webmate/socket.io/packets/json.rb +5 -0
- data/lib/webmate/socket.io/packets/message.rb +5 -0
- data/lib/webmate/socket.io/packets/noop.rb +5 -0
- data/lib/webmate/support/em_mongoid.rb +53 -0
- data/lib/webmate/version.rb +3 -0
- data/lib/webmate/views/scope.rb +25 -0
- data/lib/webmate/websockets.rb +50 -0
- data/lib/webmate.rb +129 -0
- data/spec/lib/route_helpers/route_spec.rb +41 -0
- data/spec/spec_helper.rb +18 -0
- data/vendor/.DS_Store +0 -0
- data/vendor/assets/.DS_Store +0 -0
- data/vendor/assets/javascripts/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/auth.coffee +63 -0
- data/vendor/assets/javascripts/webmate/backbone_ext/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/backbone_ext/resources.coffee +60 -0
- data/vendor/assets/javascripts/webmate/backbone_ext/sync.coffee +131 -0
- data/vendor/assets/javascripts/webmate/client.coffee +133 -0
- data/vendor/assets/javascripts/webmate/init.coffee +7 -0
- data/vendor/assets/javascripts/webmate/libs/.DS_Store +0 -0
- data/vendor/assets/javascripts/webmate/libs/backbone.js +1572 -0
- data/vendor/assets/javascripts/webmate/libs/benchmark.coffee +27 -0
- data/vendor/assets/javascripts/webmate/libs/icanhaz.js +542 -0
- data/vendor/assets/javascripts/webmate/libs/socket.io.js +3871 -0
- data/vendor/assets/javascripts/webmate/libs/underscore.js +1 -0
- data/vendor/assets/javascripts/webmate.js +10 -0
- data/webmate.gemspec +31 -0
- metadata +290 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
module Webmate
|
2
|
+
class BasePresenter
|
3
|
+
include Webmate::Presenters::Scoped
|
4
|
+
|
5
|
+
attr_accessor :accessor, :resources
|
6
|
+
|
7
|
+
def initialize(resources)
|
8
|
+
raise ArgumentError, "Resources should not be blank" if resources.blank?
|
9
|
+
@resources = resources
|
10
|
+
@errors = []
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_serializable
|
14
|
+
build_serialized default_resource do |object|
|
15
|
+
attributes object.attributes.keys
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def errors
|
20
|
+
@errors
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json(options = {})
|
24
|
+
serialize_resource.to_json
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def serialize_resource
|
30
|
+
if errors.present?
|
31
|
+
serialize_errors
|
32
|
+
else
|
33
|
+
to_serializable
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def serialize_errors
|
38
|
+
build_serialized do
|
39
|
+
namespace 'errors' do
|
40
|
+
errors.each do |error|
|
41
|
+
attribute error.key do
|
42
|
+
error.value
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def resource_by_name(name)
|
50
|
+
@resources.resource(name.to_sym)
|
51
|
+
end
|
52
|
+
|
53
|
+
def default_resource
|
54
|
+
@resources
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
=begin
|
59
|
+
class BasePresenter
|
60
|
+
include Serializers::Scoped
|
61
|
+
|
62
|
+
attr_accessor :accessor, :resources
|
63
|
+
|
64
|
+
def initialize(resources)
|
65
|
+
raise ArgumentError, "Resources should not be blank" if resources.blank?
|
66
|
+
@resources = resources
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_serializable
|
70
|
+
build_serialized default_resource do |object|
|
71
|
+
attributes object.attributes.keys
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def errors
|
76
|
+
resource_by_name(:errors) || []
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_json(options = {})
|
80
|
+
serialize_resource.to_json
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def serialize_resource
|
86
|
+
if errors.present?
|
87
|
+
serialize_errors
|
88
|
+
else
|
89
|
+
to_serializable
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def serialize_errors
|
94
|
+
errors = resource_by_name(:errors)
|
95
|
+
build_serialized do
|
96
|
+
namespace 'errors' do
|
97
|
+
errors.each do |error|
|
98
|
+
attribute error.key do
|
99
|
+
error.value
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def resource_by_name(name)
|
107
|
+
@resources.resource(name.to_sym)
|
108
|
+
end
|
109
|
+
|
110
|
+
def default_resource
|
111
|
+
@resources.resource(:default)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
=end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Webmate::Presenters
|
2
|
+
module Scoped
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
def build_serialized(*args, &block)
|
6
|
+
object = args.first
|
7
|
+
if object.is_a?(Array)
|
8
|
+
object.map do |obj|
|
9
|
+
get_serializable_attributes(obj, &block)
|
10
|
+
end
|
11
|
+
else
|
12
|
+
get_serializable_attributes(object, &block)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def get_serializable_attributes(object, &block)
|
19
|
+
serializer = serializable_class.new(object)
|
20
|
+
instance_exec do
|
21
|
+
serializer.instance_exec(object, &block)
|
22
|
+
end
|
23
|
+
serializer.attrs
|
24
|
+
end
|
25
|
+
|
26
|
+
def serializable_class
|
27
|
+
Webmate::Presenters::Base
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
require 'em-hiredis'
|
2
|
+
require 'webmate/responders/callbacks'
|
3
|
+
|
4
|
+
module Webmate::Responders
|
5
|
+
class Abstract
|
6
|
+
attr_accessor :action, :path, :metadata, :params
|
7
|
+
attr_reader :request
|
8
|
+
|
9
|
+
# request info - current request params
|
10
|
+
def initialize(request_info)
|
11
|
+
@action = request_info[:action]
|
12
|
+
@path = request_info[:path]
|
13
|
+
@metadata = request_info[:metadata]
|
14
|
+
@params = request_info[:params]
|
15
|
+
@request = request_info[:request]
|
16
|
+
|
17
|
+
# publishing actions
|
18
|
+
@users_to_notify = []
|
19
|
+
|
20
|
+
@response = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def action_method
|
24
|
+
action.to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
def params
|
28
|
+
@params.with_indifferent_access
|
29
|
+
end
|
30
|
+
|
31
|
+
def respond
|
32
|
+
process_action
|
33
|
+
rescue Exception => e
|
34
|
+
rescue_with_handler(e)
|
35
|
+
end
|
36
|
+
|
37
|
+
def respond_with(response, options = {})
|
38
|
+
if @response.is_a?(Response)
|
39
|
+
@response = response
|
40
|
+
else
|
41
|
+
default_options = {
|
42
|
+
status: 200,
|
43
|
+
path: path,
|
44
|
+
metadata: metadata,
|
45
|
+
params: params,
|
46
|
+
action: action
|
47
|
+
}
|
48
|
+
options = default_options.merge(options)
|
49
|
+
@response = Response.new(response, options)
|
50
|
+
end
|
51
|
+
# publish changes to users actions
|
52
|
+
async { publish(@response) }
|
53
|
+
|
54
|
+
@response
|
55
|
+
end
|
56
|
+
|
57
|
+
def rescue_with_handler(exception)
|
58
|
+
if handler = handler_for_rescue(exception)
|
59
|
+
handler.arity != 0 ? handler.call(exception) : handler.call
|
60
|
+
else
|
61
|
+
raise(exception)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def process_action
|
66
|
+
raise ActionNotFound unless respond_to?(action_method)
|
67
|
+
respond_with send(action_method)
|
68
|
+
end
|
69
|
+
|
70
|
+
def render_not_found
|
71
|
+
@response = Response.new("Action not Found", status: 200)
|
72
|
+
end
|
73
|
+
|
74
|
+
def async(&block)
|
75
|
+
block.call
|
76
|
+
end
|
77
|
+
|
78
|
+
include ActiveSupport::Rescuable
|
79
|
+
include Webmate::Responders::Callbacks
|
80
|
+
|
81
|
+
# subscriptions
|
82
|
+
def publisher
|
83
|
+
@publisher ||= build_connection
|
84
|
+
end
|
85
|
+
|
86
|
+
def build_connection
|
87
|
+
EM::Hiredis.connect
|
88
|
+
rescue
|
89
|
+
warn("problem with connections to redis")
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
# publish current response to private channels
|
94
|
+
# for users in user_ids
|
95
|
+
#
|
96
|
+
# publish_to(123, 456)
|
97
|
+
def publish_to(*user_ids)
|
98
|
+
@users_to_notify += user_ids
|
99
|
+
end
|
100
|
+
|
101
|
+
# take @users_to_notify
|
102
|
+
# and pass back channels names with listeners
|
103
|
+
#
|
104
|
+
def channels_to_publish
|
105
|
+
@users_to_notify.flatten.each_with_object([]) do |user_id, channels|
|
106
|
+
channel_name = Webmate::Application.get_channel_name_for(user_id)
|
107
|
+
channels << channel_name if channel_active?(channel_name)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def channel_active?(channel_name)
|
112
|
+
return true # in development
|
113
|
+
end
|
114
|
+
|
115
|
+
def publish(response)
|
116
|
+
return if publisher.nil?
|
117
|
+
|
118
|
+
# prepare args for socket.io message packet
|
119
|
+
# this should be prepared data to create socket.io message
|
120
|
+
# without any additional actions
|
121
|
+
packet_data = Webmate::SocketIO::Packets::Message.prepare_packet_data(response)
|
122
|
+
data = Webmate::Application.dump(packet_data)
|
123
|
+
|
124
|
+
channels_to_publish.each {|channel_name| publisher.publish(channel_name, data) }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'webmate/responders/abstract'
|
2
|
+
module Webmate::Responders
|
3
|
+
class Base < Abstract
|
4
|
+
after_filter :_run_observer_callbacks
|
5
|
+
after_filter :_send_websocket_events
|
6
|
+
|
7
|
+
def _send_websocket_events
|
8
|
+
packet = Webmate::SocketIO::Packets::Message.new(@response.packed)
|
9
|
+
|
10
|
+
#async do
|
11
|
+
# #Webmate::Websockets.publish(params[:channel], packet.to_packet)
|
12
|
+
#end
|
13
|
+
end
|
14
|
+
|
15
|
+
def _run_observer_callbacks
|
16
|
+
async do
|
17
|
+
Webmate::Observers::Base.execute_all(action, @response)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Webmate::Responders
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveSupport::Callbacks
|
5
|
+
|
6
|
+
included do
|
7
|
+
define_callbacks :process_action
|
8
|
+
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
9
|
+
alias_method :process_action!, :process_action
|
10
|
+
def process_action
|
11
|
+
run_callbacks(:process_action, action_method) do
|
12
|
+
process_action!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
RUBY_EVAL
|
16
|
+
end
|
17
|
+
|
18
|
+
module ClassMethods
|
19
|
+
def _normalize_callback_options(options)
|
20
|
+
if only = options[:only]
|
21
|
+
only = Array(only).map {|o| "action_method == '#{o}'"}.join(" || ")
|
22
|
+
options[:per_key] = {:if => only}
|
23
|
+
end
|
24
|
+
if except = options[:except]
|
25
|
+
except = Array(except).map {|e| "action_method == '#{e}'"}.join(" || ")
|
26
|
+
options[:per_key] = {:unless => except}
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def _insert_callbacks(callbacks, block)
|
31
|
+
options = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
|
32
|
+
_normalize_callback_options(options)
|
33
|
+
callbacks.push(block) if block
|
34
|
+
callbacks.each do |callback|
|
35
|
+
yield callback, options
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def skip_filter(*names, &blk)
|
40
|
+
skip_before_filter(*names)
|
41
|
+
skip_after_filter(*names)
|
42
|
+
skip_around_filter(*names)
|
43
|
+
end
|
44
|
+
|
45
|
+
[:before, :after, :around].each do |filter|
|
46
|
+
class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
|
47
|
+
# Append a before, after or around filter. See _insert_callbacks
|
48
|
+
# for details on the allowed parameters.
|
49
|
+
def #{filter}_filter(*names, &blk)
|
50
|
+
_insert_callbacks(names, blk) do |name, options|
|
51
|
+
options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after}
|
52
|
+
set_callback(:process_action, :#{filter}, name, options)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# Prepend a before, after or around filter. See _insert_callbacks
|
57
|
+
# for details on the allowed parameters.
|
58
|
+
def prepend_#{filter}_filter(*names, &blk)
|
59
|
+
_insert_callbacks(names, blk) do |name, options|
|
60
|
+
options[:if] = (Array.wrap(options[:if]) << "!halted") if #{filter == :after}
|
61
|
+
set_callback(:process_action, :#{filter}, name, options.merge(:prepend => true))
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# Skip a before, after or around filter. See _insert_callbacks
|
66
|
+
# for details on the allowed parameters.
|
67
|
+
def skip_#{filter}_filter(*names, &blk)
|
68
|
+
_insert_callbacks(names, blk) do |name, options|
|
69
|
+
skip_callback(:process_action, :#{filter}, name, options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# *_filter is the same as append_*_filter
|
74
|
+
alias_method :append_#{filter}_filter, :#{filter}_filter
|
75
|
+
RUBY_EVAL
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'webmate/responders/abstract'
|
2
|
+
module Webmate::Responders
|
3
|
+
class Response
|
4
|
+
attr_accessor :data, :status, :params, :action, :path, :metadata
|
5
|
+
def initialize(data, options = {})
|
6
|
+
@data = data
|
7
|
+
@status = options[:status] || 200
|
8
|
+
@params = options[:params] || {}
|
9
|
+
@action = options[:action] || @params[:action] || ''
|
10
|
+
@metadata = options[:metadata] || {}
|
11
|
+
@path = options[:path] || "/"
|
12
|
+
end
|
13
|
+
|
14
|
+
def json
|
15
|
+
Yajl::Encoder.new.encode(self.packed)
|
16
|
+
end
|
17
|
+
|
18
|
+
def packed
|
19
|
+
{ action: @action, resource: @resource, response: @data, params: safe_params }
|
20
|
+
end
|
21
|
+
|
22
|
+
def safe_params
|
23
|
+
safe_params = {}
|
24
|
+
params.each do |key, value|
|
25
|
+
if value.is_a?(String) || value.is_a?(Integer)
|
26
|
+
safe_params[key] = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
safe_params
|
30
|
+
end
|
31
|
+
|
32
|
+
def rack_format
|
33
|
+
[@status, {}, @data]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Webmate::Responders
|
2
|
+
module Templates
|
3
|
+
|
4
|
+
def slim(template, options = {}, locals = {}, &block)
|
5
|
+
render(:slim, template, options, locals, &block)
|
6
|
+
end
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def template_cache
|
11
|
+
@cache ||= Webmate::Application.template_cache
|
12
|
+
end
|
13
|
+
|
14
|
+
def scope
|
15
|
+
@scope ||= Webmate::Views::Scope.new(self)
|
16
|
+
end
|
17
|
+
|
18
|
+
def render(engine, data, options = {}, locals = {}, &block)
|
19
|
+
views = Webmate::Application.views
|
20
|
+
layouts = Webmate::Application.layouts
|
21
|
+
|
22
|
+
layout = options.delete(:layout) || false
|
23
|
+
|
24
|
+
# compile and render template
|
25
|
+
template = compile_template(engine, data, options, views)
|
26
|
+
output = template.render(scope, locals, &block)
|
27
|
+
|
28
|
+
if layout
|
29
|
+
layout_template = compile_template(engine, layout, options, layouts)
|
30
|
+
output = layout_template.render(scope, locals) { output }
|
31
|
+
end
|
32
|
+
|
33
|
+
output
|
34
|
+
end
|
35
|
+
|
36
|
+
def compile_template(engine, data, options, views)
|
37
|
+
template_cache.fetch engine, data, options, views do
|
38
|
+
template = Tilt[engine]
|
39
|
+
|
40
|
+
# find template ./views /name /action_name engine_name
|
41
|
+
file = find_template(views, data, engine)
|
42
|
+
|
43
|
+
template.new(file)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# search through all available paths
|
48
|
+
# [./views/]name.[extension].[engine]/
|
49
|
+
# this will not search for responder's custom folder
|
50
|
+
# responder
|
51
|
+
def find_template(views, name, engine)
|
52
|
+
responder_folder = self.class.name.underscore.sub(/_responder$/, '') # => namespace/responder_name
|
53
|
+
|
54
|
+
# NOTE: we can add shared, and other paths from settings
|
55
|
+
folders_to_search = [File.join(views, responder_folder, "*.#{engine.to_s}")]
|
56
|
+
folders_to_search << File.join(views, "*.#{engine.to_s}")
|
57
|
+
|
58
|
+
search_regexp = /\/#{name}[\w|\.]*.#{engine}$/
|
59
|
+
Dir[*folders_to_search].each do |file_path|
|
60
|
+
return file_path if search_regexp.match(file_path)
|
61
|
+
end
|
62
|
+
raise TemplateNotFound.new
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Webmate
|
2
|
+
class Route
|
3
|
+
FIELDS = [:method, :path, :action, :transport, :responder, :route_regexp, :static_params]
|
4
|
+
attr_reader *FIELDS
|
5
|
+
|
6
|
+
# method: GET/POST/PUT/DELETE
|
7
|
+
# path : /user/123/posts/123/comments
|
8
|
+
# transport: HTTP/WS/
|
9
|
+
# responder: class, responsible to 'respond' action
|
10
|
+
# action: method in webmate responders, called to fetch data
|
11
|
+
# static params: additional params hash, which will be passed to responder
|
12
|
+
# for example, { :scope => :user }
|
13
|
+
#
|
14
|
+
def initialize(args)
|
15
|
+
values = args.with_indifferent_access
|
16
|
+
FIELDS.each do |field_name|
|
17
|
+
instance_variable_set("@#{field_name.to_s}", values[field_name])
|
18
|
+
end
|
19
|
+
|
20
|
+
normalize_data_if_needed
|
21
|
+
@route_regexp ||= construct_match_regexp
|
22
|
+
end
|
23
|
+
|
24
|
+
# method should check coincidence of path pattern and
|
25
|
+
# given path
|
26
|
+
# '/projects/qwerty123/tasks/asdf13/comments/zxcv123'
|
27
|
+
# will be parsed with route
|
28
|
+
# /projects/:project_id/tasks/:task_id/comments/:comment_id
|
29
|
+
# and return
|
30
|
+
# result = {
|
31
|
+
# action: 'read',
|
32
|
+
# responder: CommentsResponder,
|
33
|
+
# params: {
|
34
|
+
# project_id: 'qwerty123',
|
35
|
+
# task_id: :asdf13,
|
36
|
+
# comment_id: :zxcv123
|
37
|
+
# }
|
38
|
+
# }
|
39
|
+
def match(request_path)
|
40
|
+
if match_data = @route_regexp.match(request_path)
|
41
|
+
route_data = {
|
42
|
+
action: @action,
|
43
|
+
responder: @responder,
|
44
|
+
params: HashWithIndifferentAccess.new(static_params || {})
|
45
|
+
}
|
46
|
+
@substitution_attrs.each_with_index do |key, index|
|
47
|
+
if key == :splat
|
48
|
+
route_data[:params][key] ||= []
|
49
|
+
route_data[:params][key] += match_data[index.next].split('/')
|
50
|
+
else
|
51
|
+
route_data[:params][key] = match_data[index.next]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
route_data
|
55
|
+
else
|
56
|
+
nil # not matched.
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
# /projects/:project_id/tasks/:task_id/comments/:comment_id
|
63
|
+
# result should be
|
64
|
+
# substitution_attrs = [:project_id, :task_id, :comment_id]
|
65
|
+
# route_regexp =
|
66
|
+
# (?-mix:^\/projects\/([\w\d]*)\/tasks\/([\w\d]*)\/comments\/([\w\d]*)\/?$)
|
67
|
+
#
|
68
|
+
# substitute :resource_id elements with regexp group in order
|
69
|
+
# to easy extract
|
70
|
+
def construct_match_regexp
|
71
|
+
substitutions = path.scan(/\/:(\w*)|\/(\*)/)
|
72
|
+
@substitution_attrs = substitutions.each_with_object([]) do |scan, attrs|
|
73
|
+
if scan[0]
|
74
|
+
attrs << scan[0].to_sym
|
75
|
+
elsif scan[1]
|
76
|
+
attrs << :splat
|
77
|
+
end
|
78
|
+
end
|
79
|
+
regexp_string = path.gsub(/\/:(\w*_id)/) {|t| "/([\\w\\d]*)" }
|
80
|
+
regexp_string = regexp_string.gsub(/\/\*/) {|t| "\/(.*)"}
|
81
|
+
Regexp.new("^#{regexp_string}\/?$")
|
82
|
+
end
|
83
|
+
|
84
|
+
# update attributes by following rules
|
85
|
+
# - responder should be a Class, not String
|
86
|
+
# - ..
|
87
|
+
def normalize_data_if_needed
|
88
|
+
@responder = @responder.to_s.classify.constantize unless @responder.is_a?(Class)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|