rabl-rails-0.4.1/0000755000004100000410000000000012472476357013615 5ustar www-datawww-datarabl-rails-0.4.1/Rakefile0000755000004100000410000000136612472476357015273 0ustar www-datawww-data#!/usr/bin/env rake # begin # require 'bundler/setup' # rescue LoadError # puts 'You must `gem install bundler` and `bundle install` to run rake tasks' # end # begin # require 'rdoc/task' # rescue LoadError # require 'rdoc/rdoc' # require 'rake/rdoctask' # RDoc::Task = Rake::RDocTask # end # # RDoc::Task.new(:rdoc) do |rdoc| # rdoc.rdoc_dir = 'rdoc' # rdoc.title = 'RablRails' # rdoc.options << '--line-numbers' # rdoc.rdoc_files.include('README.rdoc') # rdoc.rdoc_files.include('lib/**/*.rb') # end require 'bundler' Bundler::GemHelper.install_tasks require 'rake/testtask' Rake::TestTask.new(:test) do |t| t.libs << 'lib' t.libs << 'test' t.pattern = 'test/**/test_*.rb' # t.verbose = true end task :default => :test rabl-rails-0.4.1/Gemfile0000644000004100000410000000101612472476357015106 0ustar www-datawww-datasource 'http://rubygems.org' gemspec rails_version = ENV['RAILS_VERSION'] || 'default' rails = case rails_version when 'master' { github: 'rails/rails' } when "default" '~> 3.2.0' else "~> #{rails_version}" end minitest_version = rails_version == '4.0.0' ? '~> 4.7' : '~> 5.4' gem 'activesupport', rails gem 'railties', rails group :test do gem 'minitest', minitest_version end gem 'plist' platforms :ruby do gem 'oj' end platforms :mri do gem 'libxml-ruby' end platforms :jruby do gem 'nokogiri' end rabl-rails-0.4.1/MIT-LICENSE0000644000004100000410000000205212472476357015250 0ustar www-datawww-dataCopyright 2012 Christopher Cocchi-Perrier Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. rabl-rails-0.4.1/.travis.yml0000644000004100000410000000024012472476357015722 0ustar www-datawww-datalanguage: ruby env: - "RAILS_VERSION=3.2.0" - "RAILS_VERSION=4.0.0" - "RAILS_VERSION=4.1.0" rvm: - 1.9.3 - jruby-19mode - rbx-2 - 2.0.0 - 2.1.0 rabl-rails-0.4.1/lib/0000755000004100000410000000000012472476357014363 5ustar www-datawww-datarabl-rails-0.4.1/lib/rabl-rails.rb0000644000004100000410000000136612472476357016746 0ustar www-datawww-datarequire 'rails/railtie' require 'active_support' require 'rabl-rails/version' require 'rabl-rails/helpers' require 'rabl-rails/template' require 'rabl-rails/nodes' require 'rabl-rails/compiler' require 'rabl-rails/visitors' require 'rabl-rails/renderer' require 'rabl-rails/library' require 'rabl-rails/handler' require 'rabl-rails/railtie' require 'rabl-rails/configuration' begin require 'oj' Oj.default_options = { mode: :compat, time_format: :ruby } rescue LoadError require 'json' end module RablRails extend Renderer class << self def configure yield configuration end def configuration @_configuration ||= Configuration.new end def reset_configuration @_configuration = nil end end end rabl-rails-0.4.1/lib/tasks/0000755000004100000410000000000012472476357015510 5ustar www-datawww-datarabl-rails-0.4.1/lib/tasks/rabl-rails.rake0000644000004100000410000000013012472476357020376 0ustar www-datawww-data# desc "Explaining what the task does" # task :rabl-rails do # # Task goes here # end rabl-rails-0.4.1/lib/rabl-rails/0000755000004100000410000000000012472476357016413 5ustar www-datawww-datarabl-rails-0.4.1/lib/rabl-rails/visitors.rb0000644000004100000410000000011412472476357020616 0ustar www-datawww-datarequire 'rabl-rails/visitors/visitor' require 'rabl-rails/visitors/to_hash' rabl-rails-0.4.1/lib/rabl-rails/configuration.rb0000644000004100000410000000315712472476357021615 0ustar www-datawww-datarequire 'set' module RablRails class Configuration attr_accessor :json_engine, :include_json_root, :enable_jsonp_callbacks attr_accessor :xml_options attr_accessor :plist_engine, :include_plist_root attr_accessor :cache_templates attr_reader :use_custom_responder attr_accessor :responder_default_template attr_accessor :replace_nil_values_with_empty_strings attr_accessor :replace_empty_string_values_with_nil attr_accessor :exclude_nil_values attr_accessor :non_collection_classes def initialize @json_engine = defined?(::Oj) ? ::Oj : ::JSON @include_json_root = true @enable_jsonp_callbacks = false @xml_options = { dasherize: true, skip_types: false } @plist_engine = defined?(::Plist) ? ::Plist::Emit : nil @include_plist_root = false @cache_templates = ActionController::Base.perform_caching @use_custom_responder = false @responder_default_template = 'show' @replace_nil_values_with_empty_strings = false @replace_empty_string_values_with_nil = false @exclude_nil_values = false @non_collection_classes = Set.new(['Struct']) end def use_custom_responder=(value) @use_custom_responder = value require 'rabl-rails/responder' if value end def result_flags @result_flags ||= begin result = 0 result |= 0b01 if @replace_nil_values_with_empty_strings result |= 0b10 if @replace_empty_string_values_with_nil result |= 0b100 if @exclude_nil_values result end end end endrabl-rails-0.4.1/lib/rabl-rails/nodes/0000755000004100000410000000000012472476357017523 5ustar www-datawww-datarabl-rails-0.4.1/lib/rabl-rails/nodes/condition.rb0000644000004100000410000000034612472476357022041 0ustar www-datawww-datamodule RablRails module Nodes class Condition include Node attr_reader :condition, :nodes def initialize(condition, nodes) @condition = condition @nodes = nodes end end end endrabl-rails-0.4.1/lib/rabl-rails/nodes/glue.rb0000644000004100000410000000060712472476357021007 0ustar www-datawww-datamodule RablRails module Nodes class Glue include Node attr_reader :template def initialize(template) @template = template end def data @template.data end def nodes @template.nodes end def instance_variable_data? @instance_variable_data ||= data.to_s.start_with?('@') end end end endrabl-rails-0.4.1/lib/rabl-rails/nodes/child.rb0000644000004100000410000000027512472476357021137 0ustar www-datawww-datamodule RablRails module Nodes class Child < Glue attr_reader :name def initialize(name, template) super(template) @name = name end end end endrabl-rails-0.4.1/lib/rabl-rails/nodes/code.rb0000644000004100000410000000046312472476357020765 0ustar www-datawww-datamodule RablRails module Nodes class Code include Node attr_reader :name, :block, :condition def initialize(name, block, condition = nil) @name = name @block = block @condition = condition end def merge? !name end end end endrabl-rails-0.4.1/lib/rabl-rails/nodes/node.rb0000644000004100000410000000020112472476357020766 0ustar www-datawww-datamodule RablRails module Nodes module Node def accept(visitor) visitor.visit(self) end end end endrabl-rails-0.4.1/lib/rabl-rails/nodes/attribute.rb0000644000004100000410000000041212472476357022050 0ustar www-datawww-datarequire 'forwardable' module RablRails module Nodes class Attribute include Node extend Forwardable def_delegators :@hash, :[]=, :each attr_reader :hash def initialize(hash = {}) @hash = hash end end end endrabl-rails-0.4.1/lib/rabl-rails/responder.rb0000644000004100000410000000225012472476357020740 0ustar www-datawww-datamodule RablRails # # Override default responder's api behavior to not # user to_format methods on a resource as a default # representation but instead use a rabl template # class Responder < ActionController::Responder def initialize(controller, resources, options = {}) super if options[:locals] options[:locals][:resource] = resource else options[:locals] = { resource: resource } end end def to_format if get? || response_overridden? default_render elsif has_errors? display_errors else api_behavior(nil) end end protected def api_behavior(error) if post? template = if controller.respond_to?(:responder_default_template, true) controller.send(:responder_default_template) else RablRails.configuration.responder_default_template end options[:prefixes] = controller._prefixes options[:template] ||= template controller.default_render options.merge(status: :created) else head :no_content end rescue ActionView::MissingTemplate => e super(e) end end endrabl-rails-0.4.1/lib/rabl-rails/helpers.rb0000644000004100000410000000040512472476357020401 0ustar www-datawww-datamodule RablRails module Helpers def collection?(resource) klass = resource.class resource && resource.respond_to?(:each) && klass.ancestors.none? { |a| RablRails.configuration.non_collection_classes.include? a.name } end end endrabl-rails-0.4.1/lib/rabl-rails/version.rb0000644000004100000410000000005112472476357020421 0ustar www-datawww-datamodule RablRails VERSION = '0.4.1' end rabl-rails-0.4.1/lib/rabl-rails/handler.rb0000644000004100000410000000045512472476357020361 0ustar www-datawww-datarequire 'active_support/core_ext/class/attribute' module RablRails module Handlers class Rabl def self.call(template) %{ RablRails::Library.instance. get_rendered_template(#{template.source.inspect}, self, local_assigns) } end end end endrabl-rails-0.4.1/lib/rabl-rails/renderer.rb0000644000004100000410000000502612472476357020551 0ustar www-datawww-datarequire 'rabl-rails/renderers/hash' require 'rabl-rails/renderers/json' require 'rabl-rails/renderers/xml' require 'rabl-rails/renderers/plist' module RablRails module Renderer class TemplateNotFound < StandardError; end class PartialError < StandardError; end class LookupContext T = Struct.new(:source) def initialize(view_path, format) @view_path = view_path || 'app/views' @format = format.downcase end def rendered_format @format.to_sym end # # Manually find given rabl template file with given format. # View path can be set via options, otherwise default Rails # path is used # def find_template(name, opt, partial = false) paths = Dir["#@view_path/#{name}{.#@format,}.rabl"] file_path = paths.find { |path| File.exists?(path) } if file_path T.new(File.read(file_path)) else raise TemplateNotFound end end end # # Context class to emulate normal Rails view # context # class ViewContext attr_reader :format def initialize(path, options) @virtual_path = path @format = options.delete(:format) || :json @_assigns = {} @options = options options[:locals].each { |k, v| @_assigns[k.to_s] = v } if options[:locals] end def assigns @_assigns end def params { format: format } end def lookup_context @lookup_context ||= LookupContext.new(@options[:view_path], format) end end # # Renders object with the given rabl template. # # Object can also be passed as an option : # { locals: { object: obj_to_render } } # # Default render format is JSON, but can be changed via # an option: { format: 'xml' } # # If template includes uses of instance variables (usually # defined in the controller), you can passed them as locals # options. # For example, if you have this template: # object :@user # node(:read) { |u| u.has_read?(@post) } # # Your method call should look like this: # RablRails.render(user, 'users/show', locals: { post: Post.new }) # def render(object, template, options = {}) object = options[:locals].delete(:object) if !object && options[:locals] c = ViewContext.new(template, options) t = c.lookup_context.find_template(template, [], false) Library.instance.get_rendered_template(t.source, c, resource: object) end end endrabl-rails-0.4.1/lib/rabl-rails/compiler.rb0000644000004100000410000001101512472476357020550 0ustar www-datawww-datamodule RablRails # # Class that will compile RABL source code into a hash # representing data structure # class Compiler def initialize(view) @view = view end # # Compile from source code and return the CompiledTemplate # created. # def compile_source(source) @template = CompiledTemplate.new instance_eval(source) @template end # # Sets the object to be used as the data for the template # Example: # object :@user # object :@user, :root => :author # def object(data, options = {}) @template.data, @template.root_name = extract_data_and_name(data) @template.root_name = options[:root] if options.has_key? :root end alias_method :collection, :object def root(name) @template.root_name = name end # # Includes the attribute or method in the output # Example: # attributes :id, :name # attribute :email => :super_secret # def attribute(*args) node = Nodes::Attribute.new if args.first.is_a?(Hash) args.first.each_pair { |k, v| node[v] = k } else options = args.extract_options! args.each { |name| key = options[:as] || name node[key] = name } end @template.add_node node end alias_method :attributes, :attribute # # Creates a child node to be included in the output. # name_or data can be an object or collection or a method to call on the data. It # accepts :root and :partial options. # Notes that partial and blocks are not compatible # Example: # child(:@posts, :root => :posts) { attribute :id } # child(:posts, :partial => 'posts/base') # def child(name_or_data, options = {}) data, name = extract_data_and_name(name_or_data) name = options[:root] if options.has_key? :root if options.key?(:partial) template = Library.instance.compile_template_from_path(options[:partial], @view) template.data = data elsif block_given? template = sub_compile(data) { yield } end @template.add_node Nodes::Child.new(name, template) end # # Glues data from a child node to the output # Example: # glue(:@user) { attribute :name } # def glue(data) return unless block_given? template = sub_compile(data) { yield } @template.add_node Nodes::Glue.new(template) end # # Creates an arbitrary node in the json output. # It accepts :if option to create conditionnal nodes. The current data will # be passed to the block so it is advised to use it instead of ivars. # Example: # node(:name) { |user| user.first_name + user.last_name } # node(:role, if: ->(u) { !u.admin? }) { |u| u.role } # def node(name = nil, options = {}, &block) return unless block_given? @template.add_node Nodes::Code.new(name, block, options[:if]) end alias_method :code, :node # # Merge arbitrary data into json output. Given block should # return a hash. # Example: # merge { |item| partial("specific/#{item.to_s}", object: item) } # def merge return unless block_given? node(nil) { yield } end # # Extends an existing rabl template # Example: # extends 'users/base' # def extends(path) @template.extends Library.instance.compile_template_from_path(path, @view) end # # Provide a conditionnal block # # condition(->(u) { u.is_a?(Admin) }) do # attributes :secret # end # def condition(proc) return unless block_given? @template.add_node Nodes::Condition.new(proc, sub_compile(nil, true) { yield }) end def cache(&block) @template.cache_key = block_given? ? block : nil end protected # # Extract data root_name and root name # Example: # :@users -> [:@users, nil] # :@users => :authors -> [:@users, :authors] # def extract_data_and_name(name_or_data) case name_or_data when Symbol str = name_or_data.to_s str.start_with?('@') ? [name_or_data, str[1..-1]] : [name_or_data, name_or_data] when Hash name_or_data.first else name_or_data end end def sub_compile(data, only_nodes = false) raise unless block_given? old_template, @template = @template, CompiledTemplate.new yield @template.data = data only_nodes ? @template.nodes : @template ensure @template = old_template end end endrabl-rails-0.4.1/lib/rabl-rails/library.rb0000644000004100000410000000400412472476357020402 0ustar www-datawww-datarequire 'singleton' require 'monitor' require 'thread_safe' module RablRails class Library include Singleton UnknownFormat = Class.new(StandardError) RENDERER_MAP = { json: Renderers::JSON, xml: Renderers::XML, ruby: Renderers::Hash, plist: Renderers::PLIST }.freeze def initialize @cached_templates = ThreadSafe::Cache.new @mutex = Monitor.new end def reset_cache! @cached_templates = ThreadSafe::Cache.new end def get_rendered_template(source, view, locals = nil) compiled_template = compile_template_from_source(source, view) format = view.lookup_context.rendered_format || :json raise UnknownFormat, "#{format} is not supported in rabl-rails" unless RENDERER_MAP.key?(format) RENDERER_MAP[format].render(compiled_template, view, locals) end def compile_template_from_source(source, view) if RablRails.configuration.cache_templates path = view.instance_variable_get(:@virtual_path) synchronized_compile(path, source, view) else compile(source, view) end end def compile_template_from_path(path, view) if RablRails.configuration.cache_templates synchronized_compile(path, nil, view) else source = fetch_source(path, view) compile(source, view) end end private def synchronized_compile(path, source, view) @cached_templates[path] || @mutex.synchronize do # Any thread holding this lock will be compiling the template needed # by the threads waiting. So re-check the template presence to avoid # re-compilation @cached_templates.fetch(path) do source ||= fetch_source(path, view) @cached_templates[path] = compile(source, view) end end end def compile(source, view) Compiler.new(view).compile_source(source) end def fetch_source(path, view) view.lookup_context.find_template(path, [], false).source end end endrabl-rails-0.4.1/lib/rabl-rails/visitors/0000755000004100000410000000000012472476357020275 5ustar www-datawww-datarabl-rails-0.4.1/lib/rabl-rails/visitors/to_hash.rb0000644000004100000410000000676412472476357022264 0ustar www-datawww-datamodule Visitors class ToHash < Visitor include RablRails::Helpers attr_reader :_resource def initialize(view_context, resource = nil) @_context = view_context @_result = {} @_resource = resource copy_instance_variables_from_context end def reset_for(resource) @_resource = resource @_result = {} end def visit_Array n n.each { |i| visit i } end def visit_Attribute n n.each { |k, v| @_result[k] = _resource.send(v) } end def visit_Child n object = object_from_data(_resource, n.data, n.instance_variable_data?) @_result[n.name] = if object collection?(object) ? object.map { |o| sub_visit(o, n.nodes) } : sub_visit(object, n.nodes) else nil end end def visit_Code n if !n.condition || instance_exec(_resource, &(n.condition)) result = instance_exec _resource, &(n.block) if n.merge? raise RablRails::Renderer::PartialError, '`merge` block should return a hash' unless result.is_a?(Hash) @_result.merge!(result) else @_result[n.name] = result end end end def visit_Condition n @_result.merge!(sub_visit(_resource, n.nodes)) if instance_exec _resource, &(n.condition) end def visit_Glue n object = object_from_data(_resource, n.data, n.instance_variable_data?) @_result.merge! sub_visit(object, n.template.nodes) end def result case RablRails.configuration.result_flags when 0 @_result when 1 @_result.each { |k, v| @_result[k] = '' if v == nil } when 2, 3 @_result.each { |k, v| @_result[k] = nil if v == '' } when 4, 5 @_result.delete_if { |_, v| v == nil } when 6 @_result.delete_if { |_, v| v == nil || v == '' } end end protected # # If a method is called inside a 'node' property or a 'if' lambda # it will be passed to context if it exists or treated as a standard # missing method. # def method_missing(name, *args, &block) @_context.respond_to?(name) ? @_context.send(name, *args, &block) : super end # # Allow to use partial inside of node blocks (they are evaluated at # rendering time). # def partial(template_path, options = {}) raise RablRails::Renderer::PartialError.new("No object was given to partial #{template_path}") unless options[:object] object = options[:object] return [] if object.respond_to?(:empty?) && object.empty? template = RablRails::Library.instance.compile_template_from_path(template_path, @_context) if object.respond_to?(:each) object.map { |o| sub_visit o, template.nodes } else sub_visit object, template.nodes end end private def copy_instance_variables_from_context @_context.instance_variable_get(:@_assigns).each_pair { |k, v| instance_variable_set("@#{k}", v) unless k.to_s.start_with?('_') } end def sub_visit(resource, nodes) old_result, old_resource, @_result = @_result, @_resource, {} reset_for resource visit nodes result ensure @_result, @_resource = old_result, old_resource end def object_from_data(resource, symbol, is_variable) return resource if symbol == nil if is_variable instance_variable_get(symbol) else resource.respond_to?(symbol) ? resource.send(symbol) : @_context.send(symbol) end end end endrabl-rails-0.4.1/lib/rabl-rails/visitors/visitor.rb0000644000004100000410000000045012472476357022320 0ustar www-datawww-datamodule Visitors class Visitor def visit(node) dispatch node end private DISPATCH = Hash.new do |hash, node_class| hash[node_class] = "visit_#{node_class.name.split('::').last}" end def dispatch(node) send DISPATCH[node.class], node end end endrabl-rails-0.4.1/lib/rabl-rails/railtie.rb0000644000004100000410000000037512472476357020376 0ustar www-datawww-datamodule RablRails class Railtie < Rails::Railtie initializer "rabl.initialize" do |app| ActiveSupport.on_load(:action_view) do ActionView::Template.register_template_handler :rabl, RablRails::Handlers::Rabl end end end endrabl-rails-0.4.1/lib/rabl-rails/template.rb0000644000004100000410000000057512472476357020562 0ustar www-datawww-datamodule RablRails class CompiledTemplate attr_accessor :nodes, :data, :root_name, :cache_key def initialize @nodes = [] @cache_key = false end def initialize_dup(other) super self.nodes = other.nodes.dup end def add_node(n) @nodes << n end def extends(template) @nodes.concat template.nodes end end endrabl-rails-0.4.1/lib/rabl-rails/nodes.rb0000644000004100000410000000031312472476357020045 0ustar www-datawww-datarequire 'rabl-rails/nodes/node' require 'rabl-rails/nodes/attribute' require 'rabl-rails/nodes/glue' require 'rabl-rails/nodes/child' require 'rabl-rails/nodes/code' require 'rabl-rails/nodes/condition' rabl-rails-0.4.1/lib/rabl-rails/renderers/0000755000004100000410000000000012472476357020404 5ustar www-datawww-datarabl-rails-0.4.1/lib/rabl-rails/renderers/hash.rb0000644000004100000410000000465312472476357021664 0ustar www-datawww-datamodule RablRails module Renderers module Hash include ::RablRails::Helpers extend self # # Render a template. # Uses the compiled template source to get a hash with the actual # data and then format the result according to the `format_result` # method defined by the renderer. # def render(template, context, locals = nil) visitor = Visitors::ToHash.new(context) collection_or_resource = if template.data if context.respond_to?(template.data) context.send(template.data) else visitor.instance_variable_get(template.data) end end collection_or_resource ||= locals[:resource] if locals render_with_cache(template.cache_key, collection_or_resource) do output_hash = if collection?(collection_or_resource) render_collection(collection_or_resource, template.nodes, visitor) else render_resource(collection_or_resource, template.nodes, visitor) end format_output(output_hash, root_name: template.root_name, params: context.params) end end protected # # Format a hash into the desired output. # Renderer subclasses must implement this method # def format_output(hash, options = {}) hash = { options[:root_name] => hash } if options[:root_name] hash end private # # Render a single resource as a hash, according to the compiled # template source passed. # def render_resource(resource, nodes, visitor) visitor.reset_for resource visitor.visit nodes visitor.result end # # Call the render_resource mtehod on each object of the collection # and return an array of the returned values. # def render_collection(collection, nodes, visitor) collection.map { |o| render_resource(o, nodes, visitor) } end def resolve_cache_key(key, data) return data.cache_key unless key key.is_a?(Proc) ? instance_exec(data, &key) : key end private def render_with_cache(key, collection_or_resource) if !key.is_a?(FalseClass) && ActionController::Base.perform_caching Rails.cache.fetch(resolve_cache_key(key, collection_or_resource)) do yield end else yield end end end end endrabl-rails-0.4.1/lib/rabl-rails/renderers/xml.rb0000644000004100000410000000064412472476357021535 0ustar www-datawww-datarequire 'active_support/core_ext/hash/conversions' module RablRails module Renderers module XML include Renderers::Hash extend self def format_output(hash, options = {}) xml_options = { root: options[:root_name] }.merge!(RablRails.configuration.xml_options) hash.to_xml(xml_options) end def resolve_cache_key(key, data) "#{super}.xml" end end end endrabl-rails-0.4.1/lib/rabl-rails/renderers/json.rb0000644000004100000410000000112212472476357021676 0ustar www-datawww-datamodule RablRails module Renderers module JSON include Renderers::Hash extend self def format_output(hash, options = {}) hash = { options[:root_name] => hash } if options[:root_name] && RablRails.configuration.include_json_root json = RablRails.configuration.json_engine.dump(hash) params = options.fetch(:params, {}) RablRails.configuration.enable_jsonp_callbacks && params.has_key?(:callback) ? "#{params[:callback]}(#{json})" : json end def resolve_cache_key(key, data) "#{super}.json" end end end endrabl-rails-0.4.1/lib/rabl-rails/renderers/plist.rb0000644000004100000410000000064412472476357022070 0ustar www-datawww-datamodule RablRails module Renderers module PLIST include Renderers::Hash extend self def format_output(hash, options = {}) hash = { options[:root_name] => hash } if options[:root_name] && RablRails.configuration.include_plist_root RablRails.configuration.plist_engine.dump(hash) end def resolve_cache_key(key, data) "#{super}.plist" end end end endrabl-rails-0.4.1/metadata.yml0000644000004100000410000000731112472476357016122 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: rabl-rails version: !ruby/object:Gem::Version version: 0.4.1 prerelease: platform: ruby authors: - Christopher Cocchi-Perrier autorequire: bindir: bin cert_chain: [] date: 2014-12-28 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: activesupport requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '3.1' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '3.1' - !ruby/object:Gem::Dependency name: railties requirement: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '3.1' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '3.1' - !ruby/object:Gem::Dependency name: thread_safe requirement: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 0.3.1 type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement none: false requirements: - - ~> - !ruby/object:Gem::Version version: 0.3.1 description: Fast Rails 3+ templating system with JSON, XML and PList support email: - cocchi.c@gmail.com executables: [] extensions: [] extra_rdoc_files: [] files: - .gitignore - .travis.yml - CHANGELOG.md - Gemfile - MIT-LICENSE - README.md - Rakefile - lib/rabl-rails.rb - lib/rabl-rails/compiler.rb - lib/rabl-rails/configuration.rb - lib/rabl-rails/handler.rb - lib/rabl-rails/helpers.rb - lib/rabl-rails/library.rb - lib/rabl-rails/nodes.rb - lib/rabl-rails/nodes/attribute.rb - lib/rabl-rails/nodes/child.rb - lib/rabl-rails/nodes/code.rb - lib/rabl-rails/nodes/condition.rb - lib/rabl-rails/nodes/glue.rb - lib/rabl-rails/nodes/node.rb - lib/rabl-rails/railtie.rb - lib/rabl-rails/renderer.rb - lib/rabl-rails/renderers/hash.rb - lib/rabl-rails/renderers/json.rb - lib/rabl-rails/renderers/plist.rb - lib/rabl-rails/renderers/xml.rb - lib/rabl-rails/responder.rb - lib/rabl-rails/template.rb - lib/rabl-rails/version.rb - lib/rabl-rails/visitors.rb - lib/rabl-rails/visitors/to_hash.rb - lib/rabl-rails/visitors/visitor.rb - lib/tasks/rabl-rails.rake - rabl-rails.gemspec - test/helper.rb - test/renderers/test_hash_renderer.rb - test/renderers/test_json_renderer.rb - test/renderers/test_plist_renderer.rb - test/renderers/test_xml_renderer.rb - test/test_compiler.rb - test/test_configuration.rb - test/test_hash_visitor.rb - test/test_helpers.rb - test/test_library.rb - test/test_render.rb homepage: https://github.com/ccocchi/rabl-rails licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement none: false requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 1.8.23 signing_key: specification_version: 3 summary: Fast Rails 3+ templating system with JSON, XML and PList support test_files: - test/helper.rb - test/renderers/test_hash_renderer.rb - test/renderers/test_json_renderer.rb - test/renderers/test_plist_renderer.rb - test/renderers/test_xml_renderer.rb - test/test_compiler.rb - test/test_configuration.rb - test/test_hash_visitor.rb - test/test_helpers.rb - test/test_library.rb - test/test_render.rb rabl-rails-0.4.1/test/0000755000004100000410000000000012472476357014574 5ustar www-datawww-datarabl-rails-0.4.1/test/helper.rb0000644000004100000410000000257512472476357016411 0ustar www-datawww-dataENV['RAILS_ENV'] = 'test' $:.unshift File.expand_path('../../lib', __FILE__) # require 'rspec/mocks' require 'minitest/mock' require 'minitest/autorun' require 'rabl-rails' require 'plist' if RUBY_ENGINE == 'jruby' require 'nokogiri' elsif RUBY_ENGINE == 'ruby' require 'libxml' end MINITEST_TEST_CLASS = if defined?(Minitest::Test) Minitest::Test else Minitest::Unit::TestCase end module Configurable def with_configuration(key, value) accessor = "#{key}=" old_value = RablRails.configuration.send(key) RablRails.configuration.send(accessor, value) yield ensure RablRails.configuration.send(accessor, old_value) end end MINITEST_TEST_CLASS.send(:include, Configurable) module Rails def self.cache end end module ActionController module Base def self.perform_caching false end end end class Context class LookupContext def initialize(format) @format = format end def rendered_format @format.to_sym end end attr_writer :virtual_path attr_reader :lookup_context def initialize(format = :json) @_assigns = {} @virtual_path = nil @lookup_context = LookupContext.new(format) end def assigns @_assigns end def params {} end def context_method end end class User attr_accessor :id, :name def initialize(id = nil, name = nil) @id = id @name = name end endrabl-rails-0.4.1/test/test_library.rb0000644000004100000410000000437112472476357017631 0ustar www-datawww-datarequire 'helper' class TestLibrary < MINITEST_TEST_CLASS RablRails::Library.send(:attr_reader, :cached_templates) describe 'library' do before do @library = RablRails::Library.instance @library.reset_cache! @context = Context.new @template = RablRails::CompiledTemplate.new end describe '#get_rendered_template' do it 'compiles and renders template' do result = @library.stub :compile_template_from_source, @template do @library.get_rendered_template '', @context end assert_equal '{}', result end it 'uses for from lookup context' do context = Context.new(:xml) result = @library.stub :compile_template_from_source, @template do RablRails::Renderers::XML.stub :render, '' do @library.get_rendered_template '', context end end assert_equal '', result end it 'raises if format is not supported' do context = Context.new(:unsupported) @library.stub :compile_template_from_source, @template do assert_raises(RablRails::Library::UnknownFormat) { @library.get_rendered_template '', context } end end end describe '#compile_template_from_source' do it 'compiles a template' do compiler = MiniTest::Mock.new compiler.expect :compile_source, @template, ['attribute :id'] result = RablRails::Compiler.stub :new, compiler do @library.compile_template_from_source('attribute :id', @context) end assert_equal @template, result end it 'caches compiled template if option is set' do @context.virtual_path = 'users/base' template = with_configuration :cache_templates, true do @library.compile_template_from_source("attribute :id", @context) end assert_equal(template, @library.cached_templates['users/base']) end it 'compiles source without caching it if options is not set' do @context.virtual_path = 'users/base' template = with_configuration :cache_templates, false do @library.compile_template_from_source("attribute :id", @context) end assert_empty @library.cached_templates end end end endrabl-rails-0.4.1/test/test_helpers.rb0000644000004100000410000000105712472476357017625 0ustar www-datawww-datarequire 'helper' require 'set' class TestHelpers < MINITEST_TEST_CLASS include RablRails::Helpers def test_collection_with_default assert collection?(['foo']) refute collection?(User.new(1)) end NotACollection = Class.new do def each; end end def test_collection_with_configuration assert collection?(NotACollection.new) with_configuration(:non_collection_classes, Set.new(['Struct', 'TestHelpers::NotACollection'])) do refute collection?(NotACollection.new), 'NotACollection triggers #collection?' end end endrabl-rails-0.4.1/test/test_configuration.rb0000644000004100000410000000170312472476357021030 0ustar www-datawww-datarequire 'helper' class TestConfiguration < MINITEST_TEST_CLASS describe 'Configuration' do it 'has a zero score by default' do config = RablRails::Configuration.new assert_equal 0, config.result_flags end it 'sets a bit per option' do config = RablRails::Configuration.new config.replace_nil_values_with_empty_strings = true assert_equal 1, config.result_flags config = RablRails::Configuration.new config.replace_empty_string_values_with_nil = true assert_equal 2, config.result_flags config = RablRails::Configuration.new config.exclude_nil_values = true assert_equal 4, config.result_flags end it 'allows mutiple bits to be set at the same time' do config = RablRails::Configuration.new config.replace_nil_values_with_empty_strings = true config.replace_empty_string_values_with_nil = true assert_equal 3, config.result_flags end end endrabl-rails-0.4.1/test/test_render.rb0000644000004100000410000000473012472476357017443 0ustar www-datawww-datarequire 'helper' require 'pathname' require 'tmpdir' class TestRender < MINITEST_TEST_CLASS @@tmp_path = Pathname.new(Dir.mktmpdir) def setup @user = User.new(1, 'Marty') @tmp_path = @@tmp_path end def test_object_as_option File.open(@tmp_path + "nil.json.rabl", "w") do |f| f.puts %q{ object :@user attributes :name } end assert_equal %q({"user":{"name":"Marty"}}), RablRails.render(nil, 'nil', locals: { object: @user }, view_path: @tmp_path) end def test_load_source_from_file File.open(@tmp_path + "show.json.rabl", "w") do |f| f.puts %q{ object :@user attributes :id, :name } end assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path) end def test_template_not_found assert_raises(RablRails::Renderer::TemplateNotFound) { RablRails.render(@user, 'not_found') } end def test_with_locals_options File.open(@tmp_path + "instance.json.rabl", "w") do |f| f.puts %q{ object false node(:username) { |_| @user.name } } end assert_equal %q({"username":"Marty"}), RablRails.render(nil, 'instance', view_path: @tmp_path, locals: { user: @user }) end def test_extend_with_view_path File.open(@tmp_path + "extend.json.rabl", "w") do |f| f.puts %q{ object :@user extends 'base' } end File.open(@tmp_path + "base.json.rabl", "w") do |f| f.puts %q{ attribute :name, as: :extended_name } end assert_equal %q({"user":{"extended_name":"Marty"}}), RablRails.render(@user, 'extend', view_path: @tmp_path) end def test_format_as_symbol_or_string File.open(@tmp_path + "show.json.rabl", "w") do |f| f.puts %q{ object :@user attributes :id, :name } end assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path, format: :json) assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path, format: 'json') assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path, format: 'JSON') end def test_format_omitted File.open(@tmp_path + "show.rabl", "w") do |f| f.puts %q{ object :@user attributes :id, :name } end assert_equal %q({"user":{"id":1,"name":"Marty"}}), RablRails.render(@user, 'show', view_path: @tmp_path) end endrabl-rails-0.4.1/test/test_compiler.rb0000644000004100000410000002146612472476357020003 0ustar www-datawww-datarequire 'helper' class TestCompiler < MINITEST_TEST_CLASS describe 'compiler' do def extract_attributes(nodes) nodes.map(&:hash) end before do @view = Context.new @compiler = RablRails::Compiler.new(@view) end it "returns a compiled template instance" do assert_instance_of RablRails::CompiledTemplate, @compiler.compile_source("") end describe '#object' do it "sets data for the template" do t = @compiler.compile_source(%{ object :@user }) assert_equal :@user, t.data assert_equal([], t.nodes) end it "can define root name" do t = @compiler.compile_source(%{ object :@user => :author }) assert_equal :@user, t.data assert_equal :author, t.root_name assert_equal([], t.nodes) end end describe '#root' do it "defines root via keyword" do t = @compiler.compile_source(%{ root :author }) assert_equal :author, t.root_name end it "overrides object root" do t = @compiler.compile_source(%{ object :@user ; root :author }) assert_equal :author, t.root_name end it "can set root to false via options" do t = @compiler.compile_source(%( object :@user, root: false)) assert_equal false, t.root_name end end describe '#collection' do it "sets the data for the template" do t = @compiler.compile_source(%{ collection :@user }) assert_equal :@user, t.data assert_equal([], t.nodes) end it "can define root name" do t = @compiler.compile_source(%{ collection :@user => :users }) assert_equal :@user, t.data assert_equal :users, t.root_name assert_equal([], t.nodes) end it "can define root name via options" do t = @compiler.compile_source(%{ collection :@user, :root => :users }) assert_equal :@user, t.data assert_equal :users, t.root_name end end it "should not have a cache key if cache is not enable" do t = @compiler.compile_source('') assert_equal false, t.cache_key end describe '#cache' do it "can take no argument" do t = @compiler.compile_source(%{ cache }) assert_nil t.cache_key end it "sets the given block as cache key" do t = @compiler.compile_source(%( cache { 'foo' })) assert_instance_of Proc, t.cache_key end end # Compilation it "compiles single attributes" do t = @compiler.compile_source(%{ attributes :id, :name }) assert_equal([{ :id => :id, :name => :name }], extract_attributes(t.nodes)) end it "compiles attributes with the same name once" do skip('Failing') t = @compiler.compile_source(%{ attribute :id ; attribute :id }) assert_equal([{ :id => :id }], extract_attributes(t.nodes)) end it "aliases attributes through :as option" do t = @compiler.compile_source(%{ attribute :foo, :as => :bar }) assert_equal([{ :bar => :foo }], extract_attributes(t.nodes)) end it "aliases attributes through a hash" do t = @compiler.compile_source(%{ attribute :foo => :bar }) assert_equal([{ :bar => :foo }], extract_attributes(t.nodes)) end it "aliases multiple attributes" do t = @compiler.compile_source(%{ attributes :foo => :bar, :id => :uid }) assert_equal([{ :bar => :foo, :uid => :id }], extract_attributes(t.nodes)) end it "compiles child with record association" do t = @compiler.compile_source(%{ child :address do attributes :foo end}) assert_equal(1, t.nodes.size) child_node = t.nodes.first assert_equal(:address, child_node.name) assert_equal(:address, child_node.data) assert_equal([{ foo: :foo }], extract_attributes(child_node.nodes)) end it "compiles child with association aliased" do t = @compiler.compile_source(%{ child :address => :bar do attributes :foo end}) child_node = t.nodes.first assert_equal(:bar, child_node.name) assert_equal(:address, child_node.data) end it "compiles child with root name defined as option" do t = @compiler.compile_source(%{ child(:user, :root => :author) do attributes :foo end }) child_node = t.nodes.first assert_equal(:author, child_node.name) assert_equal(:user, child_node.data) end it "compiles child with arbitrary source" do t = @compiler.compile_source(%{ child :@user => :author do attribute :name end }) child_node = t.nodes.first assert_equal(:author, child_node.name) assert_equal(:@user, child_node.data) end it "compiles child with inline partial notation" do mock_template = RablRails::CompiledTemplate.new mock_template.add_node(RablRails::Nodes::Attribute.new(id: :id)) t = RablRails::Library.instance.stub :compile_template_from_path, mock_template do @compiler.compile_source(%{child(:user, :partial => 'users/base') }) end child_node = t.nodes.first assert_equal(:user, child_node.name) assert_equal(:user, child_node.data) assert_equal([{ id: :id }], extract_attributes(child_node.nodes)) end it "compiles glue as a child but without a name" do t = @compiler.compile_source(%{ glue(:@user) do attribute :name end }) assert_equal(1, t.nodes.size) glue_node = t.nodes.first assert_equal(:@user, glue_node.data) assert_equal([{ name: :name }], extract_attributes(glue_node.nodes)) end it "allows multiple glue within same template" do t = @compiler.compile_source(%{ glue :@user do attribute :name end glue :@user do attribute :foo end }) assert_equal(2, t.nodes.size) end it "compiles glue with RablRails DSL in its body" do t = @compiler.compile_source(%{ glue :@user do node(:foo) { |u| u.name } end }) glue_node = t.nodes.first assert_equal(1, glue_node.nodes.size) code_node = glue_node.nodes.first assert_instance_of(RablRails::Nodes::Code, code_node) assert_equal(:foo, code_node.name) end it "extends other template" do template = RablRails::CompiledTemplate.new template.add_node RablRails::Nodes::Attribute.new(id: :id) library = MiniTest::Mock.new library.expect :compile_template_from_path, template, ['users/base', @view] t = RablRails::Library.stub :instance, library do @compiler.compile_source(%{ extends 'users/base' }) end assert_equal([{ :id => :id }], extract_attributes(t.nodes)) library.verify end it "compiles extend without overwriting nodes previously defined" do template = RablRails::CompiledTemplate.new template.add_node(RablRails::Nodes::Condition.new(->{ true }, ->{ 'foo' })) t = RablRails::Library.instance.stub :compile_template_from_path, template do @compiler.compile_source(%{ condition(-> { false }) { 'bar' } extends('users/xtnd') }) end assert_equal(2, t.nodes.size) end it "compiles node" do t = @compiler.compile_source(%{ node(:foo) { bar } }) assert_equal(1, t.nodes.size) code_node = t.nodes.first assert_equal(:foo, code_node.name) assert_instance_of Proc, code_node.block end it "compiles node with condition option" do t = @compiler.compile_source(%{ node(:foo, :if => lambda { |m| m.foo.present? }) do |m| m.foo end }) code_node = t.nodes.first assert_instance_of Proc, code_node.condition end it "compiles node with no argument" do t = @compiler.compile_source(%{ node do |m| m.foo end }) node = t.nodes.first assert_nil node.name end it "compiles merge like a node but with a reserved keyword as name" do t = @compiler.compile_source(%{ merge do |m| m.foo end }) node = t.nodes.first assert_instance_of RablRails::Nodes::Code, node assert_nil node.name end it "compiles condition" do t = @compiler.compile_source(%{ condition(->(u) {}) do attributes :secret end }) assert_equal(1, t.nodes.size) node = t.nodes.first assert_instance_of RablRails::Nodes::Condition, node assert_equal([{ secret: :secret }], extract_attributes(node.nodes)) end it "compiles with no object" do t = @compiler.compile_source(%{ object false child(:@user => :user) do attribute :id end }) assert_equal false, t.data end describe '#extract_data_and_name' do it "extracts name from argument" do assert_equal [:@users, 'users'], @compiler.send(:extract_data_and_name, :@users) assert_equal [:users, :users], @compiler.send(:extract_data_and_name, :users) assert_equal [:@users, :authors], @compiler.send(:extract_data_and_name, :@users => :authors) end end end endrabl-rails-0.4.1/test/test_hash_visitor.rb0000644000004100000410000001556112472476357020672 0ustar www-datawww-datarequire 'helper' class TestHashVisitor < MINITEST_TEST_CLASS describe 'hash visitor' do def visitor_result visitor = Visitors::ToHash.new(@context) visitor.reset_for @resource visitor.visit @nodes visitor.result end before do @context = Context.new @resource = User.new(1, 'Marty') @nodes = [] end it 'renders empty nodes list' do assert_equal({}, visitor_result) end it 'renders attributes node' do @nodes << RablRails::Nodes::Attribute.new(id: :id) assert_equal({ id: 1 }, visitor_result) end it 'renders array of nodes' do @nodes = [ RablRails::Nodes::Attribute.new(id: :id), RablRails::Nodes::Attribute.new(name: :name) ] assert_equal({ id: 1, name: 'Marty' }, visitor_result) end describe 'with a child node' do Address = Struct.new(:city) before do @template = RablRails::CompiledTemplate.new @template.add_node(RablRails::Nodes::Attribute.new(city: :city)) @nodes << RablRails::Nodes::Child.new(:address, @template) @address = Address.new('Paris') end it 'renders with resource association as data source' do @template.data = :address def @resource.address; end @resource.stub :address, @address do assert_equal({ address: { city: 'Paris' } }, visitor_result) end end it 'renders with arbitrary data source' do @template.data = :@address @context.assigns['address'] = @address assert_equal({ address: { city: 'Paris' } }, visitor_result) end it 'renders with local method as data source' do @template.data = :address def @context.address; end @context.stub :address, @address do assert_equal({ address: { city: 'Paris' } }, visitor_result) end end it 'renders with a collection as data source' do @template.data = :address def @context.address; end @context.stub :address, [@address, @address] do assert_equal({ address: [ { city: 'Paris' }, { city: 'Paris' } ]}, visitor_result) end end it 'renders if the source is nil' do @template.data = :address def @resource.address; end @resource.stub :address, nil do assert_equal({ address: nil }, visitor_result) end end end it 'renders glue nodes' do template = RablRails::CompiledTemplate.new template.add_node(RablRails::Nodes::Attribute.new(name: :name)) template.data = :@user @nodes << RablRails::Nodes::Glue.new(template) @context.assigns['user'] = @resource assert_equal({ name: 'Marty'}, visitor_result) end describe 'with a code node' do before do @proc = ->(object) { object.name } end it 'renders the evaluated proc' do @nodes << RablRails::Nodes::Code.new(:name, @proc) assert_equal({ name: 'Marty'}, visitor_result) end it 'renders with a true condition' do @nodes << RablRails::Nodes::Code.new(:name, @proc, ->(o) { true }) assert_equal({ name: 'Marty'}, visitor_result) end it 'renders nothing with a false condition' do @nodes << RablRails::Nodes::Code.new(:name, @proc, ->(o) { false }) assert_equal({}, visitor_result) end it 'renders method called from context' do @proc = ->(object) { context_method } def @context.context_method; end @nodes = [RablRails::Nodes::Code.new(:name, @proc)] @context.stub :context_method, 'Biff' do assert_equal({ name: 'Biff'}, visitor_result) end end end describe 'with a condition node' do before do @ns = [RablRails::Nodes::Attribute.new(name: :name)] end it 'renders transparently if the condition is met' do @nodes << RablRails::Nodes::Condition.new(->(o) { true }, @ns) assert_equal({ name: 'Marty' }, visitor_result) end it 'renders nothing if the condition is not met' do @nodes << RablRails::Nodes::Condition.new(->(o) { false }, @ns) assert_equal({}, visitor_result) end end it 'renders a merge node' do proc = ->(c) { { custom: c.name } } @nodes << RablRails::Nodes::Code.new(nil, proc) assert_equal({ custom: 'Marty' }, visitor_result) end it 'raises an exception when trying to merge a non hash object' do proc = ->(c) { c.name } @nodes << RablRails::Nodes::Code.new(nil, proc) assert_raises(RablRails::Renderer::PartialError) { visitor_result } end it 'renders partial defined in code node' do template = RablRails::CompiledTemplate.new template.add_node(RablRails::Nodes::Attribute.new(name: :name)) proc = ->(u) { partial('users/base', object: u) } library = MiniTest::Mock.new library.expect :compile_template_from_path, template, ['users/base', @context] @nodes << RablRails::Nodes::Code.new(:user, proc) RablRails::Library.stub :instance, library do assert_equal({ user: { name: 'Marty' } }, visitor_result) end library.verify end it 'renders partial with empty target' do proc = ->(u) { partial('users/base', object: []) } @nodes << RablRails::Nodes::Code.new(:users, proc) assert_equal({ users: [] }, visitor_result) end it 'raises an exception when calling a partial without a target' do proc = ->(u) { partial('users/base') } @nodes << RablRails::Nodes::Code.new(:user, proc) assert_raises(RablRails::Renderer::PartialError) { visitor_result } end describe 'when hash options are set' do before do RablRails.reset_configuration @nodes << RablRails::Nodes::Attribute.new(name: :name) end after { RablRails.reset_configuration } it 'replaces nil values by strings' do RablRails.configuration.replace_nil_values_with_empty_strings = true @resource = User.new(1, nil) assert_equal({ name: '' }, visitor_result) end it 'replaces empty string by nil' do RablRails.configuration.replace_empty_string_values_with_nil = true @resource = User.new(1, '') assert_equal({ name: nil }, visitor_result) end it 'excludes nil values' do RablRails.configuration.exclude_nil_values = true @resource = User.new(1, nil) @nodes << RablRails::Nodes::Attribute.new(id: :id) assert_equal({ id: 1 }, visitor_result) end it 'excludes nil values and empty strings' do RablRails.configuration.replace_empty_string_values_with_nil = true RablRails.configuration.exclude_nil_values = true @resource = User.new(nil, '') @nodes << RablRails::Nodes::Attribute.new(id: :id) assert_equal({}, visitor_result) end end end endrabl-rails-0.4.1/test/renderers/0000755000004100000410000000000012472476357016565 5ustar www-datawww-datarabl-rails-0.4.1/test/renderers/test_plist_renderer.rb0000644000004100000410000000252012472476357023171 0ustar www-datawww-datarequire 'helper' class TestPListRenderer < MINITEST_TEST_CLASS INDENT_REGEXP = /\n(\s)*/ HEADER_REGEXP = /<\?[^>]+>]+>/ describe 'PList renderer' do def render output = RablRails::Renderers::PLIST.render(@template, @context).to_s.gsub!(INDENT_REGEXP, '') output.sub!(HEADER_REGEXP, '').gsub!(%r(]*>), '').sub!(%r(), '').sub(%r(), '') end before do @resource = User.new(1, 'Marty') @context = Context.new @context.assigns['user'] = @resource @template = RablRails::CompiledTemplate.new @template.data = :@user @template.add_node RablRails::Nodes::Attribute.new(name: :name) end it 'extends hash renderer' do RablRails::Renderers::PLIST.ancestors.include?(RablRails::Renderers::Hash) end it 'renders PList' do assert_equal %q(nameMarty), render end it 'uses template root_name option if include_plist_root is set' do @template.root_name = :user with_configuration :include_plist_root, true do assert_equal %q(usernameMarty), render end end it 'ignores template root_name by default' do @template.root_name = :user assert_equal %q(nameMarty), render end end endrabl-rails-0.4.1/test/renderers/test_hash_renderer.rb0000644000004100000410000000500612472476357022763 0ustar www-datawww-datarequire 'helper' class TestHashRenderer < MINITEST_TEST_CLASS describe 'hash renderer' do def render(locals = nil) RablRails::Renderers::Hash.render(@template, @context, locals) end def with_cache ActionController::Base.stub :perform_caching, true do Rails.stub :cache, @cache do yield end end end before do @cache = MiniTest::Mock.new @resource = User.new(1, 'Marty') @context = Context.new @context.assigns['user'] = @resource @template = RablRails::CompiledTemplate.new @template.data = :@user @template.add_node RablRails::Nodes::Attribute.new(name: :name) end describe 'cache' do it 'uses resource cache_key by default' do def @resource.cache_key; 'marty_cache' end @template.cache_key = nil @cache.expect :fetch, { user: 'Marty' }, ['marty_cache'] with_cache { assert_equal({ user: 'Marty' }, render) } @cache.verify end it 'uses template cache_key if present' do @template.cache_key = ->(u) { u.name } @cache.expect :fetch, { user: 'Marty' }, ['Marty'] with_cache { assert_equal({ user: 'Marty' }, render) } @cache.verify end end it 'uses a to_hash visitor' do visitor = MiniTest::Mock.new visitor.expect :instance_variable_get, @resource, [:@user] visitor.expect :reset_for, nil, [@resource] visitor.expect :visit, nil, [Array] visitor.expect :result, { some: 'result' } Visitors::ToHash.stub :new, visitor do assert_equal({ some: 'result' }, render) end visitor.verify end it 'retrieves data from context if exist' do @template.data = :context_method resource = User.new(2, 'Biff') @context.stub :context_method, resource do assert_equal({ name: 'Biff' }, render) end end it 'uses assigns from context if context has no data method' do assert_equal({ name: 'Marty' }, render) end it "uses resource passed in locals if template don't have data" do @template.data = nil resource = User.new(2, 'Biff') assert_equal({ name: 'Biff' }, render(resource: resource)) end it 'uses template root_name option' do @template.root_name = :user assert_equal({ user: { name: 'Marty' } }, render) end it 'renders collection' do @context.assigns['user'] = [@resource] assert_equal([{ name: 'Marty' }], render) end end endrabl-rails-0.4.1/test/renderers/test_xml_renderer.rb0000644000004100000410000000216512472476357022643 0ustar www-datawww-datarequire 'helper' class TestXMLRenderer < MINITEST_TEST_CLASS INDENT_REGEXP = /\n(\s)*/ HEADER_REGEXP = /<[^>]+>/ describe 'XML renderer' do def render RablRails::Renderers::XML.render(@template, @context).to_s.gsub!(INDENT_REGEXP, '').sub!(HEADER_REGEXP, '') end before do @resource = User.new(1, 'Marty') @context = Context.new @context.assigns['user'] = @resource @template = RablRails::CompiledTemplate.new @template.data = :@user @template.add_node RablRails::Nodes::Attribute.new(name: :name) end it 'extends hash renderer' do RablRails::Renderers::XML.ancestors.include?(RablRails::Renderers::Hash) end it 'uses global XML options' do @template.nodes = [RablRails::Nodes::Attribute.new(first_name: :name)] with_configuration :xml_options, { dasherize: false, skip_types: false } do assert_equal %q(Marty), render end end it 'uses template root_name option' do @template.root_name = :user assert_equal %q(Marty), render end end endrabl-rails-0.4.1/test/renderers/test_json_renderer.rb0000644000004100000410000000243612472476357023015 0ustar www-datawww-datarequire 'helper' class TestJSONRenderer < MINITEST_TEST_CLASS describe 'JSON renderer' do def render RablRails::Renderers::JSON.render(@template, @context) end before do @resource = User.new(1, 'Marty') @context = Context.new @context.assigns['user'] = @resource @template = RablRails::CompiledTemplate.new @template.data = :@user @template.add_node RablRails::Nodes::Attribute.new(name: :name) end it 'extends hash renderer' do RablRails::Renderers::JSON.ancestors.include?(RablRails::Renderers::Hash) end it 'renders JSON' do assert_equal %q({"name":"Marty"}), render end it 'uses template root_name option' do @template.root_name = :user assert_equal %q({"user":{"name":"Marty"}}), render end it 'ignores template root_name option if include_json_root is disabled' do @template.root_name = :user with_configuration :include_json_root, false do assert_equal %q({"name":"Marty"}), render end end it 'renders jsonp callback' do @context.stub :params, { callback: 'some_callback' } do with_configuration :enable_jsonp_callbacks, true do assert_equal %q[some_callback({"name":"Marty"})], render end end end end endrabl-rails-0.4.1/.gitignore0000644000004100000410000000007612472476357015610 0ustar www-datawww-data## General log doc rdoc ## Bundler .bundle pkg Gemfile.lock rabl-rails-0.4.1/rabl-rails.gemspec0000644000004100000410000000143512472476357017215 0ustar www-datawww-data$:.push File.expand_path("../lib", __FILE__) require "rabl-rails/version" Gem::Specification.new do |s| s.name = "rabl-rails" s.version = RablRails::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Christopher Cocchi-Perrier"] s.email = ["cocchi.c@gmail.com"] s.homepage = "https://github.com/ccocchi/rabl-rails" s.summary = "Fast Rails 3+ templating system with JSON, XML and PList support" s.description = "Fast Rails 3+ templating system with JSON, XML and PList support" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") s.require_paths = ["lib"] s.add_dependency 'activesupport', '>= 3.1' s.add_dependency 'railties', '>= 3.1' s.add_dependency 'thread_safe', '~> 0.3.1' end rabl-rails-0.4.1/CHANGELOG.md0000644000004100000410000000513512472476357015432 0ustar www-datawww-data# CHANGELOG ## 0.4.1 * Make classes that should not be treated as collection configurable * Internal change to determine rendering format ## 0.4.0 * Internal cleanup and refactor * Remove the `allow_empty_format_in_template` option, since it has become the default behavior. * Remove multi_json dependency * New options available * replace_nil_values_with_empty_strings * replace_empty_string_values_with_nil * exclude_nil_values ## 0.3.4 * Add `xml_options` option to root_level (brettallred) * Format can be omitted in template filename RablRails.allow_empty_format_in_template = true RablRails.render(user, 'show') # => app/view/user.rabl * Rails 4 support * Update travis configuration and remove warning in tests (petergoldstein) ## 0.3.3 * Add response caching ## 0.3.2 * Using child with a nil value will be correctly formatted as nil * Allow controller's assigns to have symbol keys * Does not modify in place format extracted from context * Add JSONP support ## 0.3.1 * Add `merge` keywork * Format can be passed as a string or a symbol * Avoid to unexpectedly change cached templates (johnbintz) * Add full template stack support to `glue` (fnordfish) * Allow format to be a symbol (lloydmeta) ## 0.3.0 * Travis integration * Add test for keywords used as variable names * Add PList renderer * Remove location header from post responses in responder * Fix bug with incomplete template prefixing ## 0.2.2 * Add condition blocks ## 0.2.1 * Avoid useless render on POST request with custom responder * Custom responder now fallback to Rails default in case the template is not found ## 0.2.0 * Add `root` in DSL to set root without changing the data source * Add XML renderer * Use MultiJson's preferred JSON engine as default (shmeltex) * Default template to render with responder can be set per controller * Reponder works out of the box with devise * object or collection can be skipped if use with `respond_to` blocks ## 0.1.3 * Render correctly when variables are not passed via the assigns ivar but as helper methods (decent_exposure, focused_controller) * Add custom Responder ## 0.1.2 * Add RablRails#render method (see README or source code) * Fix fail when JSON engine is not found. Now fallback to MultiJson.default_adapter * Warning message printed on logger when JSON engine fail to load ## 0.1.1 * Add CHANGELOG * Remove unused test in loop * Speed up rendering by not double copying variable from context * Rename private variable to avoid name conflict * Remove sqlite3 development dependency rabl-rails-0.4.1/README.md0000644000004100000410000002422112472476357015075 0ustar www-datawww-data# RABL for Rails [![Build Status](https://travis-ci.org/ccocchi/rabl-rails.png?branch=master)](https://travis-ci.org/ccocchi/rabl-rails) RABL (Ruby API Builder Language) is a ruby templating system for rendering resources in different format (JSON, XML, BSON, ...). You can find documentation [here](http://github.com/nesquena/rabl). rabl-rails is **faster** and uses **less memory** than the standard rabl gem while letting you access the same features. There are some slight changes to do on your templates to get this gem to work but it should't take you more than 5 minutes. rabl-rails only targets **Rails 3.2+ application** and is compatible with mri 1.9.3+, jRuby and rubinius. ## Installation Install as a gem : ``` gem install rabl-rails ``` or add directly to your `Gemfile` ``` gem 'rabl-rails' ``` And that's it ! ## Overview Once you have installed rabl-rails, you can directly used RABL-rails templates to render your resources without changing anything to you controller. As example, assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this : ```ruby class PostController < ApplicationController respond_to :html, :json, :xml def index @posts = Post.order('created_at DESC') respond_with(@posts) end end ``` You can create the following RABL-rails template to express the API output of `@posts` ```ruby # app/views/post/index.rabl collection :@posts attributes :id, :title, :subject child(:user) { attributes :full_name } node(:read) { |post| post.read_by?(@user) } ``` This would output the following JSON when visiting `http://localhost:3000/posts.json` ```js [{ "id" : 5, title: "...", subject: "...", "user" : { full_name : "..." }, "read" : true }] ``` That's a basic overview but there is a lot more to see such as partials, inheritance or fragment caching. ## How it works As opposed to standard RABL gem, this gem separate compiling (a.k.a transforming a RABL-rails template into a Ruby hash) and the actual rendering of the object or collection. This allow to only compile the template once and only Ruby hashes. The fact of compiling the template outside of any rendering context prevent us to use any instances variables (with the exception of node) in the template because they are rendering objects. So instead, you'll have to use symbols of these variables.For example, to render the collection `@posts` inside your `PostController`, you need to use `:@posts` inside of the template. The only places where you can actually used instance variables are into Proc (or lambda) or into custom node (because they are treated as Proc). ```ruby # We reference the @posts varibles that will be used at rendering time collection :@posts # Here you can use directly the instance variable because it # will be evaluated when rendering the object node(:read) { |post| post.read_by?(@user) } ``` The same rule applies for view helpers such as `current_user` After the template is compiled into a hash, Rabl-rails will use a renderer to do the actual output. Actually, only JSON and XML formats are supported. ## Configuration RablRails works out of the box, with default options and fastest engine available (oj, libxml). But depending on your needs, you might want to change that or how your output looks like. You can set global configuration in your application: ```ruby # config/initializers/rabl_rails.rb RablRails.configure do |config| # These are the default # config.cache_templates = true # config.include_json_root = true # config.json_engine = ::Oj # config.xml_options = { :dasherize => true, :skip_types => false } # config.use_custom_responder = false # config.default_responder_template = 'show' # config.enable_jsonp_callbacks = false # config.replace_nil_values_with_empty_strings = false # config.replace_empty_string_values_with_nil = false # config.exclude_nil_values = false # config.non_collection_classes = Set.new(['Struct']) end ``` ## Usage ### Data declaration To declare data to use in the template, you can use either `object` or `collection` with the symbol name or your data. ```ruby # app/views/users/show.json.rabl object :@user # app/views/users/index.json.rabl collection :@users ``` You can specify root label for the collection using hash or `:root` option ```ruby collection :@posts, root: :articles #is equivalent to collection :@posts => :articles # => { "articles" : [{...}, {...}] } ``` There are rares cases when the template doesn't map directly to any object. In these cases, you can set data to false or skip data declaration altogether. ```ruby object false node(:some_count) { |_| @user.posts.count } child(:@user) { attribute :name } ``` If you use gem like *decent_exposure* or *focused_controller*, you can use your variable directly without the leading `@` ```ruby object :object_exposed ``` You can even skip data declaration at all. If you used `respond_with`, rabl-rails will render the data you passed to it. As there is no name, you can set a root via the `root` macro. This allow you to use your template without caring about variables passed to it. ```ruby # in controller respond_with(@post) # in rabl-rails template root :article attribute :title ``` ### Attributes / Methods Basic usage is to declared attributes to include in the response. These can be database attributes or any instance method. ```ruby attributes :id, :title, :to_s ``` You can aliases these attributes in your response ```ruby attributes title: :foo, to_s: :bar # => { "foo" : , "bar" : <to_s value> } ``` ### Child nodes You can include informations from data associated with the parent model or arbitrary data. These informations can be grouped under a node or directly merged into current node. For example if you have a `Post` model that belongs to a `User` ```ruby object :@post child(user: :author) do attributes :name end # => { "post" : { "author" : { "name" : "John D." } } } ``` You can also use arbitrary data source with child nodes ```ruby child(:@users) do attributes :id, :name end ``` If you want to merge directly into current node, you can use the `glue` keywork ```ruby attribute :title glue(:user) do attributes :name => :author_name end # => { "post" : { "title" : "Foo", "author_name" : "John D." } } ``` ### Custom nodes You can create custom node in your response, based on the result of the given block. ```ruby object :@user node(:full_name) { |u| u.first_name + " " + u.last_name } # => { "user" : { "full_name" : "John Doe" } } ``` You can add condition on your custom nodes (if the condition is evaluated to false, the node will not be included). ```ruby node(:email, if: ->(u) { u.valid_email? }) do |u| u.email end ``` Nodes are evaluated at the rendering time, so you can use any instance variables or view helpers inside them ```ruby node(:url) { |post| post_url(post) } ``` If you want to include directly the result into the current node, use the `merge` keyword (result returned from the block should be a hash) ```ruby object :@user merge { |u| { name: u.first_name + " " + u.last_name } } # => { "user" : { "name" : "John Doe" } } ``` Custom nodes are really usefull to create flexible representations of your resources. ### Extends & Partials Often objects have a basic representation that is shared accross different views and enriched according to it. To avoid code redundancy you can extend your template from any other RABL template. ```ruby # app/views/users/base.json.rabl attributes :id, :name # app/views/users/private.json.rabl extends 'users/base' attributes :super_secret_attribute ``` You can also extends template in child nodes using `partial` option (this is the same as using `extends` in the child block) ```ruby collection @posts attribute :title child(:user, partial: 'users/base') ``` Partials can also be used inside custom nodes. When using partial this way, you MUST declare the object associated to the partial ```ruby node(:location) do |user| { city: user.city, address: partial('users/address', object: m.address) } end ``` ### Nesting Rabl allow you to define easily your templates, even with hierarchy of 2 or 3 levels. Let's suppose your have a `thread` model that has many `posts` and that each post has many `comments`. We can display a full thread in a few lines ```ruby object :@thread attribute :caption child :posts do attribute :title child :comments do extends 'comments/base' end end ``` ### Render object directly There are cases when you want to render object outside Rails view context. For instance to render objects in the console or to create message queue payloads. For these situations, you can use `RablRails.render` as show below: ```ruby RablRails.render(object, template, :view_path => 'app/views', :format => :json) #=> "{...}" ``` You can find more informations about how to use this method in the [wiki](http://github.com/ccocchi/rabl-rails/wiki/Render-object-directly) ### Other features * [Caching](https://github.com/ccocchi/rabl-rails/wiki/Caching) * [Custom responder](https://github.com/ccocchi/rabl-rails/wiki/Using-custom-responder) And more in the [WIKI](https://github.com/ccocchi/rabl-rails/wiki) ## Performance Benchmarks have been made using this [application](http://github.com/ccocchi/rabl-benchmark), with rabl 0.7.6 and rabl-rails 0.3.0 Overall, Rabl-rails is **20% faster and use 10% less memory**, even **twice faster** when using extends. You can see full tests on test application repository. ## Authors and contributors * [Christopher Cocchi-Perrier](http://github.com/ccocchi) - Creator of the project Want to add another format to Rabl-rails ? Checkout [JSON renderer](http://github.com/ccocchi/rabl-rails/blob/master/lib/rabl-rails/renderers/json.rb) for reference Want to make another change ? Just fork and contribute, any help is very much appreciated. If you found a bug, you can report it via the Github issues. ## Original idea * [RABL](http://github.com/nesquena/rabl) Standart RABL gem. I used it a lot but I needed to improve my API response time, and since most of the time was spent in view rendering, I decided to implement a faster rabl gem. ## Copyright Copyright © 2011-2012 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details. �����������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������������