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