rabl 0.8.6 → 0.9.0.pre

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 (78) hide show
  1. checksums.yaml +15 -0
  2. data/.travis.yml +0 -3
  3. data/CHANGELOG.md +7 -0
  4. data/Gemfile +1 -2
  5. data/Gemfile.ci +0 -1
  6. data/README.md +1 -0
  7. data/Rakefile +1 -1
  8. data/fixtures/rails2/config/environment.rb +22 -0
  9. data/fixtures/rails4/.gitignore +16 -0
  10. data/fixtures/rails4/Gemfile +47 -0
  11. data/fixtures/rails4/README.rdoc +28 -0
  12. data/fixtures/rails4/Rakefile +11 -0
  13. data/fixtures/rails4/app/assets/javascripts/application.js +16 -0
  14. data/fixtures/rails4/app/assets/stylesheets/application.css +13 -0
  15. data/fixtures/rails4/app/controllers/application_controller.rb +5 -0
  16. data/fixtures/rails4/app/controllers/concerns/.keep +0 -0
  17. data/fixtures/rails4/app/controllers/posts_controller.rb +11 -0
  18. data/fixtures/rails4/app/controllers/users_controller.rb +11 -0
  19. data/fixtures/rails4/app/helpers/application_helper.rb +5 -0
  20. data/fixtures/rails4/app/mailers/.keep +0 -0
  21. data/fixtures/rails4/bin/bundle +3 -0
  22. data/fixtures/rails4/bin/rails +4 -0
  23. data/fixtures/rails4/bin/rake +4 -0
  24. data/fixtures/rails4/config.ru +4 -0
  25. data/fixtures/rails4/config/application.rb +23 -0
  26. data/fixtures/rails4/config/boot.rb +4 -0
  27. data/fixtures/rails4/config/database.yml +25 -0
  28. data/fixtures/rails4/config/environment.rb +5 -0
  29. data/fixtures/rails4/config/environments/development.rb +29 -0
  30. data/fixtures/rails4/config/environments/production.rb +80 -0
  31. data/fixtures/rails4/config/environments/test.rb +36 -0
  32. data/fixtures/rails4/config/initializers/backtrace_silencers.rb +7 -0
  33. data/fixtures/rails4/config/initializers/filter_parameter_logging.rb +4 -0
  34. data/fixtures/rails4/config/initializers/inflections.rb +16 -0
  35. data/fixtures/rails4/config/initializers/mime_types.rb +7 -0
  36. data/fixtures/rails4/config/initializers/secret_token.rb +12 -0
  37. data/fixtures/rails4/config/initializers/session_store.rb +3 -0
  38. data/fixtures/rails4/config/initializers/wrap_parameters.rb +14 -0
  39. data/fixtures/rails4/config/locales/en.yml +23 -0
  40. data/fixtures/rails4/config/routes.rb +51 -0
  41. data/fixtures/rails4/db/seeds.rb +7 -0
  42. data/fixtures/rails4/lib/assets/.keep +0 -0
  43. data/fixtures/rails4/lib/tasks/.keep +0 -0
  44. data/fixtures/rails4/log/.keep +0 -0
  45. data/fixtures/rails4/public/404.html +58 -0
  46. data/fixtures/rails4/public/422.html +58 -0
  47. data/fixtures/rails4/public/500.html +57 -0
  48. data/fixtures/rails4/public/favicon.ico +0 -0
  49. data/fixtures/rails4/public/robots.txt +5 -0
  50. data/fixtures/rails4/test/controllers/.keep +0 -0
  51. data/fixtures/rails4/test/fixtures/.keep +0 -0
  52. data/fixtures/rails4/test/functional/posts_controller_test.rb +232 -0
  53. data/fixtures/rails4/test/functional/users_controller_test.rb +87 -0
  54. data/fixtures/rails4/test/helpers/.keep +0 -0
  55. data/fixtures/rails4/test/integration/.keep +0 -0
  56. data/fixtures/rails4/test/mailers/.keep +0 -0
  57. data/fixtures/rails4/test/models/.keep +0 -0
  58. data/fixtures/rails4/test/test_helper.rb +26 -0
  59. data/fixtures/rails4/vendor/assets/javascripts/.keep +0 -0
  60. data/fixtures/rails4/vendor/assets/stylesheets/.keep +0 -0
  61. data/lib/rabl.rb +6 -1
  62. data/lib/rabl/builder.rb +3 -1
  63. data/lib/rabl/digestor.rb +21 -0
  64. data/lib/rabl/engine.rb +20 -2
  65. data/lib/rabl/helpers.rb +7 -1
  66. data/lib/rabl/railtie.rb +6 -0
  67. data/lib/rabl/template.rb +1 -1
  68. data/lib/rabl/tracker.rb +53 -0
  69. data/lib/rabl/version.rb +1 -1
  70. data/rabl.gemspec +6 -1
  71. data/test/engine_test.rb +23 -0
  72. data/test/helpers_test.rb +6 -0
  73. data/test/integration/rails4/posts_controller_test.rb +232 -0
  74. data/test/integration/rails4/users_controller_test.rb +87 -0
  75. data/test/msgpack_engine_test.rb +50 -49
  76. data/test/renderer_test.rb +2 -2
  77. data/test/teststrap.rb +6 -2
  78. metadata +65 -33
@@ -0,0 +1,87 @@
1
+ # Lives in <rabl>/test/integration/users_controller_test.rb
2
+ # Symlinked to fixture applications
3
+
4
+ begin # Sinatra
5
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_config.rb')
6
+ rescue LoadError # Rails
7
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper.rb')
8
+ end
9
+
10
+ context "UsersController" do
11
+ helper(:json_output) { JSON.parse(last_response.body) }
12
+
13
+ setup do
14
+ create_users!
15
+ end
16
+
17
+ context "for index action" do
18
+ # Tests `collection @users` extending from 'show' template
19
+
20
+ setup do
21
+ get "/users", format: :json
22
+ end
23
+
24
+ # Attributes (regular)
25
+ asserts("contains user usernames") do
26
+ json_output.map { |u| u["user"]["username"] }
27
+ end.equals { @users.map(&:username) }
28
+ asserts("contains email") do
29
+ json_output.map { |u| u["user"]["email"] }
30
+ end.equals { @users.map(&:email) }
31
+ asserts("contains location") do
32
+ json_output.map { |u| u["user"]["location"] }
33
+ end.equals { @users.map(&:location) }
34
+
35
+ # Attributes (custom name)
36
+ asserts("contains registered_at") do
37
+ json_output.map { |u| u["user"]["registered_at"] }
38
+ end.equals { @users.map(&:created_at).map(&:utc).map(&:to_s) }
39
+
40
+ # Node (renders based on attribute)
41
+ asserts("contains role") do
42
+ json_output.map { |u| u["user"]["role"] }
43
+ end.equals ['normal', 'normal', 'admin']
44
+
45
+ # Child (custom collection name)
46
+ asserts("contains formatted phone numbers") do
47
+ json_output.map { |u| u["user"]["pnumbers"].map { |n| n["pnumber"]["formatted"] } }
48
+ end.equals { @users.map { |u| u.phone_numbers.map(&:formatted) } }
49
+
50
+ # Node (renders collection partial)
51
+ asserts("contains reversed node numbers") do
52
+ json_output.map { |u| u["user"]["node_numbers"].map { |n| n["reversed"] } }
53
+ end.equals { @users.map { |u| u.phone_numbers.map(&:formatted).map(&:reverse) } }
54
+ end # index
55
+
56
+ context "for show action" do
57
+ # Tests `object :user => :person` custom parent node name
58
+ setup do
59
+ get "/users/#{@user1.id}", format: :json
60
+ end
61
+
62
+ # Attributes (regular)
63
+ asserts("contains username") { json_output["person"]["username"] }.equals { @user1.username }
64
+ asserts("contains email") { json_output["person"]["email"] }.equals { @user1.email }
65
+ asserts("contains location") { json_output["person"]["location"] }.equals { @user1.location }
66
+ # Attributes (custom name)
67
+ asserts("contains registered_at") { json_output["person"]["registered_at"] }.equals { @user1.created_at.utc.to_s }
68
+ # Node (renders based on attribute)
69
+ asserts("contains role node") { json_output["person"]["role"] }.equals "normal"
70
+
71
+ # Child (custom collection name)
72
+ asserts("contains first phone number") {
73
+ json_output["person"]["pnumbers"][0]["pnumber"]["formatted"]
74
+ }.equals { @user1.phone_numbers[0].formatted }
75
+ asserts("contains second phone number") {
76
+ json_output["person"]["pnumbers"][1]["pnumber"]["formatted"]
77
+ }.equals { @user1.phone_numbers[1].formatted }
78
+
79
+ # Node (renders collection partial)
80
+ asserts("contains first node number") {
81
+ json_output["person"]["node_numbers"][0]["formatted"]
82
+ }.equals { @user1.phone_numbers[0].formatted }
83
+ asserts("contains second node number") {
84
+ json_output["person"]["node_numbers"][1]["formatted"]
85
+ }.equals { @user1.phone_numbers[1].formatted }
86
+ end # show
87
+ end
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,26 @@
1
+ # Load Silence Functionality
2
+ require File.expand_path(File.dirname(__FILE__) + "/../../../test/silence.rb")
3
+
4
+ # Load Environment
5
+ silence_warnings do
6
+ ENV["RAILS_ENV"] = "test"
7
+ require File.expand_path('../../config/environment', __FILE__)
8
+ require 'rails/test_help'
9
+ end
10
+
11
+ # Load Riot Test Environment
12
+ require File.expand_path(File.dirname(__FILE__) + "/../../../test/integration/test_init.rb")
13
+
14
+ # Run Migrations
15
+ silence_stream(STDOUT) do
16
+ dbconf = YAML::load(File.open('config/database.yml'))[Rails.env]
17
+ ActiveRecord::Base.establish_connection(dbconf)
18
+ ActiveRecord::Base.logger = Logger.new(File.open('log/database.log', 'a'))
19
+ silence_stream(STDOUT) { ActiveRecord::Migrator.up('db/migrate') }
20
+ end
21
+
22
+ class Riot::Situation
23
+ def app
24
+ Rails.application
25
+ end
26
+ end
File without changes
File without changes
data/lib/rabl.rb CHANGED
@@ -13,7 +13,12 @@ require 'rabl/builder'
13
13
  require 'rabl/configuration'
14
14
  require 'rabl/renderer'
15
15
  require 'rabl/cache_engine'
16
- require 'rabl/railtie' if defined?(Rails) && Rails.version =~ /^[34]/
16
+
17
+ if defined?(Rails)
18
+ require 'rabl/tracker' if Rails.version =~ /^[4]/
19
+ require 'rabl/digestor' if Rails.version =~ /^[4]/
20
+ require 'rabl/railtie' if Rails.version =~ /^[34]/
21
+ end
17
22
 
18
23
  # Rabl.register!
19
24
  module Rabl
data/lib/rabl/builder.rb CHANGED
@@ -99,9 +99,11 @@ module Rabl
99
99
  # child(@users => :people) { ... }
100
100
  def child(data, options={}, &block)
101
101
  return false unless data.present? && resolve_condition(options)
102
- name, object = data_name(data), data_object(data)
102
+ name = is_name_value?(options[:root]) ? options[:root] : data_name(data)
103
+ object = data_object(data)
103
104
  include_root = is_collection?(object) && options.fetch(:object_root, @options[:child_root]) # child @users
104
105
  engine_options = @options.slice(:child_root).merge(:root => include_root)
106
+ engine_options.merge!(:object_root_name => options[:object_root]) if is_name_value?(options[:object_root])
105
107
  object = { object => name } if data.respond_to?(:each_pair) && object # child :users => :people
106
108
  @_result[name] = self.object_to_hash(object, engine_options, &block)
107
109
  end
@@ -0,0 +1,21 @@
1
+ module Rabl
2
+ class Digestor < ActionView::Digestor
3
+ # Override the original digest function to ignore partial which
4
+ # rabl doesn't use the Rails conventional _ symbol.
5
+ def self.digest(name, format, finder, options = {})
6
+ cache_key = [name, format] + Array.wrap(options[:dependencies])
7
+ @@cache[cache_key.join('.')] ||= begin
8
+ Digestor.new(name, format, finder, options).digest
9
+ end
10
+ end
11
+
12
+ private
13
+ def dependency_digest
14
+ template_digests = dependencies.collect do |template_name|
15
+ Digestor.digest(template_name, format, finder, partial: true)
16
+ end
17
+
18
+ (template_digests + injected_dependencies).join("-")
19
+ end
20
+ end
21
+ end
data/lib/rabl/engine.rb CHANGED
@@ -28,7 +28,8 @@ module Rabl
28
28
  @_options[:scope] = @_scope
29
29
  @_options[:format] ||= self.request_format
30
30
  data = locals[:object].nil? ? self.default_object : locals[:object]
31
- @_data_object, @_data_name = data_object(data), data_name(data)
31
+ @_data_object = data_object(data)
32
+ @_data_name = @_options[:object_root_name] || data_name(data)
32
33
  if @_options[:source_location]
33
34
  instance_eval(@_source, @_options[:source_location]) if @_source.present?
34
35
  else # without source location
@@ -274,12 +275,29 @@ module Rabl
274
275
  _cache = @_cache if defined?(@_cache)
275
276
  cache_key, cache_options = *_cache || nil
276
277
  if template_cache_configured? && cache_key
277
- result_cache_key = Array(cache_key) + [@_options[:root_name], @_options[:format]]
278
+ if Rails.version =~ /^[4]/
279
+ result_cache_key = cache_key_with_digest(cache_key)
280
+ else # fallback for Rails 3
281
+ result_cache_key = cache_key_simple(cache_key)
282
+ end
278
283
  fetch_result_from_cache(result_cache_key, cache_options, &block)
279
284
  else # skip caching
280
285
  yield
281
286
  end
282
287
  end
283
288
 
289
+ def cache_key_with_digest(cache_key)
290
+ Array(cache_key) + [
291
+ @_options[:root_name],
292
+ @_options[:format],
293
+ Digestor.digest(@virtual_path, :rabl, lookup_context, dependencies: view_cache_dependencies)
294
+ ]
295
+ end
296
+
297
+ def cache_key_simple(key)
298
+ Array(key) + [@_options[:root_name], @_options[:format]]
299
+ end
300
+
301
+
284
302
  end
285
303
  end
data/lib/rabl/helpers.rb CHANGED
@@ -28,7 +28,8 @@ module Rabl
28
28
  return data_token.values.first if data_token.is_a?(Hash) # @user => :user
29
29
  data = data_object(data_token)
30
30
  if is_collection?(data) && data.respond_to?(:first) # data is a collection
31
- object_name = data_name(data.first).to_s.pluralize if data.first.present?
31
+ object_name = data.table_name if data.respond_to?(:table_name)
32
+ object_name ||= data_name(data.first).to_s.pluralize if data.first.present?
32
33
  object_name ||= data_token if data_token.is_a?(Symbol)
33
34
  object_name
34
35
  elsif is_object?(data) # data is an object
@@ -92,6 +93,11 @@ module Rabl
92
93
  defined?(@_collection_name) ? @_collection_name : nil
93
94
  end
94
95
 
96
+ # Returns true if the value is a name value (symbol or string)
97
+ def is_name_value?(val)
98
+ val.is_a?(String) || val.is_a?(Symbol)
99
+ end
100
+
95
101
  # Fetches a key from the cache and stores rabl template result otherwise
96
102
  # fetch_from_cache('some_key') { ...rabl template result... }
97
103
  def fetch_result_from_cache(cache_key, cache_options=nil, &block)
data/lib/rabl/railtie.rb CHANGED
@@ -4,6 +4,12 @@ module Rabl
4
4
  initializer "rabl.initialize" do |app|
5
5
  ActiveSupport.on_load(:action_view) do
6
6
  Rabl.register!
7
+
8
+ # Inject dependency tracker for :rabl
9
+ if Rails.version =~ /^[4]/
10
+ require 'action_view/dependency_tracker'
11
+ ActionView::DependencyTracker.register_tracker :rabl, Rabl::Tracker
12
+ end
7
13
  end
8
14
  end
9
15
 
data/lib/rabl/template.rb CHANGED
@@ -40,7 +40,7 @@ if defined?(ActionView) && defined?(Rails) && Rails.version.to_s =~ /^2/
40
40
  ActionView::Template.register_template_handler :rabl, ActionView::TemplateHandlers::RablHandler
41
41
  end
42
42
 
43
- # Rails 3.X Template
43
+ # Rails 3.X / 4.X Template
44
44
  if defined?(ActionView) && defined?(Rails) && Rails.version.to_s =~ /^[34]/
45
45
  module ActionView
46
46
  module Template::Handlers
@@ -0,0 +1,53 @@
1
+ module Rabl
2
+ # DependencyTracker for ActionView to support cache digest
3
+ class Tracker
4
+ # Matches:
5
+ # extends "categories/show"
6
+ EXTENDS_DEPENDENCY = /
7
+ extends\s* # extends, followed by optional whitespace
8
+ \(? # start an optional parenthesis for the extends call
9
+ \s*["']([a-z_\/\.]+) # the template name itself
10
+ /x
11
+
12
+ # Matches:
13
+ # partial "categories/show"
14
+ PARTIAL_DEPENDENCY = /
15
+ partial\s* # partial, followed by optional whitespace
16
+ \(? # start an optional parenthesis for the partial call
17
+ \s*["']([a-z_\/\.]+) # the template name itself
18
+ /x
19
+
20
+ def self.call(name, template)
21
+ new(name, template).dependencies
22
+ end
23
+
24
+ def initialize(name, template)
25
+ @name, @template = name, template
26
+ end
27
+
28
+ def dependencies
29
+ (extends_dependencies + partial_dependencies).uniq
30
+ end
31
+
32
+ attr_reader :name, :template
33
+ private :name, :template
34
+
35
+ private
36
+
37
+ def source
38
+ template.source
39
+ end
40
+
41
+ def directory
42
+ name.split("/")[0..-2].join("/")
43
+ end
44
+
45
+ def extends_dependencies
46
+ source.scan(EXTENDS_DEPENDENCY).flatten
47
+ end
48
+
49
+ def partial_dependencies
50
+ source.scan(PARTIAL_DEPENDENCY).flatten
51
+ end
52
+ end
53
+ end
data/lib/rabl/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Rabl
2
- VERSION = "0.8.6"
2
+ VERSION = "0.9.0.pre"
3
3
  end
data/rabl.gemspec CHANGED
@@ -19,7 +19,12 @@ Gem::Specification.new do |s|
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
- s.add_dependency 'activesupport', '>= 2.3.14'
22
+
23
+ if RUBY_VERSION < "1.9"
24
+ s.add_dependency 'activesupport', '>= 2.3.14', '<= 4'
25
+ else
26
+ s.add_dependency "activesupport", '>= 2.3.14'
27
+ end
23
28
 
24
29
  s.add_development_dependency 'riot', '~> 0.12.3'
25
30
  s.add_development_dependency 'rr', '~> 1.0.2'
data/test/engine_test.rb CHANGED
@@ -305,6 +305,29 @@ context "Rabl::Engine" do
305
305
  template.render(scope)
306
306
  end.equals "{\"user\":{\"name\":\"leo\",\"users\":[{\"city\":\"UNO\"},{\"city\":\"DOS\"}]}}"
307
307
 
308
+ asserts "it allows modification of object root node for child collection" do
309
+ template = rabl %{
310
+ object @user
311
+ attribute :name
312
+ child(@users, :object_root => 'person') { attribute :city }
313
+ }
314
+ scope = Object.new
315
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA')
316
+ scope.instance_variable_set :@users, [User.new(:name => 'one', :city => 'UNO'), User.new(:name => 'two', :city => 'DOS')]
317
+ template.render(scope)
318
+ end.equals "{\"user\":{\"name\":\"leo\",\"users\":[{\"person\":{\"city\":\"UNO\"}},{\"person\":{\"city\":\"DOS\"}}]}}"
319
+
320
+ asserts "it allows modification of both root labels for child collection" do
321
+ template = rabl %{
322
+ object @user
323
+ attribute :name
324
+ child(@users, :root => "people", :object_root => 'item') { attribute :city }
325
+ }
326
+ scope = Object.new
327
+ scope.instance_variable_set :@user, User.new(:name => 'leo', :city => 'LA')
328
+ scope.instance_variable_set :@users, [User.new(:name => 'one', :city => 'UNO'), User.new(:name => 'two', :city => 'DOS')]
329
+ template.render(scope)
330
+ end.equals "{\"user\":{\"name\":\"leo\",\"people\":[{\"item\":{\"city\":\"UNO\"}},{\"item\":{\"city\":\"DOS\"}}]}}"
308
331
  end
309
332
 
310
333
  context "#glue" do
data/test/helpers_test.rb CHANGED
@@ -32,6 +32,12 @@ context "Rabl::Helpers" do
32
32
  asserts "returns name of an object" do
33
33
  @helper_class.data_name(@user)
34
34
  end.equals('user')
35
+
36
+ asserts "returns table_name of collection if responds" do
37
+ @coll = [@user, @user]
38
+ mock(@coll).table_name { "people" }
39
+ @helper_class.data_name(@coll)
40
+ end.equals('people')
35
41
  end # data_name method
36
42
 
37
43
  context "for is_object method" do
@@ -0,0 +1,232 @@
1
+ # Lives in <rabl>/test/integration/posts_controller_test.rb
2
+ # Symlinked to fixture applications
3
+
4
+ begin # Padrino
5
+ require File.expand_path(File.dirname(__FILE__) + '/../../test_config.rb')
6
+ rescue LoadError # Rails
7
+ require File.expand_path(File.dirname(__FILE__) + '/../test_helper.rb')
8
+ end
9
+
10
+ require 'rexml/document'
11
+
12
+ context "PostsController" do
13
+ helper(:json_output) { JSON.parse(last_response.body) }
14
+
15
+ setup do
16
+ create_users!
17
+ Post.delete_all
18
+ @post1 = Post.create(:title => "Foo", :body => "Bar", :user_id => @user1.id)
19
+ @post2 = Post.create(:title => "Baz", :body => "Bah", :user_id => @user2.id)
20
+ @post3 = Post.create(:title => "Kaz", :body => "<script>alert('xss & test');</script>", :user_id => @user3.id)
21
+ @posts = [@post1, @post2, @post3]
22
+ end
23
+
24
+ context "for index action" do
25
+ setup do
26
+ get "/posts", format: :json
27
+ end
28
+
29
+ # Attributes (regular)
30
+ asserts("contains post titles") do
31
+ json_output['articles'].map { |o| o["article"]["title"] }
32
+ end.equals { @posts.map(&:title) }
33
+
34
+ asserts("contains post bodies") do
35
+ json_output['articles'].map { |o| o["article"]["body"] }
36
+ end.equals { @posts.map(&:body) }
37
+
38
+ # Attributes (custom name)
39
+ asserts("contains post posted_at") do
40
+ json_output['articles'].map { |o| o["article"]["posted_at"] }
41
+ end.equals { @posts.map(&:created_at).map{ |t| t.iso8601(3) } }
42
+
43
+ # Child
44
+ asserts("contains post user child username") do
45
+ json_output['articles'].map { |o| o["article"]["user"]["username"] }
46
+ end.equals { @posts.map(&:user).map(&:username) }
47
+
48
+ asserts("contains post user child role") do
49
+ json_output['articles'].map { |o| o["article"]["user"]["role"] }
50
+ end.equals { ["normal", "normal", "admin"] }
51
+
52
+ # Child Numbers of the Child User
53
+ asserts("contains post user child numbers") do
54
+ json_output['articles'].map { |o| o["article"]["user"]["pnumbers"][0]["pnumber"]["formatted"] }
55
+ end.equals { @posts.map(&:user).map(&:phone_numbers).map(&:first).map(&:formatted) }
56
+
57
+ # Glue (username to article)
58
+ asserts("contains glued usernames") do
59
+ json_output['articles'].map { |o| o["article"]["author_name"] }
60
+ end.equals { @posts.map(&:user).map(&:username) }
61
+
62
+ # Conditional Child (admin)
63
+ asserts("contains admin child only for admins") do
64
+ json_output['articles'].map { |o| o["article"]["admin"]["username"] if o["article"].has_key?("admin") }.compact
65
+ end.equals { [@user3.username] }
66
+
67
+ # Conditional Node (created_by_admin)
68
+ asserts("contains created_by_admin node for admins") do
69
+ json_output['articles'].last['article']['created_by_admin']
70
+ end.equals { true }
71
+
72
+ denies("contains no created_by_admin node for non-admins") do
73
+ json_output['articles'].first['article']
74
+ end.includes(:created_by_admin)
75
+ end # index action, json
76
+
77
+ context "escaping output in index action" do
78
+ context "for first post" do
79
+ setup do
80
+ Rabl.configuration.escape_all_output = true
81
+ get "/posts/#{@post1.id}", format: :json
82
+ json_output['post']
83
+ end
84
+
85
+ # Attributes (regular)
86
+ asserts("contains post title") { topic['title'] }.equals { @post1.title }
87
+ asserts("contains post body") { topic['body'] }.equals { @post1.body }
88
+ end
89
+
90
+ context "for third post with script tags" do
91
+ setup do
92
+ Rabl.configuration.escape_all_output = true
93
+ get "/posts/#{@post3.id}", format: :json
94
+ json_output['post']
95
+ end
96
+
97
+ # Attributes (regular)
98
+ asserts("contains post title") { topic['title'] }.equals { @post3.title }
99
+ asserts("contains escaped post body") { topic['body'] }.equals { ERB::Util.h(@post3.body) }
100
+ end
101
+ end # escaping output
102
+
103
+ context "for show action" do
104
+ setup do
105
+ get "/posts/#{@post1.id}", format: :json
106
+ json_output['post']
107
+ end
108
+
109
+ # Attributes (regular)
110
+ asserts("contains post title") { topic['title'] }.equals { @post1.title }
111
+ asserts("contains post body") { topic['body'] }.equals { @post1.body }
112
+
113
+ # Attributes (custom name)
114
+ asserts("contains post posted_at") { topic['posted_at'] }.equals { @post1.created_at.utc.to_s }
115
+
116
+ # Child
117
+ asserts("contains post user child username") { topic["user"]["username"] }.equals { @post1.user.username }
118
+ asserts("contains post user child role") { topic["user"]["role"] }.equals { "normal" }
119
+
120
+ # Child Numbers of the Child User
121
+ asserts("contains post user child numbers") do
122
+ topic["user"]["pnumbers"][0]["pnumber"]["formatted"]
123
+ end.equals { @post1.user.phone_numbers[0].formatted }
124
+
125
+ # Glue (username to article)
126
+ asserts("contains glued username") { topic["author_name"] }.equals { @post1.user.username }
127
+
128
+ # Non-ORM Date Node Partial
129
+ context "for date node" do
130
+ setup { json_output['post']['created_date'] }
131
+ asserts("contains date partial with day") { topic['day'] }.equals { @post1.created_at.day }
132
+ asserts("contains date partial with hour") { topic['hour'] }.equals { @post1.created_at.hour }
133
+ asserts("contains date partial with full") { topic['full'] }.equals { @post1.created_at.iso8601 }
134
+ end # date node
135
+
136
+ asserts("contains helper action") { topic["foo"] }.equals { "BAR!" }
137
+ denies("contains helper action") { topic["created_at_in_words"] }.nil
138
+
139
+ asserts("contains post attributes via node") { topic["post"] }.equals { [@post1.title, @post1.body] }
140
+ end # show action, json
141
+
142
+ context "for index action rendering JSON within HTML" do
143
+ setup do
144
+ get "/posts", format: :html
145
+ end
146
+
147
+ asserts(:body).includes { "<html>" }
148
+ end # index action, html
149
+
150
+ context "for show action rendering JSON within HTML" do
151
+ setup do
152
+ get "/posts/#{@post1.id}", format: :html
153
+ end
154
+
155
+ asserts(:body).includes { "<html>" }
156
+ end # show action, html
157
+
158
+ context "mime_type" do
159
+ setup do
160
+ get "/posts/#{@post1.id}", format: :rabl_test_v1
161
+ end
162
+
163
+ asserts("contains post title") { json_output['post']['title_v1'] }.equals { @post1.title }
164
+ asserts("contains username") { json_output['post']['user']['username_v1'] }.equals { @post1.user.username }
165
+ end
166
+
167
+ context "caching" do
168
+ helper(:cache_hit) do |key|
169
+ Rails.cache.read(ActiveSupport::Cache.expand_cache_key(key, :rabl))
170
+ end
171
+
172
+ setup do
173
+ mock(ActionController::Base).perform_caching.any_number_of_times { true }
174
+ Rails.cache.clear
175
+ end
176
+
177
+ context "for index action with caching in json" do
178
+ setup do
179
+ get "/posts", format: :json
180
+ end
181
+
182
+ asserts("contains post titles") do
183
+ json_output['articles'].map { |o| o['article']['title'] }
184
+ end.equals { @posts.map(&:title) }
185
+
186
+ asserts(:body).equals { cache_hit ['kittens!', @posts, nil, 'json', 'e83f65eee5ffb454c418a59105f222c4'] }
187
+
188
+ asserts("contains cache hits per object (posts by title)") do
189
+ json_output['articles'].map { |o| o['article']['title'] }
190
+ end.equals { @posts.map{ |p| cache_hit([p, nil, 'hash', 'e83f65eee5ffb454c418a59105f222c4'])[:title] } }
191
+ end # index action, caching, json
192
+
193
+ context "for index action with caching in xml" do
194
+ setup do
195
+ get "/posts", format: :xml
196
+ end
197
+
198
+ asserts("contains post titles") do
199
+ doc = REXML::Document.new topic.body
200
+ doc.elements.inject('articles/article/title', []) {|arr, ele| arr << ele.text}
201
+ end.equals { @posts.map(&:title) }
202
+
203
+ asserts(:body).equals { cache_hit ['kittens!', @posts, nil, 'xml', 'e83f65eee5ffb454c418a59105f222c4'] }
204
+ end # index action, caching, xml
205
+
206
+ context "for show action with caching" do
207
+ setup do
208
+ get "/posts/#{@post1.id}", format: :json
209
+ end
210
+
211
+ asserts("contains post title") { json_output['post']['title'] }.equals { @post1.title }
212
+
213
+ asserts(:body).equals { cache_hit [@post1, nil, 'json', 'e373525f49a3b3b044af05255e84839d'] }
214
+ end # show action, caching, json
215
+
216
+ context "cache_all_output" do
217
+ helper(:cache_hit) do |key|
218
+ Rails.cache.read(ActiveSupport::Cache.expand_cache_key([key, 'article', 'json'], :rabl))
219
+ end
220
+
221
+ setup do
222
+ Rabl.configuration.cache_all_output = true
223
+ get "/posts", format: :json
224
+ end
225
+
226
+ asserts("contains cache hits per object (posts by title)") do
227
+ json_output['articles'].map { |o| o['article']['title'] }
228
+ end.equals { @posts.map{ |p| cache_hit(p)['article'][:title] } }
229
+ end # index action, cache_all_output
230
+ end
231
+
232
+ end