rabl 0.8.6 → 0.9.0.pre

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