rabl-rails-0.6.2/0000755000004100000410000000000014043362544013604 5ustar www-datawww-datarabl-rails-0.6.2/.travis.yml0000644000004100000410000000035614043362544015721 0ustar www-datawww-datalanguage: ruby cache: bundler dist: trusty env: - "RAILS_VERSION=4.2.6" - "RAILS_VERSION=5.2.0" - "RAILS_VERSION=6.1.0" rvm: - 2.5.3 - 2.6.0 - 2.7.2 - jruby before_install: - gem update bundler matrix: fast_finish: true rabl-rails-0.6.2/test/0000755000004100000410000000000014043362544014563 5ustar www-datawww-datarabl-rails-0.6.2/test/helper.rb0000644000004100000410000000262414043362544016373 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' require 'action_dispatch/http/mime_type' require 'action_view' if RUBY_ENGINE == 'jruby' require 'nokogiri' elsif RUBY_ENGINE == 'ruby' require 'libxml' end ActionView::Template.register_template_handler :rabl, RablRails::Handlers::Rabl 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.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 formats [@format] 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 end rabl-rails-0.6.2/test/test_helpers.rb0000644000004100000410000000105214043362544017607 0ustar www-datawww-datarequire 'helper' require 'set' class TestHelpers < Minitest::Test 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.6.2/test/test_library.rb0000644000004100000410000000521114043362544017612 0ustar www-datawww-datarequire 'helper' class TestLibrary < Minitest::Test 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' with_configuration :cache_templates, false do @library.compile_template_from_source("attribute :id", @context) end assert_empty @library.cached_templates end it 'caches multiple templates in one compilation' do @context.virtual_path = 'users/show' with_configuration :cache_templates, true do @library.stub :fetch_source, 'attributes :id' do @library.compile_template_from_source("child(:account, partial: 'users/_account')", @context) end end assert_equal 2, @library.cached_templates.size end end end end rabl-rails-0.6.2/test/test_hash_visitor.rb0000644000004100000410000002352714043362544020662 0ustar www-datawww-datarequire 'helper' class TestHashVisitor < Minitest::Test 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 attributes with a condition' do n = RablRails::Nodes::Attribute.new(id: :id) n.condition = lambda { |o| false } @nodes << n assert_equal({}, 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)) @address = Address.new('Paris') end it 'renders with resource association as data source' do @template.data = :address @nodes << RablRails::Nodes::Child.new(:address, @template) 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 @nodes = [RablRails::Nodes::Child.new(:address, @template)] @context.assigns['address'] = @address assert_equal({ address: { city: 'Paris' } }, visitor_result) end it 'renders with local method as data source' do @template.data = :address @nodes << RablRails::Nodes::Child.new(:address, @template) 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 @nodes << RablRails::Nodes::Child.new(:address, @template) 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 @nodes << RablRails::Nodes::Child.new(:address, @template) 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 it 'renders fetch node' do template = RablRails::CompiledTemplate.new template.add_node(RablRails::Nodes::Attribute.new(name: :name)) template.data = :@users_hash @nodes << RablRails::Nodes::Fetch.new(:user, template, :id) @context.assigns['users_hash'] = { @resource.id => @resource } assert_equal({ user: { 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 it 'renders a const node' do @nodes << RablRails::Nodes::Const.new(:locale, 'fr_FR') assert_equal({ locale: 'fr_FR' }, visitor_result) end it 'renders a positive lookup node' do @nodes << RablRails::Nodes::Lookup.new(:favorite, :@user_favorites, :id, true) @context.assigns['user_favorites'] = { 1 => true } assert_equal({ favorite: true }, visitor_result) end it 'renders a negative lookup node' do @nodes << RablRails::Nodes::Lookup.new(:favorite, :@user_favorites, :id, false) @context.assigns['user_favorites'] = { 2 => true } assert_equal({ favorite: nil }, visitor_result) 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::PartialError) { visitor_result } end it 'renders partial defined in 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 defined in node' do template = RablRails::CompiledTemplate.new template.add_node(RablRails::Nodes::Attribute.new(name: :name)) library = MiniTest::Mock.new library.expect :compile_template_from_path, template, ['users/base', @context] @nodes << RablRails::Nodes::Polymorphic.new(->(_) { 'users/base' }) RablRails::Library.stub :instance, library do assert_equal({ name: 'Marty' }, visitor_result) end library.verify end it 'allows uses of locals variables with partials' do template = RablRails::CompiledTemplate.new template.add_node(RablRails::Nodes::Code.new(:hide_comments, ->(u) { locals[:hide_comments] }, ->(u) { locals.key?(:hide_comments) })) proc = ->(u) { partial('users/locals', object: u, locals: { hide_comments: true }) } library = MiniTest::Mock.new library.expect :compile_template_from_path, template, ['users/locals', @context] @nodes << RablRails::Nodes::Code.new(:user, proc) RablRails::Library.stub :instance, library do assert_equal({ user: { hide_comments: true } }, visitor_result) end library.verify end it 'renders extend with locals' do n = RablRails::Nodes::Attribute.new(id: :id) n.condition = lambda { |_| locals[:display_id] } @nodes << RablRails::Nodes::Extend.new(n, display_id: true) assert_equal({ id: 1 }, visitor_result) @nodes.first.locals[:display_id] = false assert_equal({}, visitor_result) 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::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 end rabl-rails-0.6.2/test/test_compiler.rb0000644000004100000410000002734514043362544017774 0ustar www-datawww-datarequire 'helper' require 'pathname' require 'tmpdir' class TestCompiler < Minitest::Test @@tmp_path = Pathname.new(Dir.mktmpdir) File.open(@@tmp_path + 'user.rabl', 'w') do |f| f.puts %q{ attributes :id } end @@view_class = if ActionView::Base.respond_to?(:with_empty_template_cache) # From Rails 6.1 ActionView::Base.with_empty_template_cache else ActionView::Base end describe 'compiler' do def extract_attributes(nodes) nodes.map(&:hash) end before do @view = @@view_class.new(ActionView::LookupContext.new(@@tmp_path), {}, nil) @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 attribtues with a condition" do t = @compiler.compile_source(%( attributes :id, if: ->(o) { false } )) assert_equal([{ id: :id }], extract_attributes(t.nodes)) refute_nil t.nodes.first.condition 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 root name defined with `as` option" do t = @compiler.compile_source(%{ child(:user, as: :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 t = @compiler.compile_source(%{child(:user, :partial => 'user') }) 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 "compiles glue with a partial" do t = @compiler.compile_source(%{ glue(:@user, partial: 'user') }) glue_node = t.nodes.first assert_equal(1, glue_node.nodes.size) assert_equal([{ :id => :id }], extract_attributes(glue_node.nodes)) end it "compiles fetch with record association" do t = @compiler.compile_source(%{ fetch :address do attributes :foo end}) assert_equal(1, t.nodes.size) fetch_node = t.nodes.first assert_equal(:address, fetch_node.name) assert_equal(:address, fetch_node.data) assert_equal(:id, fetch_node.field) assert_equal([{ foo: :foo }], extract_attributes(fetch_node.nodes)) end it "compiles fetch with options" do t = @compiler.compile_source(%{ fetch(:user, as: :author, field: :uid) do attributes :foo end }) fetch_node = t.nodes.first assert_equal(:author, fetch_node.name) assert_equal(:user, fetch_node.data) assert_equal(:uid, fetch_node.field) end it "compiles constant node" do t = @compiler.compile_source(%{ const(:locale, 'fr_FR') }) const_node = t.nodes.first assert_equal :locale, const_node.name assert_equal 'fr_FR', const_node.value end it "compiles lookup node" do t = @compiler.compile_source(%{ lookup(:favorite, :@user_favorites, cast: true) }) lookup_node = t.nodes.first assert_equal :favorite, lookup_node.name assert_equal :@user_favorites, lookup_node.data assert_equal :id, lookup_node.field assert lookup_node.cast_to_boolean? end it "extends other template" do t = @compiler.compile_source(%{ extends 'user' }) assert_equal([{ :id => :id }], extract_attributes(t.nodes)) end it "extends with a lambda" do t = @compiler.compile_source(%{ extends -> { 'user' } }) node = t.nodes.first assert_instance_of(RablRails::Nodes::Polymorphic, node) assert_equal('user', node.template_lambda.call) end it "compiles extend without overwriting nodes previously defined" do File.open(@@tmp_path + 'xtnd.rabl', 'w') do |f| f.puts %q{ condition(-> { true }) { 'foo' } } end t = @compiler.compile_source(%{ condition(-> { false }) { 'bar' } extends 'xtnd' }) assert_equal(2, t.nodes.size) end it "extends template that has been compiled previously by ActionView" do t = @view.lookup_context.find_template('user') t.send(:compile!, @view) t = @compiler.compile_source(%{ extends 'user' }) assert_equal([{ :id => :id }], extract_attributes(t.nodes)) end it "compiles extends with locals" do t = @compiler.compile_source(%{ extends 'user', locals: { display_credit_card: false } }) node = t.nodes.first assert_instance_of RablRails::Nodes::Extend, node assert_equal([{ :id => :id }], extract_attributes(node.nodes)) assert_equal({ display_credit_card: false }, node.locals) 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" 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 merge with options" do t = @compiler.compile_source(%{ merge(->(m) { true }) do |m| m.foo end }) node = t.nodes.first refute_nil node.condition 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 end rabl-rails-0.6.2/test/renderers/0000755000004100000410000000000014043362544016554 5ustar www-datawww-datarabl-rails-0.6.2/test/renderers/test_json_renderer.rb0000644000004100000410000000243114043362544022777 0ustar www-datawww-datarequire 'helper' class TestJSONRenderer < Minitest::Test 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.6.2/test/renderers/test_plist_renderer.rb0000644000004100000410000000251314043362544023162 0ustar www-datawww-datarequire 'helper' class TestPListRenderer < Minitest::Test 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.6.2/test/renderers/test_xml_renderer.rb0000644000004100000410000000216014043362544022625 0ustar www-datawww-datarequire 'helper' class TestXMLRenderer < Minitest::Test 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.6.2/test/renderers/test_hash_renderer.rb0000644000004100000410000000443714043362544022761 0ustar www-datawww-datarequire 'helper' class TestHashRenderer < Minitest::Test describe 'hash renderer' do def render RablRails::Renderers::Hash.render(@template, @context, {}) 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 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 end rabl-rails-0.6.2/test/test_configuration.rb0000644000004100000410000000167714043362544021031 0ustar www-datawww-datarequire 'helper' class TestConfiguration < Minitest::Test 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 end rabl-rails-0.6.2/README.md0000644000004100000410000002535114043362544015071 0ustar www-datawww-data# RABL for Rails [![Build Status](https://travis-ci.org/ccocchi/rabl-rails.svg?branch=master)](https://travis-ci.org/ccocchi/rabl-rails) `rabl-rails` is a ruby templating system for rendering your objects in different format (JSON, XML, PLIST). This gem aims for speed and little memory footprint while letting you build complex response with a very intuitive DSL. `rabl-rails` targets **Rails 4.2/5/6 application** and have been testing with MRI and jRuby. ## Installation Install as a gem : ``` gem install rabl-rails ``` or add directly to your `Gemfile` ``` gem 'rabl-rails', '~> 0.6.0' ``` ## Overview The gem enables you to build responses using views like you would using HTML/erb/haml. As example, assuming you have a `Post` model filled with blog posts, and a `PostController` that look like this: ```ruby class PostController < ApplicationController def index @posts = Post.order('created_at DESC') 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 }] ``` ## How it works This gem separates compiling, ie. transforming a RABL-rails template into a Ruby hash, and the actual rendering of the object or collection. This allows to only compile the template once (when template caching is enabled) which is the slow part, and only use hashes during rendering. The drawback of compiling the template outside of any rendering context is that we can't access instance variables like usual. Instead, you'll mostly use symbols representing your variables and the gem will retrieve them when needed. There are places where the gem allows for "dynamic code" -- code that is evaluated at each rendering, such as within `node` or `condition` blocks. ```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 create the actual output. Currently, JSON, XML and PList 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.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. ```ruby object false node(:some_count) { |_| @user.posts.count } child(:@user) { attribute :name } ``` If you use gems like *decent_exposure* or *focused_controller*, you can use your variable directly without the leading `@` ```ruby object :object_exposed ``` ### Attributes / Methods Adds a new field to the response object, calling the method on the object being rendered. Methods called this way should return natives types from the format you're using (such as `String`, `integer`, etc for JSON). For more complex objects, see `child` nodes. ```ruby attributes :id, :title, :to_s ``` You can aliases these attributes in your response ```ruby attributes :my_custom_method, as: :title # => { "title" : } ``` or show attributes based on a condition. The currently rendered object is given to the `proc` condition. ```ruby attributes :published_at, :anchor, if: ->(post) { post.published? } ``` ### Child nodes Changes the object being rendered for the duration of the block. Depending on if you use `node` or `glue`, the result will be added as a new field or merged respectively. Data passed can be a method or a reference to an instance variable. For example if you have a `Post` model that belongs to a `User` and want to add the user's name to your response. ```ruby object :@post child(:user, as: :author) do attributes :name end # => { "post": { "author" : { "name" : "John D." } } } ``` If instead of having an `author` node in your response you wanted the name at the root level, you can use `glue`: ```ruby object :@post glue(:user) do attributes :name, as: :author_name end # => { "post": { "author_name" : "John D." } } ``` Arbitrary data source can also be passed: ```ruby # in your controller # @custom_data = [...] # in the view child(:@custom_data) do attributes :id, :name end # => { "custom_data": [...] } ``` You can use a Hash-like data source, as long as keys match a method or attribute of your main resource, using the `fetch` keyword: ```ruby # assuming you have something similar in your controller # @users_hash = { 1 => User.new(pseudo: 'Batman') } # in the view object :@post fetch(:@users_hash, as: :user, field: :user_id) do attributes :pseudo end # => { user: { pseudo: 'Batman' } } ``` This comes very handy when adding attributes from external queries not really bound to a relation, like statistics. ### Constants Adds a new field to the response using an immutable value. ```ruby const(:api_version, API::VERSION) const(:locale, 'fr_FR') ``` ### Lookups Adds a new field to the response, using rendered resource's id by default or any method to fetch a value from the given hash variable. ```ruby collection :@posts lookup(:comments_count, :@comments_count, field: :uuid, cast: false) # => [{ "comments_count": 3 }, { "comments_count": 6 }] ``` In the example above, for each post it will fetch the value from `@comments_count` using the post's `uuid` as key. When the `cast` value is set to `true` (it is `false` by default), the value will be casted to a boolean using `!!`. ### Custom nodes Adds a new field to the response with block's result as value. ```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 evaluates to a falsey value, the node will not added to the response at all. ```ruby node(:email, if: ->(u) { u.valid_email? }) do |u| u.email end ``` Nodes are evaluated at rendering time, so you can use any instance variables or view helpers within them ```ruby node(:url) { |post| post_url(post) } ``` If the result of the block is a Hash, it can be directly merge into the response using `merge` instead of `node` ```ruby object :@user merge { |u| { name: u.first_name + " " + u.last_name } } # => { "user" : { "name" : "John Doe" } } ``` ### 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/shared/_user.rabl attributes :id, :name # app/views/users/show.rabl object :@user extends('shared/_user') attributes :super_secret_attribute #=> { "id": 1, "name": "John", "super_secret_attribute": "Doe" } ``` When used with child node, if they are the only thing added you can instead use the `partial` option directly. ```ruby child(:user, partial: 'shared/_user') # is equivalent to child(:user) do extends('shared/_user') end ``` Extends can be used dynamically using rendered object and lambdas. ```ruby extends ->(user) { "shared/_#{user.client_type}_infos" } ``` 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 ``` When used this way, partials can take locals variables that can be accessed in the included template. ```ruby # _credit_card.rabl node(:credit_card, if: ->(u) { locals[:display_credit_card] }) do |user| user.credit_card_info end # user.json.rabl merge { |u| partial('_credit_card', object: u, locals: { display_credit_card: true }) } ``` ### Putting it all together `rabl-rails` allows you to format your responses easily, from simple objects to hierarchy of 2 or 3 levels. ```ruby object :@thread attribute :caption, as: :title child(:@sorted_posts, as: :posts) do attributes :title, :slug child :comments do extends 'shared/_comment' lookup(:upvotes, :@upvotes_per_comment) end end ``` ### Other features * [Caching](https://github.com/ccocchi/rabl-rails/wiki/Caching) 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.13.1 and rabl-rails 0.5.0 Overall, rabl-rails is **10% faster and use 10% less memory**, but these numbers skyrockets to **50%** when using `extends` with collection of objects. 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 © 2012-2020 Christopher Cocchi-Perrier. See [MIT-LICENSE](http://github.com/ccocchi/rabl-rails/blob/master/MIT-LICENSE) for details. rabl-rails-0.6.2/CHANGELOG.md0000644000004100000410000000675414043362544015431 0ustar www-datawww-data# CHANGELOG ## 0.6.2 * Add `fetch` node ## 0.6.1 * Fix bug when template contains double quotes ## 0.6.0 (yanked) * Remove Rails 6+ warnings * Uniformize node options * Refresh README.md ## 0.5.5 * Add `lookup` node ## 0.5.4 * Relax concurrent-ruby version dependency (javierjulio) ## 0.5.3 * Allow `extends` to accept lambdas ## 0.5.2 * Add `const` node ## 0.5.1 * Fix bug when trying to compile partials with caching enabled ## 0.5.0 * Add requirement ruby >= 2.2 * Drop support for Rails < 4.2 * Replace `thread_safe` with `concurrent_ruby` * Remove custom responder * Remove rendering outside of Rails * Improve Rails 5 compatibility ## 0.4.3 * Fix custom responder compatibility with responders 2.1 (itkin) * Fix bug when template was already loaded by ActionView and causing a nil error ## 0.4.2 * Allow to pass locals to partials * Add condition to `attributes` ## 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.6.2/.gitignore0000644000004100000410000000013414043362544015572 0ustar www-datawww-data## General log doc rdoc ## Bundler .bundle pkg Gemfile.lock .ruby-version .byebug_history rabl-rails-0.6.2/Rakefile0000755000004100000410000000136614043362544015262 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.6.2/rabl-rails.gemspec0000644000004100000410000000173414043362544017206 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 4+ templating system with JSON, XML and PList support" s.description = "Fast Rails 4+ templating system with JSON, XML and PList support" s.license = 'MIT' s.required_ruby_version = '>= 2.2.0' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- test/*`.split("\n") s.require_paths = ["lib"] s.add_dependency 'activesupport', '>= 4.2' s.add_dependency 'railties', '>= 4.2' s.add_dependency 'concurrent-ruby', '~> 1.0', ">= 1.0.2" s.add_development_dependency 'actionpack', '>= 4.2' s.add_development_dependency 'actionview', '>= 4.2' end rabl-rails-0.6.2/lib/0000755000004100000410000000000014043362544014352 5ustar www-datawww-datarabl-rails-0.6.2/lib/tasks/0000755000004100000410000000000014043362544015477 5ustar www-datawww-datarabl-rails-0.6.2/lib/tasks/rabl-rails.rake0000644000004100000410000000013014043362544020365 0ustar www-datawww-data# desc "Explaining what the task does" # task :rabl-rails do # # Task goes here # end rabl-rails-0.6.2/lib/rabl-rails.rb0000644000004100000410000000162014043362544016726 0ustar www-datawww-datarequire 'active_support' require 'rabl-rails/version' require 'rabl-rails/helpers' require 'rabl-rails/exceptions' require 'rabl-rails/template' require 'rabl-rails/nodes' require 'rabl-rails/compiler' require 'rabl-rails/visitors' require 'rabl-rails/renderers/hash' require 'rabl-rails/renderers/json' require 'rabl-rails/renderers/xml' require 'rabl-rails/renderers/plist' require 'rabl-rails/library' require 'rabl-rails/handler' if defined?(Rails) require 'rails/railtie' require 'rabl-rails/railtie' end require 'rabl-rails/configuration' begin require 'oj' Oj.default_options = { mode: :compat, time_format: :ruby } rescue LoadError require 'json' end module RablRails class << self def configure yield configuration end def configuration @_configuration ||= Configuration.new end def reset_configuration @_configuration = nil end end end rabl-rails-0.6.2/lib/rabl-rails/0000755000004100000410000000000014043362544016402 5ustar www-datawww-datarabl-rails-0.6.2/lib/rabl-rails/template.rb0000644000004100000410000000063414043362544020545 0ustar www-datawww-datamodule RablRails class CompiledTemplate attr_accessor :nodes, :data, :root_name, :cache_key def initialize @nodes = [] @data = nil @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 end rabl-rails-0.6.2/lib/rabl-rails/version.rb0000644000004100000410000000005114043362544020410 0ustar www-datawww-datamodule RablRails VERSION = '0.6.2' end rabl-rails-0.6.2/lib/rabl-rails/exceptions.rb0000644000004100000410000000007714043362544021114 0ustar www-datawww-datamodule RablRails class PartialError < StandardError; end end rabl-rails-0.6.2/lib/rabl-rails/railtie.rb0000644000004100000410000000121614043362544020360 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 if Rails::VERSION::MAJOR >= 5 module ::ActionController module ApiRendering include ActionView::Rendering end end ActiveSupport.on_load :action_controller do if self == ActionController::API include ActionController::Helpers include ActionController::ImplicitRender end end end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/0000755000004100000410000000000014043362544017512 5ustar www-datawww-datarabl-rails-0.6.2/lib/rabl-rails/nodes/polymorphic.rb0000644000004100000410000000030714043362544022404 0ustar www-datawww-datamodule RablRails module Nodes class Polymorphic attr_reader :template_lambda def initialize(template_lambda) @template_lambda = template_lambda end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/condition.rb0000644000004100000410000000032314043362544022023 0ustar www-datawww-datamodule RablRails module Nodes class Condition attr_reader :condition, :nodes def initialize(condition, nodes) @condition = condition @nodes = nodes end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/lookup.rb0000644000004100000410000000064714043362544021357 0ustar www-datawww-datamodule RablRails module Nodes class Lookup attr_reader :name, :data, :field def initialize(name, data, field, cast = false) @name = name @data = data @field = field @cast = cast @is_var = @data.to_s.start_with?('@') end def instance_variable_data? @is_var end def cast_to_boolean? @cast end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/const.rb0000644000004100000410000000027614043362544021172 0ustar www-datawww-datamodule RablRails module Nodes class Const attr_reader :name, :value def initialize(name, value) @name = name @value = value end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/child.rb0000644000004100000410000000027514043362544021126 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.6.2/lib/rabl-rails/nodes/extend.rb0000644000004100000410000000030514043362544021324 0ustar www-datawww-datamodule RablRails module Nodes class Extend attr_reader :nodes, :locals def initialize(nodes, locals) @nodes = nodes @locals = locals end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/attribute.rb0000644000004100000410000000047414043362544022047 0ustar www-datawww-datamodule RablRails module Nodes class Attribute attr_reader :hash attr_accessor :condition def initialize(hash = {}) @hash = hash end def []=(key, value) @hash[key] = value end def each(&block) @hash.each(&block) end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/code.rb0000644000004100000410000000044014043362544020747 0ustar www-datawww-datamodule RablRails module Nodes class Code attr_reader :name, :block, :condition def initialize(name, block, condition = nil) @name = name @block = block @condition = condition end def merge? !name end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/fetch.rb0000644000004100000410000000031714043362544021131 0ustar www-datawww-datamodule RablRails module Nodes class Fetch < Child attr_reader :field def initialize(name, template, field) super(name, template) @field = field end end end end rabl-rails-0.6.2/lib/rabl-rails/nodes/glue.rb0000644000004100000410000000046714043362544021002 0ustar www-datawww-datamodule RablRails module Nodes class Glue attr_reader :nodes, :data def initialize(template) @nodes = template.nodes @data = template.data @is_var = @data.to_s.start_with?('@') end def instance_variable_data? @is_var end end end end rabl-rails-0.6.2/lib/rabl-rails/helpers.rb0000644000004100000410000000040514043362544020370 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.6.2/lib/rabl-rails/visitors/0000755000004100000410000000000014043362544020264 5ustar www-datawww-datarabl-rails-0.6.2/lib/rabl-rails/visitors/visitor.rb0000644000004100000410000000055214043362544022312 0ustar www-datawww-datamodule Visitors class Visitor def visit(node) dispatch(node) end def visit_Array a a.each { |n| dispatch(n) } 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 end rabl-rails-0.6.2/lib/rabl-rails/visitors/to_hash.rb0000644000004100000410000001114214043362544022235 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 @_locals = {} copy_instance_variables_from_context end def reset_for(resource) @_resource = resource @_result = {} end def visit_Attribute n if !n.condition || instance_exec(_resource, &(n.condition)) n.each { |k, v| @_result[k] = _resource.send(v) } end end def visit_Child n object = object_from_data(_resource, n) @_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_Glue n object = object_from_data(_resource, n) @_result.merge!(sub_visit(object, n.nodes)) if object end def visit_Fetch n hash = object_from_data(_resource, n) key = _resource.public_send(n.field) object = hash[key] @_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::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_Const n @_result[n.name] = n.value end def visit_Lookup n object = object_from_data(_resource, n) key = _resource.public_send(n.field) value = object[key] value = !!value if n.cast_to_boolean? @_result[n.name] = value end def visit_Condition n @_result.merge!(sub_visit(_resource, n.nodes)) if instance_exec _resource, &(n.condition) end def visit_Extend n @_locals = n.locals @_result.merge!(sub_visit(_resource, n.nodes)) ensure @_locals = {} end def visit_Polymorphic n template_path = n.template_lambda.call(_resource) template = RablRails::Library.instance.compile_template_from_path(template_path, @_context) @_result.merge!(sub_visit(_resource, template.nodes)) end def result case RablRails.configuration.result_flags when 0 @_result when 1 @_result.each { |k, v| @_result[k] = ''.freeze if v == nil } when 2, 3 @_result.each { |k, v| @_result[k] = nil if v == ''.freeze } when 4, 5 @_result.delete_if { |_, v| v == nil } when 6 @_result.delete_if { |_, v| v == nil || v == ''.freeze } 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 def locals @_locals end # # Allow to use partial inside of node blocks (they are evaluated at # rendering time). # def partial(template_path, options = {}) raise RablRails::PartialError.new("No object was given to partial #{template_path}") unless options[:object] object = options[:object] @_locals = options[:locals].freeze 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 ensure @_locals = {} 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?('_'.freeze) } 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, node) return resource if node.data == nil symbol = node.data if node.instance_variable_data? instance_variable_get(symbol) else resource.respond_to?(symbol) ? resource.send(symbol) : @_context.send(symbol) end end end end rabl-rails-0.6.2/lib/rabl-rails/configuration.rb0000644000004100000410000000251214043362544021576 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_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 @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 result_flags @result_flags ||= begin result = 0 result |= 0b001 if @replace_nil_values_with_empty_strings result |= 0b010 if @replace_empty_string_values_with_nil result |= 0b100 if @exclude_nil_values result end end end end rabl-rails-0.6.2/lib/rabl-rails/renderers/0000755000004100000410000000000014043362544020373 5ustar www-datawww-datarabl-rails-0.6.2/lib/rabl-rails/renderers/plist.rb0000644000004100000410000000064414043362544022057 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.6.2/lib/rabl-rails/renderers/xml.rb0000644000004100000410000000064414043362544021524 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.6.2/lib/rabl-rails/renderers/hash.rb0000644000004100000410000000455514043362544021654 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 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 end rabl-rails-0.6.2/lib/rabl-rails/renderers/json.rb0000644000004100000410000000112214043362544021665 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.6.2/lib/rabl-rails/visitors.rb0000644000004100000410000000011414043362544020605 0ustar www-datawww-datarequire 'rabl-rails/visitors/visitor' require 'rabl-rails/visitors/to_hash' rabl-rails-0.6.2/lib/rabl-rails/handler.rb0000644000004100000410000000051014043362544020340 0ustar www-datawww-datarequire 'active_support/core_ext/class/attribute' module RablRails module Handlers class Rabl def self.call(template, source = nil) %{ RablRails::Library.instance. get_rendered_template(#{(source || template.source).inspect}, self, local_assigns) } end end end end rabl-rails-0.6.2/lib/rabl-rails/compiler.rb0000644000004100000410000001451714043362544020551 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 } node.condition = options[:if] 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. # Note 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 name = options[:as] if options.has_key? :as template = partial_or_block(data, options) { yield } @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, options = {}) template = partial_or_block(data, options) { yield } @template.add_node Nodes::Glue.new(template) end # # Creates a node to be added to the output by fetching an object using # current resource's field as key to the data, and appliying given # template to said object # Example: # fetch(:@stats, field: :id) { attributes :total } # def fetch(name_or_data, options = {}) data, name = extract_data_and_name(name_or_data) name = options[:as] if options.key?(:as) field = options.fetch(:field, :id) template = partial_or_block(data, options) { yield } @template.add_node Nodes::Fetch.new(name, template, field) 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 # # Creates a constant node in the json output. # Example: # const(:locale, 'fr_FR') # def const(name, value) @template.add_node Nodes::Const.new(name, value) end # # Create a node `name` by looking the current resource being rendered in the # `object` hash using, by default, the resource's id. # Example: # lookup(:favorite, :@user_favorites, cast: true) # def lookup(name, object, field: :id, cast: false) @template.add_node Nodes::Lookup.new(name, object, field, cast) end # # Merge arbitrary data into json output. Given block should # return a hash. # Example: # merge { |item| partial("specific/#{item.to_s}", object: item) } # def merge(opts = {}) return unless block_given? node(nil, opts) { yield } end # # Extends an existing rabl template # Example: # extends 'users/base' # extends ->(item) { "v1/#{item.class}/_core" } # extends 'posts/base', locals: { hide_comments: true } # def extends(path_or_lambda, options = nil) if path_or_lambda.is_a?(Proc) @template.add_node Nodes::Polymorphic.new(path_or_lambda) return end other = Library.instance.compile_template_from_path(path_or_lambda, @view) if options && options.is_a?(Hash) @template.add_node Nodes::Extend.new(other.nodes, options[:locals]) else @template.extends(other) end 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 alias_method :_if, :condition def cache(&block) @template.cache_key = block_given? ? block : nil end protected def partial_or_block(data, options) if options&.key?(:partial) template = Library.instance.compile_template_from_path(options[:partial], @view) template.data = data template elsif block_given? sub_compile(data) { yield } end end # # 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 end rabl-rails-0.6.2/lib/rabl-rails/nodes.rb0000644000004100000410000000053014043362544020035 0ustar www-datawww-datarequire 'rabl-rails/nodes/attribute' require 'rabl-rails/nodes/const' require 'rabl-rails/nodes/glue' require 'rabl-rails/nodes/child' require 'rabl-rails/nodes/code' require 'rabl-rails/nodes/condition' require 'rabl-rails/nodes/extend' require 'rabl-rails/nodes/polymorphic' require 'rabl-rails/nodes/lookup' require 'rabl-rails/nodes/fetch' rabl-rails-0.6.2/lib/rabl-rails/library.rb0000644000004100000410000000400114043362544020366 0ustar www-datawww-datarequire 'singleton' require 'monitor' 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 = {} @monitor = Monitor.new end def reset_cache! @cached_templates = {} end def get_rendered_template(source, view, locals = nil) compiled_template = compile_template_from_source(source, view) format = view.lookup_context.formats.first || :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] || @monitor.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) t = view.lookup_context.find_template(path, [], false) t = t.refresh(view) unless t.source t.source end end end rabl-rails-0.6.2/Gemfile0000644000004100000410000000073614043362544015105 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" '~> 5.2.1' else "~> #{rails_version}" end gem 'activesupport', rails gem 'railties', rails group :test do gem 'minitest', '~> 5.8' gem 'actionpack', rails gem 'actionview', rails end gem 'plist' platforms :mri do gem 'libxml-ruby' gem 'oj' end platforms :jruby do gem 'nokogiri' end rabl-rails-0.6.2/MIT-LICENSE0000644000004100000410000000205214043362544015237 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.