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.
Files changed (62) hide show
  1. data/.gitignore +19 -0
  2. data/GUIDE.md +115 -0
  3. data/Gemfile +10 -0
  4. data/LICENSE.txt +22 -0
  5. data/README.md +47 -0
  6. data/Rakefile +1 -0
  7. data/bin/webmate +70 -0
  8. data/lib/webmate/application.rb +138 -0
  9. data/lib/webmate/config.rb +23 -0
  10. data/lib/webmate/decorators/base.rb +38 -0
  11. data/lib/webmate/env.rb +17 -0
  12. data/lib/webmate/logger.rb +20 -0
  13. data/lib/webmate/observers/base.rb +24 -0
  14. data/lib/webmate/presenters/base.rb +69 -0
  15. data/lib/webmate/presenters/base_presenter.rb +115 -0
  16. data/lib/webmate/presenters/scoped.rb +30 -0
  17. data/lib/webmate/responders/abstract.rb +127 -0
  18. data/lib/webmate/responders/base.rb +21 -0
  19. data/lib/webmate/responders/callbacks.rb +79 -0
  20. data/lib/webmate/responders/exceptions.rb +4 -0
  21. data/lib/webmate/responders/response.rb +36 -0
  22. data/lib/webmate/responders/templates.rb +65 -0
  23. data/lib/webmate/route_helpers/route.rb +91 -0
  24. data/lib/webmate/route_helpers/routes_collection.rb +273 -0
  25. data/lib/webmate/socket.io/actions/connection.rb +14 -0
  26. data/lib/webmate/socket.io/actions/handshake.rb +34 -0
  27. data/lib/webmate/socket.io/packets/ack.rb +5 -0
  28. data/lib/webmate/socket.io/packets/base.rb +156 -0
  29. data/lib/webmate/socket.io/packets/connect.rb +5 -0
  30. data/lib/webmate/socket.io/packets/disconnect.rb +5 -0
  31. data/lib/webmate/socket.io/packets/error.rb +5 -0
  32. data/lib/webmate/socket.io/packets/event.rb +5 -0
  33. data/lib/webmate/socket.io/packets/heartbeat.rb +5 -0
  34. data/lib/webmate/socket.io/packets/json.rb +5 -0
  35. data/lib/webmate/socket.io/packets/message.rb +5 -0
  36. data/lib/webmate/socket.io/packets/noop.rb +5 -0
  37. data/lib/webmate/support/em_mongoid.rb +53 -0
  38. data/lib/webmate/version.rb +3 -0
  39. data/lib/webmate/views/scope.rb +25 -0
  40. data/lib/webmate/websockets.rb +50 -0
  41. data/lib/webmate.rb +129 -0
  42. data/spec/lib/route_helpers/route_spec.rb +41 -0
  43. data/spec/spec_helper.rb +18 -0
  44. data/vendor/.DS_Store +0 -0
  45. data/vendor/assets/.DS_Store +0 -0
  46. data/vendor/assets/javascripts/.DS_Store +0 -0
  47. data/vendor/assets/javascripts/webmate/.DS_Store +0 -0
  48. data/vendor/assets/javascripts/webmate/auth.coffee +63 -0
  49. data/vendor/assets/javascripts/webmate/backbone_ext/.DS_Store +0 -0
  50. data/vendor/assets/javascripts/webmate/backbone_ext/resources.coffee +60 -0
  51. data/vendor/assets/javascripts/webmate/backbone_ext/sync.coffee +131 -0
  52. data/vendor/assets/javascripts/webmate/client.coffee +133 -0
  53. data/vendor/assets/javascripts/webmate/init.coffee +7 -0
  54. data/vendor/assets/javascripts/webmate/libs/.DS_Store +0 -0
  55. data/vendor/assets/javascripts/webmate/libs/backbone.js +1572 -0
  56. data/vendor/assets/javascripts/webmate/libs/benchmark.coffee +27 -0
  57. data/vendor/assets/javascripts/webmate/libs/icanhaz.js +542 -0
  58. data/vendor/assets/javascripts/webmate/libs/socket.io.js +3871 -0
  59. data/vendor/assets/javascripts/webmate/libs/underscore.js +1 -0
  60. data/vendor/assets/javascripts/webmate.js +10 -0
  61. data/webmate.gemspec +31 -0
  62. metadata +290 -0
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .rspec
7
+ .ruby-version
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
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
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in webmate.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'rake'
8
+ gem 'rspec'
9
+ gem 'webmate-sprockets'
10
+ end
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
@@ -0,0 +1,17 @@
1
+ module Webmate
2
+ def self.root
3
+ WEBMATE_ROOT
4
+ end
5
+
6
+ def self.env
7
+ ENV["RACK_ENV"]
8
+ end
9
+
10
+ def self.env?(env)
11
+ self.env == env.to_s
12
+ end
13
+
14
+ def self.logger
15
+ @logger ||= Webmate::Logger.new
16
+ end
17
+ end
@@ -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