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
data/.gitignore
ADDED
data/GUIDE.md
ADDED
@@ -0,0 +1,115 @@
|
|
1
|
+
#webmate app skeleton
|
2
|
+
see working example on https://github.com/evgeniypetrov/webmate-app-skeleton
|
3
|
+
|
4
|
+
## Quick start
|
5
|
+
|
6
|
+
to start application, run
|
7
|
+
webmate start
|
8
|
+
application will be available on localhost:3000 url
|
9
|
+
|
10
|
+
to run irb/console
|
11
|
+
webmate console
|
12
|
+
|
13
|
+
type webmate without params to know all options
|
14
|
+
|
15
|
+
show all available routes [ without assets ]
|
16
|
+
rake routes k
|
17
|
+
|
18
|
+
## Tutorial
|
19
|
+
### Skeleton
|
20
|
+
require files
|
21
|
+
|
22
|
+
to create file, add following gem to Gemfile
|
23
|
+
|
24
|
+
Gemfile
|
25
|
+
gem 'webmate'
|
26
|
+
gem 'slim'
|
27
|
+
gem 'sass', group: assets
|
28
|
+
gem 'alphasights-sinatra-sprockets', require: 'sinatra-sprockets', group: assets
|
29
|
+
|
30
|
+
base required files:
|
31
|
+
|
32
|
+
config.ru
|
33
|
+
require './config/environment'
|
34
|
+
if configatron.assets.compile
|
35
|
+
map '/assets' do
|
36
|
+
run Sinatra::Sprockets.environment
|
37
|
+
end
|
38
|
+
end
|
39
|
+
run ExampleApp
|
40
|
+
|
41
|
+
config/config.rb
|
42
|
+
Webmate::Application.configure do |config|
|
43
|
+
# add directory to application load paths
|
44
|
+
#config.app.load_paths << ["app/uploaders"]
|
45
|
+
config.app.cache_classes = true
|
46
|
+
config.assets.compile = false
|
47
|
+
|
48
|
+
config.websockets.namespace = 'api'
|
49
|
+
config.websockets.enabled = true
|
50
|
+
config.websockets.port = 9020
|
51
|
+
end
|
52
|
+
|
53
|
+
Webmate::Application.configure(:development) do |config|
|
54
|
+
config.app.cache_classes = false
|
55
|
+
config.assets.compile = true
|
56
|
+
config.websockets.port = 3503
|
57
|
+
end
|
58
|
+
|
59
|
+
config/application.rb
|
60
|
+
require 'digest/sha1'
|
61
|
+
require 'base64'
|
62
|
+
|
63
|
+
class ExampleApp < Webmate::Application
|
64
|
+
# do other things)
|
65
|
+
end
|
66
|
+
|
67
|
+
config/environment.rb
|
68
|
+
WEBMATE_ROOT = File.expand_path('.')
|
69
|
+
require 'webmate'
|
70
|
+
|
71
|
+
Dir[File.join(WEBMATE_ROOT, 'app', 'routes', '**', '*.rb')].each do |file|
|
72
|
+
require file
|
73
|
+
end
|
74
|
+
|
75
|
+
### Hello world
|
76
|
+
adding route
|
77
|
+
app/routes/facade_routes.rb
|
78
|
+
ExampleApp.define_routes do
|
79
|
+
get '/', to: 'pages#index', transport: [:http]
|
80
|
+
end
|
81
|
+
|
82
|
+
adding responder to this route
|
83
|
+
create files in app/responders
|
84
|
+
base_responder
|
85
|
+
class BaseResponder < Webmate::Responders::Base
|
86
|
+
# Available options
|
87
|
+
# before_filter :do_something
|
88
|
+
|
89
|
+
rescue_from Webmate::Responders::ActionNotFound do
|
90
|
+
render_not_found
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
pages_responder
|
95
|
+
class PagesResponder < BaseResponder
|
96
|
+
include Webmate::Responders::Templates
|
97
|
+
|
98
|
+
def index
|
99
|
+
slim :index, layout: 'application'
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
also, required files
|
104
|
+
app/views/layouts/application.html.slim
|
105
|
+
app/views/pages/index.html.slim
|
106
|
+
|
107
|
+
### Assets
|
108
|
+
assets will be searched in app/assets folder, so place them to
|
109
|
+
app/assets/javascripts
|
110
|
+
app/assets/stylesheets
|
111
|
+
|
112
|
+
### Utilities - rake task, scripts
|
113
|
+
Rakefile
|
114
|
+
require File.expand_path('../config/environment', __FILE__)
|
115
|
+
Webmate::Application.load_tasks
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Iskander Haziev
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# Webmate
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Prerequisites
|
6
|
+
git
|
7
|
+
mongo
|
8
|
+
redis
|
9
|
+
|
10
|
+
sinatra 4.2
|
11
|
+
to make this, use sinatra-contrib at least 1.4.0 version
|
12
|
+
Specify in your Gemfile
|
13
|
+
gem 'sinatra-contrib', git: 'git://github.com/sinatra/sinatra-contrib.git'
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
Add this line to your application's Gemfile:
|
18
|
+
|
19
|
+
gem 'webmate'
|
20
|
+
|
21
|
+
And then execute:
|
22
|
+
|
23
|
+
$ bundle
|
24
|
+
|
25
|
+
Or install it yourself as:
|
26
|
+
|
27
|
+
$ gem install webmate
|
28
|
+
|
29
|
+
## Usage
|
30
|
+
|
31
|
+
TODO: Write usage instructions here
|
32
|
+
|
33
|
+
start server as
|
34
|
+
webmate server [-p port]
|
35
|
+
|
36
|
+
running interactive console
|
37
|
+
webmate console [environment]
|
38
|
+
or
|
39
|
+
webmate console [-e environment]
|
40
|
+
|
41
|
+
## Contributing
|
42
|
+
|
43
|
+
1. Fork it
|
44
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
45
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
46
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
47
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/bin/webmate
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# -*- encoding: utf-8 -*-
|
3
|
+
|
4
|
+
require 'optparse'
|
5
|
+
require 'ostruct'
|
6
|
+
|
7
|
+
options = OpenStruct.new(
|
8
|
+
port: 3000,
|
9
|
+
environment: 'development',
|
10
|
+
daemon: false,
|
11
|
+
rackup: 'config.ru'
|
12
|
+
)
|
13
|
+
|
14
|
+
opt_parser = OptionParser.new do |opt|
|
15
|
+
opt.banner = "Usage: webmate COMMAND [OPTIONS]"
|
16
|
+
opt.separator ""
|
17
|
+
opt.separator "Commands"
|
18
|
+
opt.separator " server: start server"
|
19
|
+
opt.separator " console [environment]: application console"
|
20
|
+
opt.separator " generate [app_name]: do something useful"
|
21
|
+
opt.separator ""
|
22
|
+
opt.separator "Options"
|
23
|
+
|
24
|
+
opt.on("-e","--environment ENVIRONMENT","which environment you want to run") do |environment|
|
25
|
+
options.environment = environment
|
26
|
+
end
|
27
|
+
|
28
|
+
opt.on("-d","--daemon","daemon mode") do |daemon|
|
29
|
+
options.daemon = daemon
|
30
|
+
end
|
31
|
+
|
32
|
+
opt.on("-p","--port PORT", Integer, "port number") do |port|
|
33
|
+
options.port = port
|
34
|
+
end
|
35
|
+
|
36
|
+
opt.on("-R","--rackup FILE", "rackup file") do |file|
|
37
|
+
options.rackup_file = file
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
opt_parser.parse!
|
42
|
+
|
43
|
+
command = ARGV.shift
|
44
|
+
case command
|
45
|
+
when 'server'
|
46
|
+
#TODO refactor to
|
47
|
+
# require 'lib/commands/server'
|
48
|
+
# with options
|
49
|
+
require './config/environment'
|
50
|
+
require 'thin'
|
51
|
+
|
52
|
+
cmd = ["start"]
|
53
|
+
cmd += ["-p", options.port.to_s]
|
54
|
+
|
55
|
+
Thin::Runner.new(cmd).run!
|
56
|
+
when 'console'
|
57
|
+
require 'irb'
|
58
|
+
ENV["RACK_ENV"] ||= ARGV.first || options.environment || 'development'
|
59
|
+
puts "Running Webmate console with env: #{ENV["RACK_ENV"]}"
|
60
|
+
require './config/environment'
|
61
|
+
ARGV.clear
|
62
|
+
IRB.start
|
63
|
+
|
64
|
+
when 'generate'
|
65
|
+
puts 'not yet implemented'
|
66
|
+
puts opt_parser
|
67
|
+
|
68
|
+
else
|
69
|
+
puts opt_parser
|
70
|
+
end
|
@@ -0,0 +1,138 @@
|
|
1
|
+
module Webmate
|
2
|
+
class Application < Sinatra::Base
|
3
|
+
# override sinatra's method
|
4
|
+
def route!(base = settings, pass_block = nil)
|
5
|
+
transport = @request.websocket? ? 'WS' : 'HTTP'
|
6
|
+
|
7
|
+
route_info = base.routes.match(@request.request_method, transport, @request.path)
|
8
|
+
|
9
|
+
# no route case - use default sinatra's processors
|
10
|
+
if !route_info
|
11
|
+
route_eval(&pass_block) if pass_block
|
12
|
+
route_missing
|
13
|
+
end
|
14
|
+
|
15
|
+
if @request.websocket?
|
16
|
+
unless authorized_to_open_connection?(route_info[:params][:scope])
|
17
|
+
return [401, {}, []]
|
18
|
+
end
|
19
|
+
|
20
|
+
session_id = route_info[:params][:session_id].inspect
|
21
|
+
Webmate::Websockets.subscribe(session_id, @request) do |message|
|
22
|
+
if route_info = base.routes.match(message['method'], 'WS', message.path)
|
23
|
+
request_info = {
|
24
|
+
path: message.path,
|
25
|
+
metadata: message.metadata || {},
|
26
|
+
action: route_info[:action],
|
27
|
+
params: message.params.merge(route_info[:params]),
|
28
|
+
request: @request
|
29
|
+
}
|
30
|
+
# here we should create subscriber who can live
|
31
|
+
# between messages.. but not between requests.
|
32
|
+
response = route_info[:responder].new(request_info).respond
|
33
|
+
|
34
|
+
# result of block will be sent back to user
|
35
|
+
response
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# this response not pass to user - so we keep connection alive.
|
40
|
+
# passing other response will close connection and socket
|
41
|
+
non_pass_response = [-1, {}, []]
|
42
|
+
return non_pass_response
|
43
|
+
|
44
|
+
else # HTTP
|
45
|
+
# this should return correct Rack response..
|
46
|
+
request_info = params_for_responder(route_info)
|
47
|
+
response = route_info[:responder].new(request_info).respond
|
48
|
+
|
49
|
+
return response.rack_format
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# this method prepare data for responder
|
54
|
+
# {
|
55
|
+
# path: '/',
|
56
|
+
# metadata: {},
|
57
|
+
# action: 'index',
|
58
|
+
# params: { test: true }
|
59
|
+
# }
|
60
|
+
def params_for_responder(route_info)
|
61
|
+
# create unified request info
|
62
|
+
# request_info = { path: '/', metadata: {}, action: 'index', params: { test: true } }
|
63
|
+
request_params = parsed_request_params
|
64
|
+
metadata = request_params.delete(:metadata)
|
65
|
+
{
|
66
|
+
path: @request.path,
|
67
|
+
metadata: metadata || {},
|
68
|
+
action: route_info[:action],
|
69
|
+
params: request_params.merge(route_info[:params]),
|
70
|
+
request: @request
|
71
|
+
}
|
72
|
+
end
|
73
|
+
|
74
|
+
# @request.params working only for get params
|
75
|
+
# and params in url line ?key=value
|
76
|
+
def parsed_request_params
|
77
|
+
request_params = HashWithIndifferentAccess.new
|
78
|
+
request_params.merge!(@request.params || {})
|
79
|
+
|
80
|
+
# read post or put params. this will erase params
|
81
|
+
# {code: 123, mode: 123}
|
82
|
+
# "code=123&mode=123"
|
83
|
+
request_body = @request.body.read
|
84
|
+
if request_body.present?
|
85
|
+
body_params = begin
|
86
|
+
JSON.parse(request_body) # {code: 123, mode: 123}
|
87
|
+
rescue JSON::ParserError
|
88
|
+
Rack::Utils.parse_nested_query(request_body) # "code=123&mode=123"
|
89
|
+
end
|
90
|
+
else
|
91
|
+
body_params = {}
|
92
|
+
end
|
93
|
+
|
94
|
+
request_params.merge(body_params)
|
95
|
+
end
|
96
|
+
|
97
|
+
# update this method to create auth restrictions
|
98
|
+
def authorized_to_open_connection?(scope = :user)
|
99
|
+
return true
|
100
|
+
end
|
101
|
+
|
102
|
+
class << self
|
103
|
+
def configure(env = nil, &block)
|
104
|
+
if !env || Webmate.env?(env)
|
105
|
+
block.call(configatron)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def define_routes(&block)
|
110
|
+
settings = Webmate::Application
|
111
|
+
unless settings.routes.is_a?(RoutesCollection)
|
112
|
+
routes = RoutesCollection.new()
|
113
|
+
settings.set(:routes, routes)
|
114
|
+
end
|
115
|
+
settings.routes.define_routes(&block)
|
116
|
+
|
117
|
+
routes
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_channel_name_for(user_id)
|
121
|
+
channel_name = "some-unique-key-for-app-#{user_id}"
|
122
|
+
end
|
123
|
+
|
124
|
+
def dump(obj)
|
125
|
+
Yajl::Encoder.encode(obj)
|
126
|
+
end
|
127
|
+
|
128
|
+
def restore(str)
|
129
|
+
Yajl::Parser.parse(str)
|
130
|
+
end
|
131
|
+
|
132
|
+
def load_tasks
|
133
|
+
file_path = Pathname.new(__FILE__)
|
134
|
+
load File.join(file_path.dirname, "../../tasks/routes.rake")
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Webmate::Application.configure do |config|
|
2
|
+
config.app.load_paths = ["app/responders", "app/models", "app/services", "app/observers", "app/decorators"]
|
3
|
+
config.app.cache_classes = false
|
4
|
+
|
5
|
+
config.app.name = 'webmate'
|
6
|
+
config.app.host = 'localhost'
|
7
|
+
config.app.port = 80
|
8
|
+
config.app.host_with_port = Configatron::Delayed.new { "#{configatron.app.host}:#{configatron.app.port}" }
|
9
|
+
|
10
|
+
config.logger.path = "#{Webmate.root}/log"
|
11
|
+
|
12
|
+
config.assets.debug = false
|
13
|
+
config.assets.compress = false
|
14
|
+
config.assets.compile = true
|
15
|
+
config.assets.digest = false
|
16
|
+
|
17
|
+
config.cookies.key = Configatron::Delayed.new { "_#{configatron.app.name}_session" }
|
18
|
+
config.cookies.domain = nil
|
19
|
+
config.cookies.secret = "65e604cae451847ff2722ba84cb13db90f1b0a9ddc35a37169bec"
|
20
|
+
|
21
|
+
config.websockets.enabled = true
|
22
|
+
config.websockets.port = 80
|
23
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Webmate::Decorators
|
2
|
+
class Base
|
3
|
+
attr_reader :entity, :options
|
4
|
+
|
5
|
+
def initialize(entity, options = {})
|
6
|
+
@entity, @options = entity, options
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def decorate(entity_or_collection, options = {})
|
11
|
+
if entity_or_collection.respond_to?(:map)
|
12
|
+
entity_or_collection.map { |e| self.decorate(e, options) }
|
13
|
+
else
|
14
|
+
self.new(entity_or_collection, options)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def id
|
20
|
+
entity.id.to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
def method_missing(method, *args, &block)
|
24
|
+
if entity.respond_to?(method)
|
25
|
+
self.class.send :define_method, method do |*args, &blokk|
|
26
|
+
entity.send method, *args, &blokk
|
27
|
+
end
|
28
|
+
|
29
|
+
send method, *args, &block
|
30
|
+
else
|
31
|
+
super
|
32
|
+
end
|
33
|
+
rescue NoMethodError => no_method_error
|
34
|
+
super if no_method_error.name == method
|
35
|
+
raise no_method_error
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
data/lib/webmate/env.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
module Webmate
|
2
|
+
class Logger < Rack::CommonLogger
|
3
|
+
cattr_accessor :logger_file
|
4
|
+
def initialize(app = nil)
|
5
|
+
@app = app
|
6
|
+
Dir.mkdir(configatron.logger.path) unless File.exists?(configatron.logger.path)
|
7
|
+
@@logger_file ||= File.new(File.join(configatron.logger.path, "#{Webmate.env}.log"), 'a')
|
8
|
+
end
|
9
|
+
|
10
|
+
def log(env, status, header, began_at)
|
11
|
+
dump(%Q{HTTP #{env["REQUEST_METHOD"]}: #{env["PATH_INFO"]} #{status} \nParams: #{env['rack.request.query_hash']}})
|
12
|
+
end
|
13
|
+
|
14
|
+
def dump(text)
|
15
|
+
[@@logger_file, STDOUT].each do |out|
|
16
|
+
out.write %Q{[#{Time.now.strftime("%D %H:%M:%S")}] #{text} \n\n}
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Webmate::Observers
|
2
|
+
class Base
|
3
|
+
class << self
|
4
|
+
cattr_accessor :subscriptions
|
5
|
+
|
6
|
+
def subscribe(action, &block)
|
7
|
+
Webmate::Observers::Base.subscribe!(action, &block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def subscribe!(action, &block)
|
11
|
+
self.subscriptions ||= {}
|
12
|
+
self.subscriptions[action] ||= []
|
13
|
+
self.subscriptions[action] << block
|
14
|
+
end
|
15
|
+
|
16
|
+
def execute_all(action, data)
|
17
|
+
self.subscriptions ||= {}
|
18
|
+
(self.subscriptions[action] || []).each do |block|
|
19
|
+
block.call(data)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Webmate::Presenters
|
2
|
+
class Base
|
3
|
+
attr_reader :attrs
|
4
|
+
|
5
|
+
def initialize(object, options = {})
|
6
|
+
@object, @options = object, options.with_indifferent_access
|
7
|
+
@attrs = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def namespace(name, &block)
|
11
|
+
serializer = self.class.new(@object, @options)
|
12
|
+
serializer.instance_exec(@object, &block)
|
13
|
+
self.attrs[name] = serializer.attrs
|
14
|
+
end
|
15
|
+
|
16
|
+
def resource(name, object = nil, &block)
|
17
|
+
raise "You should set name for resource" if name.blank?
|
18
|
+
raise "You should specify object" if @object.nil? && object.nil?
|
19
|
+
nested_name = name.to_s
|
20
|
+
nested_object = object || @object.send(nested_name)
|
21
|
+
if nested_object.blank?
|
22
|
+
self.attrs[nested_name] = {}
|
23
|
+
else
|
24
|
+
self.attrs[nested_name] = nested_resource(nested_name, nested_object, @options, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def resources(name, objects = nil, &block)
|
29
|
+
raise "You should specify object" if @object.nil? && objects.nil?
|
30
|
+
objects = objects.flatten unless objects.nil?
|
31
|
+
nested_objects = objects || @object.send(name.to_s)
|
32
|
+
if nested_objects.blank?
|
33
|
+
self.attrs[name.to_s] = []
|
34
|
+
else
|
35
|
+
self.attrs[name.to_s] = (nested_objects || []).inject([]) do |result, obj|
|
36
|
+
resource = nested_resource(name, obj, @options, &block)
|
37
|
+
resource.empty? ? result : (result << resource)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def attributes(*attrs, &block)
|
43
|
+
if @object.blank?
|
44
|
+
object = attrs.last
|
45
|
+
attrs.delete(attrs.last)
|
46
|
+
raise ArgumentError, "Object was not specified" if object.is_a?(Symbol)
|
47
|
+
end
|
48
|
+
|
49
|
+
target = object || @object
|
50
|
+
Array.wrap(attrs).flatten.each do |attribute|
|
51
|
+
self.attrs[attribute.to_s] = target.send(attribute.to_s)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def attribute(attr, &block)
|
56
|
+
self.attrs[attr.to_s] = yield
|
57
|
+
end
|
58
|
+
|
59
|
+
protected
|
60
|
+
|
61
|
+
def nested_resource(name, object, options, &block)
|
62
|
+
return nil if !object || object.blank?
|
63
|
+
serializer = self.class.new(object, options)
|
64
|
+
serializer.instance_exec(object, &block)
|
65
|
+
serializer.attrs
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|