representable-3.0.4/0000755000175000017500000000000013137322612013375 5ustar pravipravirepresentable-3.0.4/README.md0000644000175000017500000001177313137322612014665 0ustar pravipravi# Representable Representable maps Ruby objects to documents and back. [![Gitter Chat](https://badges.gitter.im/trailblazer/chat.svg)](https://gitter.im/trailblazer/chat) [![TRB Newsletter](https://img.shields.io/badge/TRB-newsletter-lightgrey.svg)](http://trailblazer.to/newsletter/) [![Build Status](https://travis-ci.org/trailblazer/representable.svg)](https://travis-ci.org/trailblazer/representable) [![Gem Version](https://badge.fury.io/rb/representable.svg)](http://badge.fury.io/rb/representable) In other words: Take an object and decorate it with a representer module. This will allow you to render a JSON, XML or YAML document from that object. But that's only half of it! You can also use representers to parse a document and create or populate an object. Representable is helpful for all kind of mappings, rendering and parsing workflows. However, it is mostly useful in API code. Are you planning to write a real REST API with representable? Then check out the [Roar](http://github.com/apotonick/roar) gem first, save work and time and make the world a better place instead. ## Full Documentation Representable comes with a rich set of options and semantics for parsing and rendering documents. Its [full documentation](http://trailblazer.to/gems/representable/3.0/api.html) can be found on the Trailblazer site. ## Example What if we're writing an API for music - songs, albums, bands. ```ruby class Song < OpenStruct end song = Song.new(title: "Fallout", track: 1) ``` ## Defining Representations Representations are defined using representer classes, called _decorator, or modules. In these examples, let's use decorators ```ruby class SongRepresenter < Representable::Decorator include Representable::JSON property :title property :track end ``` In the representer the #property method allows declaring represented attributes of the object. All the representer requires for rendering are readers on the represented object, e.g. `#title` and `#track`. When parsing, it will call setters - in our example, that'd be `#title=` and `#track=`. ## Rendering Mixing in the representer into the object adds a rendering method. ```ruby SongRepresenter.new(song).to_json #=> {"title":"Fallout","track":1} ``` ## Parsing It also adds support for parsing. ```ruby song = SongRepresenter.new(song).from_json(%{ {"title":"Roxanne"} }) #=> # ``` Note that parsing hashes per default does [require string keys](http://trailblazer.to/gems/representable/3.0/api.html#symbol-keys) and does _not_ pick up symbol keys. ## Collections Let's add a list of composers to the song representation. ```ruby class SongRepresenter < Representable::Decorator include Representable::JSON property :title property :track collection :composers end ``` Surprisingly, `#collection` lets us define lists of objects to represent. ```ruby Song.new(title: "Fallout", composers: ["Stewart Copeland", "Sting"]). extend(SongRepresenter).to_json #=> {"title":"Fallout","composers":["Stewart Copeland","Sting"]} ``` And again, this works both ways - in addition to the title it extracts the composers from the document, too. ## Nesting Representers can also manage compositions. Why not use an album that contains a list of songs? ```ruby class Album < OpenStruct end album = Album.new(name: "The Police", songs: [song, Song.new(title: "Synchronicity")]) ``` Here comes the representer that defines the composition. ```ruby class AlbumRepresenter < Representable::Decorator include Representable::JSON property :name collection :songs, decorator: SongRepresenter, class: Song end ``` ## Inline Representers If you don't want to maintain two separate modules when nesting representations you can define the `SongRepresenter` inline. ```ruby class AlbumRepresenter < Representable::Decorator include Representable::JSON property :name collection :songs, class: Song do property :title property :track collection :composers end ``` ## More Representable has many more features and can literally parse and render any kind of document to an arbitrary Ruby object graph. Please check the [official documentation for more](http://trailblazer.to/gems/representable/). ## Installation The representable gem runs with all Ruby versions >= 1.9.3. ```ruby gem 'representable' ``` ### Dependencies Representable does a great job with JSON, it also features support for XML, YAML and pure ruby hashes. But Representable did not bundle dependencies for JSON and XML. If you want to use JSON, add the following to your Gemfile: ```ruby gem 'multi_json' ``` If you want to use XML, add the following to your Gemfile: ```ruby gem 'nokogiri' ``` ## Copyright Representable started as a heavily simplified fork of the ROXML gem. Big thanks to Ben Woosley for his extremely inspiring work. * Copyright (c) 2011-2016 Nick Sutterer * ROXML is Copyright (c) 2004-2009 Ben Woosley, Zak Mandhro and Anders Engstrom. Representable is released under the [MIT License](http://www.opensource.org/licenses/MIT). representable-3.0.4/TODO0000644000175000017500000000250313137322612014065 0ustar pravipravi* Pass key/index as first block arg to :class and :extend class: |key, hsh| document `XML::AttributeHash` etc * cleanup ReadableWriteable * deprecate Representable::*::ClassMethods (::from_hash and friends) * Song < OpenStruct in test_helper * have representable-options (:include, :exclude) and user-options * make all properties "Object-like", even arrays of strings etc. This saves us from having `extend ObjectBinding if typed?` and we could just call to_hash/from_hash on all attributes. performance issues here? otherwise: implement! def compile_fragment(doc) module ReaderWriter def compile_fragment(doc) do whatever super end => do that for all "features" (what parts would that be?: getter/setter, reader/writer, readable/writeable )? * make lambda options optional (arity == 0) * pass args to methods when arity matches * DISCUSS if Decorator.new.representable_attrs != Decorator.representable_attrs ? (what about performance?) * REMOVE :from, make :a(lia)s authorative. * does :instance not work with :decorator ? * make it easy to override Binding#options via #to_hash(whatever: {hit: {decorator: HitDecorator}}) * DISCUSS: should inline representers be created at runtime, so we don't need ::representer_engine? * deprecate `Decorator::Coercion`. * cleanup XML so it matches the current #serialize standard. representable-3.0.4/representable.gemspec0000644000175000017500000000261013137322612017574 0ustar pravipravilib = File.expand_path('../lib/', __FILE__) $:.unshift lib unless $:.include?(lib) require 'representable/version' Gem::Specification.new do |spec| spec.name = "representable" spec.version = Representable::VERSION spec.platform = Gem::Platform::RUBY spec.authors = ["Nick Sutterer"] spec.email = ["apotonick@gmail.com"] spec.homepage = "https://github.com/trailblazer/representable/" spec.summary = %q{Renders and parses JSON/XML/YAML documents from and to Ruby objects. Includes plain properties, collections, nesting, coercion and more.} spec.description = spec.summary spec.files = `git ls-files`.split("\n") spec.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") spec.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } spec.require_paths = ["lib"] spec.license = "MIT" spec.required_ruby_version = '>= 1.9.3' spec.add_dependency "uber", "< 0.2.0" spec.add_dependency "declarative", "< 0.1.0" spec.add_dependency "declarative-option", "< 0.2.0" spec.add_development_dependency "rake" spec.add_development_dependency "test_xml", ">= 0.1.6" spec.add_development_dependency "minitest" spec.add_development_dependency "virtus" spec.add_development_dependency "multi_json" spec.add_development_dependency "ruby-prof" if RUBY_ENGINE == "ruby" # mri end representable-3.0.4/lib/0000755000175000017500000000000013137322612014143 5ustar pravipravirepresentable-3.0.4/lib/representable.rb0000644000175000017500000000711113137322612017323 0ustar pravipravirequire "uber/delegates" require "uber/callable" require "declarative/option" require "declarative/schema" require "representable/config" require "representable/definition" require "representable/declarative" require "representable/deserializer" require "representable/serializer" require "representable/binding" require "representable/pipeline" require "representable/insert" # Pipeline::Insert require "representable/cached" require "representable/for_collection" require "representable/represent" module Representable attr_writer :representable_attrs def self.included(base) base.class_eval do extend Declarative # make Representable horizontally and vertically inheritable. extend ModuleExtensions, ::Declarative::Heritage::Inherited, ::Declarative::Heritage::Included extend ClassMethods extend ForCollection extend Represent end end private # Reads values from +doc+ and sets properties accordingly. def update_properties_from(doc, options, format) propagated_options = normalize_options(options) representable_map!(doc, propagated_options, format, :uncompile_fragment) represented end # Compiles the document going through all properties. def create_representation_with(doc, options, format) propagated_options = normalize_options(options) representable_map!(doc, propagated_options, format, :compile_fragment) doc end class Binding::Map < Array def call(method, options) each do |bin| options[:binding] = bin # this is so much faster than options.merge(). bin.send(method, options) end end # TODO: Merge with Definitions. def <<(binding) # can be slow. this is compile time code. (existing = find { |bin| bin.name == binding.name }) ? self[index(existing)] = binding : super(binding) end end def representable_map(options, format) Binding::Map.new(representable_bindings_for(format, options)) end def representable_map!(doc, options, format, method) options = {doc: doc, options: options, represented: represented, decorator: self} representable_map(options, format).(method, options) # .(:uncompile_fragment, options) end def representable_bindings_for(format, options) representable_attrs.collect {|definition| format.build(definition) } end def normalize_options(options) return options if options.any? {user_options: {}}.merge(options) # TODO: use keyword args once we drop 2.0. end # Prepares options for a particular nested representer. # This is used in Serializer and Deserializer. OptionsForNested = ->(options, binding) do child_options = {user_options: options[:user_options], } # wrap: child_options[:wrap] = binding[:wrap] unless binding[:wrap].nil? # nested params: child_options.merge!(options[binding.name.to_sym]) if options[binding.name.to_sym] child_options end def representable_attrs @representable_attrs ||= self.class.definitions end def representation_wrap(*args) representable_attrs.wrap_for(represented, *args) end def represented self end module ModuleExtensions # Copies the representable_attrs reference to the extended object. # Note that changing attrs in the instance will affect the class configuration. def extended(object) super object.representable_attrs=(representable_attrs) # yes, we want a hard overwrite here and no inheritance. end end module ClassMethods def prepare(represented) represented.extend(self) end end # require "representable/deprecations" end require 'representable/autoload' representable-3.0.4/lib/representable/0000755000175000017500000000000013137322612016776 5ustar pravipravirepresentable-3.0.4/lib/representable/for_collection.rb0000644000175000017500000000165013137322612022326 0ustar pravipravimodule Representable # Gives us Representer::for_collection and its configuration directive # ::collection_representer. module ForCollection def for_collection # this is done at run-time, not a big fan of this. however, it saves us from inheritance/self problems. @collection_representer ||= collection_representer!({}) # DON'T make it inheritable as it would inherit the wrong singular. end private def collection_representer!(options) singular = self # what happens here is basically # Module.new { include Representable::JSON::Collection; ... } nested_builder.( _base: default_nested_class, _features: [singular.collection_representer_class], _block: ->(*) { items options.merge(:extend => singular) } ) end def collection_representer(options={}) @collection_representer = collection_representer!(options) end end endrepresentable-3.0.4/lib/representable/object/0000755000175000017500000000000013137322612020244 5ustar pravipravirepresentable-3.0.4/lib/representable/object/binding.rb0000644000175000017500000000131213137322612022200 0ustar pravipravimodule Representable module Object class Binding < Representable::Binding def self.build_for(definition) # TODO: remove default arg. return Collection.new(definition) if definition.array? new(definition) end def read(hash, as) fragment = hash.send(as) # :getter? no, that's for parsing! return FragmentNotFound if fragment.nil? and typed? fragment end def write(hash, fragment, as) true end def deserialize_method :from_object end def serialize_method :to_object end class Collection < self include Representable::Binding::Collection end end end endrepresentable-3.0.4/lib/representable/represent.rb0000644000175000017500000000030413137322612021327 0ustar pravipravimodule Representable::Represent def represent(represented, array_class=Array) return for_collection.prepare(represented) if represented.is_a?(array_class) prepare(represented) end end representable-3.0.4/lib/representable/xml.rb0000644000175000017500000000351313137322612020125 0ustar pravipravirequire 'representable' begin require 'nokogiri' rescue LoadError => _ abort "Missing dependency 'nokogiri' for Representable::XML. See dependencies section in README.md for details." end module Representable module XML def self.included(base) base.class_eval do include Representable extend ClassMethods self.representation_wrap = true # let representable compute it. register_feature Representable::XML end end module ClassMethods def remove_namespaces! representable_attrs.options[:remove_namespaces] = true end def format_engine Representable::XML end def collection_representer_class Collection end end def from_xml(doc, *args) node = parse_xml(doc, *args) from_node(node, *args) end def from_node(node, options={}) update_properties_from(node, options, Binding) end # Returns a Nokogiri::XML object representing this object. def to_node(options={}) options[:doc] = Nokogiri::XML::Document.new # DISCUSS: why do we need a fresh Document here? root_tag = options[:wrap] || representation_wrap(options) create_representation_with(Node(options[:doc], root_tag.to_s), options, Binding) end def to_xml(*args) to_node(*args).to_s end alias_method :render, :to_xml alias_method :parse, :from_xml private def remove_namespaces? # TODO: make local Config easily extendable so you get Config#remove_ns? etc. representable_attrs.options[:remove_namespaces] end def parse_xml(doc, *args) node = Nokogiri::XML(doc) node.remove_namespaces! if remove_namespaces? node.root end end end require "representable/xml/binding" require "representable/xml/collection" require "representable/xml/namespace" representable-3.0.4/lib/representable/object.rb0000644000175000017500000000121113137322612020564 0ustar pravipravirequire 'representable' require 'representable/object/binding' module Representable module Object def self.included(base) base.class_eval do include Representable extend ClassMethods register_feature Representable::Object end end module ClassMethods def collection_representer_class Collection end end def from_object(data, options={}, binding_builder=Binding) update_properties_from(data, options, binding_builder) end def to_object(options={}, binding_builder=Binding) create_representation_with(nil, options, binding_builder) end end endrepresentable-3.0.4/lib/representable/deserializer.rb0000644000175000017500000000725713137322612022020 0ustar pravipravimodule Representable # we don't use keyword args, because i didn't want to discriminate 1.9 users, yet. # this will soon get introduces and remove constructs like options[:binding][:default]. # Deprecation strategy: # binding.evaluate_option_with_deprecation(:reader, options, :doc) # => binding.evaluate_option(:reader, options) # always pass in options. AssignFragment = ->(input, options) { options[:fragment] = input } ReadFragment = ->(input, options) { options[:binding].read(input, options[:as]) } Reader = ->(input, options) { options[:binding].evaluate_option(:reader, input, options) } StopOnNotFound = ->(input, options) do Binding::FragmentNotFound == input ? Pipeline::Stop : input end StopOnNil = ->(input, options) do # DISCUSS: Not tested/used, yet. input.nil? ? Pipeline::Stop : input end OverwriteOnNil = ->(input, options) do input.nil? ? (SetValue.(input, options); Pipeline::Stop) : input end Default = ->(input, options) do Binding::FragmentNotFound == input ? options[:binding][:default] : input end SkipParse = ->(input, options) do options[:binding].evaluate_option(:skip_parse, input, options) ? Pipeline::Stop : input end module Function class Prepare def call(input, options) binding = options[:binding] binding.evaluate_option(:prepare, input, options) end end class Decorate def call(object, options) binding = options[:binding] return object unless object # object might be nil. mod = binding.evaluate_option(:extend, object, options) prepare_for(mod, object, binding) end def prepare_for(mod, object, binding) mod.prepare(object) end end end module CreateObject Instance = ->(input, options) { options[:binding].evaluate_option(:instance, input, options)|| raise( DeserializeError.new(":instance did not return class constant for `#{options[:binding].name}`.")) } Class = ->(input, options) do object_class = options[:binding].evaluate_option(:class, input, options) || raise( DeserializeError.new(":class did not return class constant for `#{options[:binding].name}`.")) object_class.new end # FIXME: no additional args passed here, yet. Populator = ->(*) { raise "Populator: implement me!" } end # CreateObject = Function::CreateObject.new Prepare = Function::Prepare.new Decorate = Function::Decorate.new Deserializer = ->(input, options) { options[:binding].evaluate_option(:deserialize, input, options) } Deserialize = ->(input, args) do binding, fragment, options = args[:binding], args[:fragment], args[:options] # user_options: child_options = OptionsForNested.(options, args[:binding]) input.send(binding.deserialize_method, fragment, child_options) end ParseFilter = ->(input, options) do options[:binding][:parse_filter].(input, options) end Setter = ->(input, options) { options[:binding].evaluate_option(:setter, input, options) } SetValue = ->(input, options) { options[:binding].send(:exec_context, options).send(options[:binding].setter, input) } Stop = ->(*) { Pipeline::Stop } If = ->(input, options) { options[:binding].evaluate_option(:if, nil, options) ? input : Pipeline::Stop } StopOnExcluded = ->(input, options) do return input unless options[:options] return input unless props = (options[:options][:exclude] || options[:options][:include]) res = props.include?(options[:binding].name.to_sym) # false with include: Stop. false with exclude: go! return input if options[:options][:include]&&res return input if options[:options][:exclude]&&!res Pipeline::Stop end endrepresentable-3.0.4/lib/representable/json.rb0000644000175000017500000000221713137322612020276 0ustar pravipravirequire "representable/hash" require "representable/json/collection" begin require "multi_json" rescue LoadError => _ abort "Missing dependency 'multi_json' for Representable::JSON. See dependencies section in README.md for details." end module Representable # Brings #to_json and #from_json to your object. module JSON extend Hash::ClassMethods include Hash def self.included(base) base.class_eval do include Representable # either in Hero or HeroRepresentation. extend ClassMethods # DISCUSS: do that only for classes? register_feature Representable::JSON end end module ClassMethods def format_engine Representable::Hash end def collection_representer_class JSON::Collection end end # Parses the body as JSON and delegates to #from_hash. def from_json(data, *args) data = MultiJson.load(data) from_hash(data, *args) end # Returns a JSON string representing this object. def to_json(*args) MultiJson.dump to_hash(*args) end alias_method :render, :to_json alias_method :parse, :from_json end end representable-3.0.4/lib/representable/yaml/0000755000175000017500000000000013137322612017740 5ustar pravipravirepresentable-3.0.4/lib/representable/yaml/binding.rb0000644000175000017500000000213413137322612021677 0ustar pravipravirequire 'representable/hash/binding' module Representable module YAML class Binding < Representable::Hash::Binding def self.build_for(definition) return Collection.new(definition) if definition.array? new(definition) end def write(map, fragment, as) map.children << Psych::Nodes::Scalar.new(as) map.children << node_for(fragment) # FIXME: should be serialize. end # private def node_for(fragment) write_scalar(fragment) end def write_scalar(value) return value if typed? Psych::Nodes::Scalar.new(value.to_s) end def serialize_method :to_ast end def deserialize_method :from_hash end class Collection < self include Representable::Binding::Collection def node_for(fragments) Psych::Nodes::Sequence.new.tap do |seq| seq.style = Psych::Nodes::Sequence::FLOW if self[:style] == :flow fragments.each { |frag| seq.children << write_scalar(frag) } end end end end end end representable-3.0.4/lib/representable/hash/0000755000175000017500000000000013137322612017721 5ustar pravipravirepresentable-3.0.4/lib/representable/hash/collection.rb0000644000175000017500000000244113137322612022402 0ustar pravipravimodule Representable::Hash module Collection include Representable::Hash def self.included(base) base.class_eval do include Representable::Hash extend ClassMethods property(:_self, {:collection => true}) end end module ClassMethods def items(options={}, &block) collection(:_self, options.merge(:getter => lambda { |*| self }), &block) end end # TODO: revise lonely collection and build separate pipeline where we just use Serialize, etc. def create_representation_with(doc, options, format) options = normalize_options(options) options[:_self] = options bin = representable_bindings_for(format, options).first Collect[*bin.default_render_fragment_functions]. (represented, {doc: doc, fragment: represented, options: options, binding: bin, represented: represented}) end def update_properties_from(doc, options, format) options = normalize_options(options) options[:_self] = options bin = representable_bindings_for(format, options).first value = Collect[*bin.default_parse_fragment_functions]. (doc, fragment: doc, document: doc, options: options, binding: bin, represented: represented) represented.replace(value) end end end representable-3.0.4/lib/representable/hash/allow_symbols.rb0000644000175000017500000000117213137322612023135 0ustar pravipravimodule Representable module Hash module AllowSymbols private def filter_wrap_for(data, *args) super(Conversion.stringify_keys(data), *args) end def update_properties_from(data, *args) super(Conversion.stringify_keys(data), *args) end end class Conversion # DISCUSS: we could think about mixin in IndifferentAccess here (either hashie or ActiveSupport). # or decorating the hash. def self.stringify_keys(hash) hash = hash.dup hash.keys.each do |k| hash[k.to_s] = hash.delete(k) end hash end end end endrepresentable-3.0.4/lib/representable/hash/binding.rb0000644000175000017500000000117413137322612021663 0ustar pravipravirequire 'representable/binding' module Representable module Hash class Binding < Representable::Binding def self.build_for(definition) return Collection.new(definition) if definition.array? new(definition) end def read(hash, as) hash.has_key?(as) ? hash[as] : FragmentNotFound end def write(hash, fragment, as) hash[as] = fragment end def serialize_method :to_hash end def deserialize_method :from_hash end class Collection < self include Representable::Binding::Collection end end end end representable-3.0.4/lib/representable/json/0000755000175000017500000000000013137322612017747 5ustar pravipravirepresentable-3.0.4/lib/representable/json/collection.rb0000644000175000017500000000027013137322612022426 0ustar pravipravimodule Representable::JSON module Collection include Representable::JSON def self.included(base) base.send :include, Representable::Hash::Collection end end end representable-3.0.4/lib/representable/json/hash.rb0000644000175000017500000000073513137322612021224 0ustar pravipravirequire 'representable/hash_methods' module Representable::JSON # "Lonely Hash" support. module Hash def self.included(base) base.class_eval do include Representable extend ClassMethods include Representable::JSON include Representable::HashMethods property(:_self, hash: true) end end module ClassMethods def values(options, &block) hash(:_self, options, &block) end end end end representable-3.0.4/lib/representable/debug.rb0000644000175000017500000000407713137322612020421 0ustar pravipravimodule Representable module Debug def update_properties_from(doc, options, format) puts puts "[Deserialize]........." puts "[Deserialize] document #{doc.inspect}" super end def create_representation_with(doc, options, format) puts puts "[Serialize]........." puts "[Serialize]" super end def representable_map(*) super.tap do |arr| arr.collect { |bin| bin.extend(Binding) } end end module Binding def evaluate_option(name, *args, &block) puts "=====#{self[name]}" if name ==:prepare puts (evaled = self[name]) ? " #evaluate_option [#{name}]: eval!!!" : " #evaluate_option [#{name}]: skipping" value = super puts " #evaluate_option [#{name}]: --> #{value}" if evaled puts " #evaluate_option [#{name}]: -->= #{args.first}" if name == :setter value end def parse_pipeline(*) super.extend(Pipeline::Debug) end def render_pipeline(*) super.extend(Pipeline::Debug) end end end module Pipeline::Debug def call(input, options) puts "Pipeline#call: #{inspect}" puts " input: #{input.inspect}" super end def evaluate(block, memo, options) block.extend(Pipeline::Debug) if block.is_a?(Collect) puts " Pipeline : -> #{_inspect_function(block)} " super.tap do |res| puts " Pipeline : result: #{res.inspect}" end end def inspect functions = collect do |func| _inspect_function(func) end.join(", ") "#{self.class.to_s.split("::").last}[#{functions}]" end # prints SkipParse instead of . i know, i can make this better, but not now. def _inspect_function(func) return func.extend(Pipeline::Debug).inspect if func.is_a?(Collect) return func unless func.is_a?(Proc) File.readlines(func.source_location[0])[func.source_location[1]-1].match(/^\s+(\w+)/)[1] end end end representable-3.0.4/lib/representable/pipeline_factories.rb0000644000175000017500000000630113137322612023167 0ustar pravipravi# NOTE: this might become a separate class, that's why it's in a separate file. module Representable module Binding::Factories def pipeline_for(name, input, options) return yield unless proc = @definition[name] # proc.(self, options) instance_exec(input, options, &proc) end # i decided not to use polymorphism here for the sake of clarity. def collect_for(item_functions) return [Collect[*item_functions]] if array? return [Collect::Hash[*item_functions]] if self[:hash] item_functions end def parse_functions [*default_parse_init_functions, *collect_for(default_parse_fragment_functions), *default_post_functions] end # DISCUSS: StopOnNil, before collect def render_functions [*default_render_init_functions, *collect_for(default_render_fragment_functions), WriteFragment] end def default_render_fragment_functions functions = [] functions << SkipRender if self[:skip_render] if typed? # TODO: allow prepare regardless of :extend, which makes it independent of typed? if self[:prepare] functions << Prepare end # functions << (self[:prepare] ? Prepare : Decorate) end functions << Decorate if self[:extend] and !self[:prepare] if representable? functions << (self[:serialize] ? Serializer : Serialize) end functions end def default_render_init_functions functions = [] functions << Stop if self[:readable]==false functions << StopOnExcluded functions << If if self[:if] functions << (self[:getter] ? Getter : GetValue) functions << Writer if self[:writer] functions << RenderFilter if self[:render_filter].any? functions << RenderDefault if has_default? functions << StopOnSkipable functions << (self[:as] ? AssignAs : AssignName) end def default_parse_init_functions functions = [] functions << Stop if self[:writeable]==false functions << StopOnExcluded functions << If if self[:if] functions << (self[:as] ? AssignAs : AssignName) functions << (self[:reader] ? Reader : ReadFragment) functions << (has_default? ? Default : StopOnNotFound) functions << OverwriteOnNil # include StopOnNil if you don't want to erase things. end def default_parse_fragment_functions functions = [AssignFragment] functions << SkipParse if self[:skip_parse] if self[:class] or self[:extend] or self[:instance] or self[:populator] if self[:populator] functions << CreateObject::Populator elsif self[:parse_strategy] functions << CreateObject::Instance # TODO: remove in 2.5. else functions << (self[:class] ? CreateObject::Class : CreateObject::Instance) end functions << Prepare if self[:prepare] functions << Decorate if self[:extend] if representable? functions << (self[:deserialize] ? Deserializer : Deserialize) end end functions end def default_post_functions funcs = [] funcs << ParseFilter if self[:parse_filter].any? funcs << (self[:setter] ? Setter : SetValue) end end endrepresentable-3.0.4/lib/representable/xml/0000755000175000017500000000000013137322612017576 5ustar pravipravirepresentable-3.0.4/lib/representable/xml/collection.rb0000644000175000017500000000112513137322612022255 0ustar pravipravimodule Representable::XML module Collection def self.included(base) base.send :include, Representable::XML base.send :include, Representable::Hash::Collection base.send :include, Methods end module Methods def create_representation_with(doc, options, format) bin = representable_map(options, format).first bin.write(doc, super, bin.name) end def update_properties_from(doc, *args) super(doc.search("./*"), *args) # pass the list of collection items to Hash::Collection#update_properties_from. end end end end representable-3.0.4/lib/representable/xml/hash.rb0000644000175000017500000000200313137322612021041 0ustar pravipravirequire 'representable/xml' require 'representable/hash_methods' module Representable::XML module AttributeHash include Representable::XML include Representable::HashMethods def self.included(base) base.class_eval do include Representable extend ClassMethods property(:_self, hash: true, use_attributes: true) end end module ClassMethods def values(options) hash :_self, options.merge!(:use_attributes => true) end end def create_representation_with(doc, options, format) bin = representable_bindings_for(format, options).first bin.write(doc, super, options) end end module Hash include Representable::XML include HashMethods def self.included(base) base.class_eval do include Representable extend ClassMethods property(:_self, {:hash => true}) end end module ClassMethods def values(options) hash :_self, options end end end end representable-3.0.4/lib/representable/xml/namespace.rb0000644000175000017500000001043113137322612022056 0ustar pravipravimodule Representable::XML # Experimental! # Best explanation so far: http://books.xmlschemata.org/relaxng/relax-CHP-11-SECT-1.html # # Note: This module doesn't work with JRuby because Nokogiri uses a completely # different implementation in Java which has other requirements that we couldn't fulfil. # Please wait for Representable 4 where we replace Nokogiri with Oga. module Namespace def self.included(includer) includer.extend(DSL) end module DSL def namespace(namespace) representable_attrs.options[:local_namespace] = namespace representable_attrs.options[:namespace_mappings] ||= {} representable_attrs.options[:namespace_mappings][namespace] = nil # this might get overwritten via #namespace_def later. end def namespace_def(mapping) namespace_defs.merge!(mapping.invert) end # :private: def namespace_defs representable_attrs.options[:namespace_mappings] ||= {} end def property(name, options={}) uri = representable_attrs.options[:local_namespace] # per default, a property belongs to the local namespace. options[:namespace] ||= uri # don't override if already set. # a nested representer is automatically assigned "its" local namespace. It's like saying # property :author, namespace: "http://ns/author" do ... end super.tap do |dfn| if dfn.typed? # FIXME: ouch, this should be doable with property's API to hook into the creation process. dfn.merge!( namespace: dfn.representer_module.representable_attrs.options[:local_namespace] ) update_namespace_defs!(namespace_defs) end end end # :private: # super ugly hack # recursively injects the namespace_defs into all representers of this tree. will be done better in 4.0. def update_namespace_defs!(namespace_defs) representable_attrs.each do |dfn| dfn.merge!(namespace_defs: namespace_defs) # this only helps with scalars if dfn.typed? representer = Class.new(dfn.representer_module) # don't pollute classes. representer.update_namespace_defs!(namespace_defs) dfn.merge!(extend: representer) end end end end module AsWithNamespace def write(doc, fragment, as) super(doc, fragment, prefixed(self, as)) end # FIXME: this is shit, the NestedOptions is executed too late here! def read(node, as) super(node, prefixed(self, as)) end private def prefixed(dfn, as) uri = dfn[:namespace] # this is generic behavior and per property prefix = dfn[:namespace_defs][uri] as = Namespace::Namespaced(prefix, as) end end # FIXME: some "bug" in Representable's XML doesn't consider the container tag, so we could theoretically pick the # wrong namespaced tag here :O def from_node(node, options={}) super end def to_node(options={}) local_uri = representable_attrs.options[:local_namespace] # every decorator MUST have a local namespace. prefix = self.class.namespace_defs[local_uri] root_tag = [prefix, representation_wrap(options)].compact.join(":") options = { wrap: root_tag }.merge(options) # TODO: there should be an easier way to pass a set of options to all nested #to_node decorators. representable_attrs.keys.each do |property| options[property.to_sym] = { show_definition: false, namespaces: options[:namespaces] } end super(options).tap do |node| add_namespace_definitions!(node, self.class.namespace_defs) unless options[:show_definition] == false end end # "Physically" add `xmlns` attributes to `node`. def add_namespace_definitions!(node, namespaces) namespaces.each do |uri, prefix| prefix = prefix.nil? ? nil : prefix.to_s node.add_namespace_definition(prefix, uri) end end def self.Namespaced(prefix, name) [ prefix, name ].compact.join(":") end # FIXME: this is a PoC, we need a better API to inject code. def representable_map(options, format) super.tap do |map| map.each { |bin| bin.extend(AsWithNamespace) unless bin.is_a?(Binding::Attribute) } end end end end representable-3.0.4/lib/representable/xml/binding.rb0000644000175000017500000001067313137322612021544 0ustar pravipravirequire 'representable/binding' require 'representable/hash/binding.rb' module Representable module XML module_function def Node(document, name, attributes={}) node = Nokogiri::XML::Node.new(name.to_s, document) # Java::OrgW3cDom::DOMException: NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces. attributes.each { |k, v| node[k] = v } # TODO: benchmark. node end class Binding < Representable::Binding def self.build_for(definition) return Collection.new(definition) if definition.array? return Hash.new(definition) if definition.hash? and not definition[:use_attributes] # FIXME: hate this. return AttributeHash.new(definition) if definition.hash? and definition[:use_attributes] return Attribute.new(definition) if definition[:attribute] return Content.new(definition) if definition[:content] new(definition) end def write(parent, fragments, as) wrap_node = parent if wrap = self[:wrap] parent << wrap_node = XML::Node(parent, wrap) end wrap_node << serialize_for(fragments, parent, as) end def read(node, as) nodes = find_nodes(node, as) return FragmentNotFound if nodes.size == 0 # TODO: write dedicated test! deserialize_from(nodes) end # Creates wrapped node for the property. def serialize_for(value, parent, as) node = XML::Node(parent, as) # node doesn't have attr="" attributes!!! serialize_node(node, value, as) end def serialize_node(node, value, as) if typed? value.name = as if as != self[:name] return value end node.content = value node end def deserialize_from(nodes) content_for(nodes.first) end # DISCUSS: why is this public? def serialize_method :to_node end def deserialize_method :from_node end private def find_nodes(doc, as) selector = as selector = "#{self[:wrap]}/#{as}" if self[:wrap] doc.xpath(selector) # nodes end def content_for(node) # TODO: move this into a ScalarDecorator. return node if typed? node.content end class Collection < self include Representable::Binding::Collection def serialize_for(value, parent, as) # return NodeSet so << works. set_for(parent, value.collect { |item| super(item, parent, as) }) end def deserialize_from(nodes) content_nodes = nodes.collect do |item| # TODO: move this to Node? content_for(item) end content_nodes end private def set_for(parent, nodes) Nokogiri::XML::NodeSet.new(parent.document, nodes) end end class Hash < Collection def serialize_for(value, parent, as) set_for(parent, value.collect do |k, v| node = XML::Node(parent, k) serialize_node(node, v, as) end) end def deserialize_from(nodes) hash = {} nodes.children.each do |node| hash[node.name] = content_for node end hash end end class AttributeHash < Collection # DISCUSS: use AttributeBinding here? def write(parent, value, as) # DISCUSS: is it correct overriding #write here? value.collect do |k, v| parent[k] = v.to_s end parent end # FIXME: this is not tested! def deserialize_from(node) HashDeserializer.new(self).deserialize(node) end end # Represents a tag attribute. Currently this only works on the top-level tag. class Attribute < self def read(node, as) node[as] end def serialize_for(value, parent, as) parent[as] = value.to_s end def write(parent, value, as) serialize_for(value, parent, as) end end # Represents tag content. class Content < self def read(node, as) node.content end def serialize_for(value, parent) parent.content = value.to_s end def write(parent, value, as) serialize_for(value, parent) end end end # Binding end end representable-3.0.4/lib/representable/serializer.rb0000644000175000017500000000351613137322612021501 0ustar pravipravimodule Representable Getter = ->(input, options) do options[:binding].evaluate_option(:getter, input, options) end GetValue = ->(input, options) { options[:binding].send(:exec_context, options).public_send(options[:binding].getter) } Writer = ->(input, options) do options[:binding].evaluate_option(:writer, input, options) Pipeline::Stop end # TODO: evaluate this, if we need this. RenderDefault = ->(input, options) do binding = options[:binding] binding.skipable_empty_value?(input) ? binding[:default] : input end StopOnSkipable = ->(input, options) do options[:binding].send(:skipable_empty_value?, input) ? Pipeline::Stop : input end RenderFilter = ->(input, options) do options[:binding][:render_filter].(input, options) end SkipRender = ->(input, options) do options[:binding].evaluate_option(:skip_render, input, options) ? Pipeline::Stop : input end Serializer = ->(input, options) do return if input.nil? # DISCUSS: how can we prevent that? options[:binding].evaluate_option(:serialize, input, options) end Serialize = ->(input, options) do return if input.nil? # DISCUSS: how can we prevent that? binding, options = options[:binding], options[:options] # FIXME: rename to :local_options. options_for_nested = OptionsForNested.(options, binding) input.send(binding.serialize_method, options_for_nested) end WriteFragment = ->(input, options) { options[:binding].write(options[:doc], input, options[:as]) } As = ->(input, options) { options[:binding].evaluate_option(:as, input, options) } # Warning: don't rely on AssignAs/AssignName, i am not sure if i leave that as functions. AssignAs = ->(input, options) { options[:as] = As.(input, options); input } AssignName = ->(input, options) { options[:as] = options[:binding].name; input } end representable-3.0.4/lib/representable/hash_methods.rb0000644000175000017500000000227513137322612021777 0ustar pravipravimodule Representable module HashMethods def create_representation_with(doc, options, format) hash = filter_keys_for!(represented, options) # FIXME: this modifies options and replicates logic from Representable. bin = representable_map(options, format).first Collect::Hash[*bin.default_render_fragment_functions].(hash, {doc: doc, options: options, binding: bin, represented: represented, decorator: self}) end def update_properties_from(doc, options, format) hash = filter_keys_for!(doc, options) bin = representable_map(options, format).first value = Collect::Hash[*bin.default_parse_fragment_functions].(hash, fragment: hash, document: doc, binding: bin, represented: represented, options: options, decorator: self) represented.replace(value) end private def filter_keys_for!(hash, options) excluding = options[:exclude] # TODO: use same filtering method as in normal representer in Representable#create_representation_with. return hash unless props = options.delete(:exclude) || options.delete(:include) hash.reject { |k,v| excluding ? props.include?(k.to_sym) : !props.include?(k.to_sym) } end end end representable-3.0.4/lib/representable/declarative.rb0000644000175000017500000000324513137322612021612 0ustar pravipravimodule Representable module Declarative def representation_wrap=(name) heritage.record(:representation_wrap=, name) definitions.wrap = name end def collection(name, options={}, &block) property(name, options.merge(collection: true), &block) end def hash(name=nil, options={}, &block) return super() unless name # allow Object.hash. options[:hash] = true property(name, options, &block) end # Allows you to nest a block of properties in a separate section while still mapping # them to the original object. def nested(name, options={}, &block) options = options.merge( getter: ->(opts) { self }, setter: ->(opts) { }, instance: ->(opts) { self }, ) if block options[:_nested_builder] = Decorator.nested_builder options[:_base] = Decorator.default_nested_class end property(name, options, &block) end include ::Declarative::Schema::DSL # ::property include ::Declarative::Schema::Feature include ::Declarative::Heritage::DSL def default_nested_class Module.new # FIXME: make that unnecessary in Declarative end NestedBuilder = ->(options) do Module.new do include Representable # FIXME: do we really need this? feature(*options[:_features]) include(*options[:_base]) # base when :inherit, or in decorator. module_eval(&options[:_block]) end end def nested_builder NestedBuilder end def definitions @definitions ||= Config.new(Representable::Definition) end alias_method :representable_attrs, :definitions end endrepresentable-3.0.4/lib/representable/insert.rb0000644000175000017500000000200513137322612020624 0ustar pravipravimodule Representable class Pipeline < Array # i hate that. module Function class Insert def call(arr, func, options) arr = arr.dup delete!(arr, func) if options[:delete] replace!(arr, options[:replace], func) if options[:replace] arr end private def replace!(arr, old_func, new_func) arr.each_with_index { |func, index| if func.is_a?(Collect) arr[index] = Collect[*Pipeline::Insert.(func, new_func, replace: old_func)] end arr[index] = new_func if func==old_func } end def delete!(arr, removed_func) arr.delete(removed_func) # TODO: make nice. arr.each_with_index { |func, index| if func.is_a?(Collect) arr[index] = Collect[*Pipeline::Insert.(func, removed_func, delete: true)] end } end end end Insert = Pipeline::Function::Insert.new end # Pipeline endrepresentable-3.0.4/lib/representable/populator.rb0000644000175000017500000000253013137322612021350 0ustar pravipravimodule Representable class Populator FindOrInstantiate = ->(input, options) { binding = options[:binding] object_class = binding[:class].(input, options) object = object_class.find_by(id: input["id"]) || object_class.new if options[:binding].array? # represented.songs[i] = model options[:represented].send(binding.getter)[options[:index]] = object else # represented.song = model options[:represented].send(binding.setter, object) end object } # pipeline: [StopOnExcluded, AssignName, ReadFragment, StopOnNotFound, OverwriteOnNil, AssignFragment, #, #, Deserialize, Set] def self.apply!(options) return unless populator = options[:populator] options[:parse_pipeline] = ->(input, opts) do pipeline = Pipeline[*parse_functions] # TODO: AssignFragment pipeline = Pipeline::Insert.(pipeline, SetValue, delete: true) # remove the setter function. pipeline = Pipeline::Insert.(pipeline, populator, replace: CreateObject::Populator) # let the actual populator do the job. # puts pipeline.extend(Representable::Pipeline::Debug).inspect pipeline end end end FindOrInstantiate = Populator::FindOrInstantiate end representable-3.0.4/lib/representable/decorator.rb0000644000175000017500000000225213137322612021306 0ustar pravipravirequire "representable" require "uber/inheritable_attr" module Representable class Decorator attr_reader :represented alias_method :decorated, :represented # TODO: when moving all class methods into ClassMethods, i get a segfault. def self.prepare(represented) new(represented) end def self.default_nested_class #FIXME. SHOULD we move that into NestedBuilder? Representable::Decorator end # extend ::Declarative::Heritage::Inherited # DISCUSS: currently, that is handled via Representable::inherited. # This is called from inheritable_attr when inheriting a decorator class to a subclass. # Explicitly subclassing the Decorator makes sure representable_attrs is a clean version. def self.clone Class.new(self) end include Representable # include after class methods so Decorator::prepare can't be overwritten by Representable::prepare. include Cached extend Uber::InheritableAttr inheritable_attr :map self.map = Binding::Map.new def initialize(represented) @represented = represented end def self.nested_builder ::Declarative::Schema::DSL::NestedBuilder end end end representable-3.0.4/lib/representable/hash.rb0000644000175000017500000000311513137322612020246 0ustar pravipravirequire 'representable' require 'representable/hash/binding' module Representable # The generic representer. Brings #to_hash and #from_hash to your object. # If you plan to write your own representer for a new media type, try to use this module (e.g., check how JSON reuses Hash's internal # architecture). module Hash def self.included(base) base.class_eval do include Representable # either in Hero or HeroRepresentation. extend ClassMethods register_feature Representable::Hash end end module ClassMethods def format_engine Representable::Hash end def collection_representer_class Collection end end def from_hash(data, options={}, binding_builder=Binding) data = filter_wrap(data, options) update_properties_from(data, options, binding_builder) end def to_hash(options={}, binding_builder=Binding) hash = create_representation_with({}, options, binding_builder) return hash if options[:wrap] == false return hash unless wrap = options[:wrap] || representation_wrap(options) {wrap => hash} end alias_method :render, :to_hash alias_method :parse, :from_hash private def filter_wrap(data, options) return data if options[:wrap] == false return data unless wrap = options[:wrap] || representation_wrap(options) filter_wrap_for(data, wrap) end def filter_wrap_for(data, wrap) data[wrap.to_s] || {} # DISCUSS: don't initialize this more than once. # TODO: this should be done with #read. end end end representable-3.0.4/lib/representable/yaml.rb0000644000175000017500000000175113137322612020271 0ustar pravipravirequire 'psych' require 'representable/hash' require 'representable/yaml/binding' module Representable module YAML include Hash def self.included(base) base.class_eval do include Representable register_feature Representable::YAML extend ClassMethods end end module ClassMethods def format_engine Representable::YAML end end def from_yaml(doc, options={}) hash = Psych.load(doc) from_hash(hash, options, Binding) end # Returns a Nokogiri::XML object representing this object. def to_ast(options={}) Psych::Nodes::Mapping.new.tap do |map| create_representation_with(map, options, Binding) end end def to_yaml(*args) stream = Psych::Nodes::Stream.new stream.children << doc = Psych::Nodes::Document.new doc.children << to_ast(*args) stream.to_yaml end alias_method :render, :to_yaml alias_method :parse, :from_yaml end end representable-3.0.4/lib/representable/coercion.rb0000644000175000017500000000156213137322612021130 0ustar pravipravirequire "virtus" module Representable module Coercion class Coercer def initialize(type) @type = type end # This gets called when the :render_filter or :parse_filter option is evaluated. # Usually the Coercer instance is an element in a Pipeline to allow >1 filters per property. def call(input, options) Virtus::Attribute.build(@type).coerce(input) end end def self.included(base) base.class_eval do extend ClassMethods register_feature Coercion end end module ClassMethods def property(name, options={}, &block) super.tap do |definition| return definition unless type = options[:type] definition.merge!(render_filter: coercer = Coercer.new(type)) definition.merge!(parse_filter: coercer) end end end end endrepresentable-3.0.4/lib/representable/pipeline.rb0000644000175000017500000000274413137322612021137 0ustar pravipravimodule Representable # Allows to implement a pipeline of filters where a value gets passed in and the result gets # passed to the next callable object. class Pipeline < Array Stop = Class.new # options is mutuable. def call(input, options) inject(input) do |memo, block| res = evaluate(block, memo, options) return(Stop)if Stop == res res end end private def evaluate(block, input, options) block.call(input, options) end module Macros # TODO: explicit test. # Macro to quickly modify an array of functions via Pipeline::Insert and return a # Pipeline instance. def insert(functions, new_function, options) Pipeline.new(Pipeline::Insert.(functions, new_function, options)) end end extend Macros end # Pipeline # Collect applies a pipeline to each element of input. class Collect < Pipeline # when stop, the element is skipped. (should that be Skip then?) def call(input, options) arr = [] input.each_with_index do |item_fragment, i| result = super(item_fragment, options.merge(index: i)) # DISCUSS: NO :fragment set. Pipeline::Stop == result ? next : arr << result end arr end class Hash < Pipeline def call(input, options) {}.tap do |hsh| input.each { |key, item_fragment| hsh[key] = super(item_fragment, options) }# DISCUSS: NO :fragment set. end end end end end representable-3.0.4/lib/representable/definition.rb0000644000175000017500000000616413137322612021462 0ustar pravipravirequire "representable/populator" module Representable # Created at class compile time. Keeps configuration options for one property. class Definition < ::Declarative::Definitions::Definition def initialize(sym, options={}, &block) options[:extend] = options[:nested] if options[:nested] super # defaults: options[:parse_filter] = Pipeline[*options[:parse_filter]] options[:render_filter] = Pipeline[*options[:render_filter]] setup!(options, &block) end def name self[:name] end alias_method :getter, :name def merge!(options, &block) options = options.clone options[:parse_filter] = @options[:parse_filter].push(*options[:parse_filter]) options[:render_filter] = @options[:render_filter].push(*options[:render_filter]) setup!(options, &block) # FIXME: this doesn't yield :as etc. self end def delete!(name) @runtime_options.delete(name) @options.delete(name) self end def [](name) # return nil if name==:extend && self[:nested].nil? # return Uber::Options::Value.new(self[:nested]) if name==:extend @runtime_options[name] end def setter :"#{name}=" end def typed? # TODO: remove. self[:class] or self[:extend] or self[:instance] end def representable? return if self[:representable] == false self[:representable] or typed? end def array? self[:collection] end def hash? self[:hash] end def has_default? @options.has_key?(:default) end def representer_module @options[:extend] end def create_binding(*args) self[:binding].call(self, *args) end def inspect state = (instance_variables-[:@runtime_options, :@name]).collect { |ivar| "#{ivar}=#{instance_variable_get(ivar)}" } "##{name} #{state.join(" ")}>" end private def setup!(options, &block) handle_extend!(options) handle_as!(options) # DISCUSS: we could call more macros here (e.g. for :nested). Representable::Populator.apply!(options) yield options if block_given? @options.merge!(options) runtime_options!(@options) end # wrapping dynamic options in Value does save runtime, as this is used very frequently (and totally unnecessary to wrap an option # at runtime, its value never changes). def runtime_options!(options) @runtime_options = {} for name, value in options value = ::Declarative::Option(value, instance_exec: true, callable: Uber::Callable) if dynamic_options.include?(name) @runtime_options[name] = value end end def dynamic_options [:as, :getter, :setter, :class, :instance, :reader, :writer, :extend, :prepare, :if, :deserialize, :serialize, :skip_parse, :skip_render] end def handle_extend!(options) mod = options.delete(:extend) || options.delete(:decorator) and options[:extend] = mod end def handle_as!(options) options[:as] = options[:as].to_s if options[:as].is_a?(Symbol) # Allow symbols for as: end end end representable-3.0.4/lib/representable/autoload.rb0000644000175000017500000000063613137322612021140 0ustar pravipravimodule Representable autoload :Hash, 'representable/hash' autoload :JSON, 'representable/json' autoload :Object, 'representable/object' autoload :YAML, 'representable/yaml' autoload :XML, 'representable/xml' module Hash autoload :AllowSymbols, 'representable/hash/allow_symbols' autoload :Collection, 'representable/hash/collection' end autoload :Decorator, 'representable/decorator' end representable-3.0.4/lib/representable/binding.rb0000644000175000017500000000617013137322612020741 0ustar pravipravimodule Representable # The Binding provides methods to read/write the fragment for one property. # # Actually parsing the fragment from the document happens in Binding#read, everything after that is generic. class Binding class FragmentNotFound end def self.build(definition) build_for(definition) end def initialize(definition) @definition = definition @name = @definition.name @getter = @definition.getter @setter = @definition.setter setup_exec_context! end attr_reader :name, :getter, :setter extend Uber::Delegates delegates :@definition, :has_default?, :representable?, :array?, :typed? # Single entry points for rendering and parsing a property are #compile_fragment # and #uncompile_fragment in Mapper. module Deprecatable # Retrieve value and write fragment to the doc. def compile_fragment(options) render_pipeline(nil, options).(nil, options) end # Parse value from doc and update the model property. def uncompile_fragment(options) parse_pipeline(options[:doc], options).(options[:doc], options) end end include Deprecatable module EvaluateOption def evaluate_option(name, input, options) proc = self[name] # puts "@@@@@ #{self.inspect}, #{name}...... #{self[name]}" proc.(send(:exec_context, options), options.merge(user_options: options[:options][:user_options], input: input)) # from Uber::Options::Value. # NOTE: this can also be the Proc object if it's not wrapped by Uber:::Value. end end include EvaluateOption def [](name) @definition[name] end def skipable_empty_value?(value) value.nil? and not self[:render_nil] end def default_for(value) return self[:default] if skipable_empty_value?(value) value end attr_accessor :cached_representer require "representable/pipeline_factories" include Factories private def setup_exec_context! @exec_context = ->(options) { options[:represented] } unless self[:exec_context] @exec_context = ->(options) { self } if self[:exec_context] == :binding @exec_context = ->(options) { options[:decorator] } if self[:exec_context] == :decorator end def exec_context(options) @exec_context.(options) end def parse_pipeline(input, options) @parse_pipeline ||= pipeline_for(:parse_pipeline, input, options) { Pipeline[*parse_functions] } end def render_pipeline(input, options) @render_pipeline ||= pipeline_for(:render_pipeline, input, options) { Pipeline[*render_functions] } end # generics for collection bindings. module Collection def skipable_empty_value?(value) # TODO: this can be optimized, again. return true if value.nil? and not self[:render_nil] # FIXME: test this without the "and" return true if self[:render_empty] == false and value and value.size == 0 # TODO: change in 2.0, don't render emtpy. end end end class DeserializeError < RuntimeError end end representable-3.0.4/lib/representable/cached.rb0000644000175000017500000000073513137322612020537 0ustar pravipravimodule Representable # Using this module only makes sense with Decorator representers. module Cached module BuildDefinition def build_definition(*) super.tap do |definition| binding_builder = format_engine::Binding map << binding_builder.build(definition) end end end def self.included(includer) includer.extend(BuildDefinition) end def representable_map(*) self.class.map end end endrepresentable-3.0.4/lib/representable/version.rb0000644000175000017500000000005513137322612021010 0ustar pravipravimodule Representable VERSION = "3.0.4" end representable-3.0.4/lib/representable/config.rb0000644000175000017500000000216613137322612020575 0ustar pravipravimodule Representable # Stores Definitions from ::property. It preserves the adding order (1.9+). # Same-named properties get overridden, just like in a Hash. # # Overwrite definition_class if you need a custom Definition object (helpful when using # representable in other gems). class Config < ::Declarative::Definitions def initialize(*) super @wrap = nil end def remove(name) delete(name.to_s) end def options # FIXME: this is not inherited. @options ||= {} end def wrap=(value) value = value.to_s if value.is_a?(Symbol) @wrap = ::Declarative::Option(value, instance_exec: true, callable: Uber::Callable) end # Computes the wrap string or returns false. def wrap_for(represented, *args, &block) return unless @wrap value = @wrap.(represented, *args) return value if value != true infer_name_for(represented.class.name) end private def infer_name_for(name) name.to_s.split('::').last. gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2'). gsub(/([a-z\d])([A-Z])/,'\1_\2'). downcase end end end representable-3.0.4/.travis.yml0000644000175000017500000000035413137322612015510 0ustar pravipravilanguage: ruby cache: bundler sudo: false rvm: - 1.9.3-p551 - 2.0.0-p648 - 2.1.9 - 2.2.6 - 2.3.3 - 2.4.0 matrix: include: - rvm: jruby-9.1.5.0 env: JRUBY_OPTS="--profile.api" allow_failures: - rvm: jruby-9.1.5.0 representable-3.0.4/.gitignore0000644000175000017500000000013313137322612015362 0ustar pravipravispec/**/*.xml rdoc build doc Rake.config pkg .project .bundle Gemfile.lock *.swp *.swo bin representable-3.0.4/TODO-4.0.md0000644000175000017500000000322113137322612014761 0ustar pravipravi# Decorator XML::Binding::Collection.to_xml(represented) bindings.each bin.to_xml # hat vorteil: [].each{ Collection.to_xml(item) } * make all properties "Object-like", even arrays of strings etc. This saves us from having `extend ObjectBinding if typed?` and we could just call to_hash/from_hash on all attributes. performance issues here? otherwise: implement! # how to? class CH wrap :character prpoerty :a class proerty :author, dec: CH # how to? * override specific bindings and their logic? e.g. `Namespace#read` * Extend nested representers, e.g. the namespace prefix, when it gets plugged into composition * Easier polymorphic representer # XML * ditch annoying nokogiri in favor of https://github.com/YorickPeterse/oga # Parsing * Let bindings have any "xpath" * Allow to parse "wildcard" sections where you have no idea about the property names (and attribute names, eg. with links) # Options * There should be an easier way to pass a set of options to all nested #to_node decorators. ```ruby representable_attrs.keys.each do |property| options[property.to_sym] = { show_definition: false, namespaces: options[:namespaces] } end ``` * Allow passing options to Binding#serialize. serialize(.., options{:exclude => ..}) # wrap, as AsWithNamespace( Binding ) BUT NOT FOR AsWithNamespace( Binding::Attribute ) => selectively wrap bindings at compile- and runtime * Cleanup the manifest part in Decorator. * all property objects should be extended/wrapped so we don't need the switch. # Deprecations * deprecate instance: { nil } which is superseded by parse_strategy: :sync from_hash, property :band, class: vergessen representable-3.0.4/LICENSE0000644000175000017500000000210213137322612014375 0ustar pravipraviCopyright (c) 2011 - 2013 Nick Sutterer and the roar contributors 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. representable-3.0.4/Rakefile0000644000175000017500000000063513137322612015046 0ustar pravipravirequire 'bundler/setup' require 'rake/testtask' desc 'Test the representable gem.' task :default => :test Rake::TestTask.new(:test) do |test| test.libs << 'test' test.test_files = FileList['test/**/*_test.rb'] test.verbose = true end Rake::TestTask.new(:dtest) do |test| test.libs << 'test-with-deprecations' test.test_files = FileList['test-with-deprecations/**/*_test.rb'] test.verbose = true endrepresentable-3.0.4/Gemfile0000644000175000017500000000045513137322612014674 0ustar pravipravisource 'https://rubygems.org' gemspec group :test do gem 'nokogiri', '~> 1.6.8', require: false gem "multi_json", require: false gem "minitest-line" end # gem "declarative", path: "../declarative" # gem "declarative", github: "apotonick/declarative" # gem "uber","0.0.15" #, path: "../uber" representable-3.0.4/test/0000755000175000017500000000000013137322612014354 5ustar pravipravirepresentable-3.0.4/test/filter_test.rb0000644000175000017500000000374113137322612017232 0ustar pravipravirequire 'test_helper' class FilterPipelineTest < MiniTest::Spec let(:block1) { lambda { |input, options| "1: #{input}" } } let(:block2) { lambda { |input, options| "2: #{input}" } } subject { Representable::Pipeline[block1, block2] } it { subject.call("Horowitz", {}).must_equal "2: 1: Horowitz" } end class FilterTest < MiniTest::Spec representer! do property :title property :track, :parse_filter => lambda { |input, options| "#{input.downcase},#{options[:doc]}" }, :render_filter => lambda { |val, options| "#{val.upcase},#{options[:doc]},#{options[:options][:user_options]}" } end # gets doc and options. it { song = OpenStruct.new.extend(representer).from_hash("title" => "VULCAN EARS", "track" => "Nine") song.title.must_equal "VULCAN EARS" song.track.must_equal "nine,{\"title\"=>\"VULCAN EARS\", \"track\"=>\"Nine\"}" } it { OpenStruct.new("title" => "vulcan ears", "track" => "Nine").extend(representer).to_hash.must_equal( {"title"=>"vulcan ears", "track"=>"NINE,{\"title\"=>\"vulcan ears\"},{}"}) } describe "#parse_filter" do representer! do property :track, :parse_filter => [ lambda { |input, options| "#{input}-1" }, lambda { |input, options| "#{input}-2" }], :render_filter => [ lambda { |val, options| "#{val}-1" }, lambda { |val, options| "#{val}-2" }] end # order matters. it { OpenStruct.new.extend(representer).from_hash("track" => "Nine").track.must_equal "Nine-1-2" } it { OpenStruct.new("track" => "Nine").extend(representer).to_hash.must_equal({"track"=>"Nine-1-2"}) } end end # class RenderFilterTest < MiniTest::Spec # representer! do # property :track, :render_filter => [lambda { |val, options| "#{val}-1" } ] # property :track, :render_filter => [lambda { |val, options| "#{val}-2" } ], :inherit => true # end # it { OpenStruct.new("track" => "Nine").extend(representer).to_hash.must_equal({"track"=>"Nine-1-2"}) } # endrepresentable-3.0.4/test/instance_test.rb0000644000175000017500000001775113137322612017557 0ustar pravipravirequire 'test_helper' class InstanceTest < BaseTest Song = Struct.new(:id, :title) Song.class_eval do def self.find(id) new(id, "Invincible") end end describe "lambda { fragment } (new way of class: lambda { nil })" do representer! do property :title, :instance => lambda { |options| options[:fragment] } end it "skips creating new instance" do object = Object.new object.instance_eval do def from_hash(hash, *args) hash end end song = OpenStruct.new.extend(representer).from_hash({"title" => object}) song.title.must_equal object end end # TODO: use *args in from_hash. # DISCUSS: do we need parse_strategy? describe "property with :instance" do representer!(:inject => :song_representer) do property :song, :instance => lambda { |options| options[:fragment]["id"] == song.id ? song : Song.find(options[:fragment]["id"]) }, :extend => song_representer end it( "xxx") { OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer). from_hash("song" => {"id" => 1}).song.must_equal Song.new(1, "The Answer Is Still No") } it { OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer). from_hash("song" => {"id" => 2}).song.must_equal Song.new(2, "Invincible") } end describe "collection with :instance" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| options[:fragment]["id"] == songs[options[:index]].id ? songs[options[:index]] : Song.find(options[:fragment]["id"]) }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer end it { album= Struct.new(:songs).new([ Song.new(1, "The Answer Is Still No"), Song.new(2, "")]) album. extend(representer). from_hash("songs" => [{"id" => 2},{"id" => 2, "title"=>"The Answer Is Still No"}]).songs.must_equal [ Song.new(2, "Invincible"), Song.new(2, "The Answer Is Still No")] } end describe "property with lambda receiving fragment and args" do representer!(:inject => :song_representer) do property :song, :instance => lambda { |options| Struct.new(:args, :id).new([options[:fragment], options[:user_options]]) }, :extend => song_representer end it { OpenStruct.new(:song => Song.new(1, "The Answer Is Still No")).extend(representer). from_hash({"song" => {"id" => 1}}, user_options: { volume: 1 }).song.args.must_equal([{"id"=>1}, {:volume=>1}]) } end # TODO: raise and test instance:{nil} # describe "property with instance: { nil }" do # TODO: introduce :representable option? # representer!(:inject => :song_representer) do # property :song, :instance => lambda { |*| nil }, :extend => song_representer # end # let(:hit) { hit = OpenStruct.new(:song => song).extend(representer) } # it "calls #to_hash on song instance, nothing else" do # hit.to_hash.must_equal("song"=>{"title"=>"Resist Stance"}) # end # it "calls #from_hash on the existing song instance, nothing else" do # song_id = hit.song.object_id # hit.from_hash("song"=>{"title"=>"Suffer"}) # hit.song.title.must_equal "Suffer" # hit.song.object_id.must_equal song_id # end # end # lambda { |fragment, i, Context(binding: <..>, args: [..])| } describe "sync" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| songs[options[:index]] }, :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invncble")]) album. extend(representer). from_hash("songs" => [{"title" => "The Answer Is Still No"}, {"title" => "Invincible"}]) album.songs.must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")] songs.object_id.must_equal album.songs.object_id songs[0].object_id.must_equal album.songs[0].object_id songs[1].object_id.must_equal album.songs[1].object_id } end describe "update existing elements, only" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| #fragment["id"] == songs[i].id ? songs[i] : Song.find(fragment["id"]) songs.find { |s| s.id == options[:fragment]["id"] } }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it("hooray") { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invncble")]) album. extend(representer). from_hash("songs" => [{"id" => 2, "title" => "Invincible"}]). songs.must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")] # TODO: check elements object_id! songs.object_id.must_equal album.songs.object_id songs[0].object_id.must_equal album.songs[0].object_id songs[1].object_id.must_equal album.songs[1].object_id } end describe "add incoming elements, only" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| songs << song=Song.new(2) song }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No")]) album. extend(representer). from_hash("songs" => [{"title" => "Invincible"}]). songs.must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")] songs.object_id.must_equal album.songs.object_id songs[0].object_id.must_equal album.songs[0].object_id } end # not sure if this must be a library strategy describe "replace existing element" do representer!(:inject => :song_representer) do collection :songs, :instance => lambda { |options| id = options[:fragment].delete("replace_id") replaced = songs.find { |s| s.id == id } songs[songs.index(replaced)] = song=Song.new(3) song }, # let's not allow returning nil anymore. make sure we can still do everything as with nil. also, let's remove parse_strategy: sync. :extend => song_representer, # :parse_strategy => :sync :setter => lambda { |*| } end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No"), Song.new(2, "Invincible")]) album. extend(representer). from_hash("songs" => [{"replace_id"=>2, "id" => 3, "title" => "Soulmate"}]). songs.must_equal [ Song.new(1, "The Answer Is Still No"), Song.new(3, "Soulmate")] songs.object_id.must_equal album.songs.object_id songs[0].object_id.must_equal album.songs[0].object_id } end describe "replace collection" do representer!(:inject => :song_representer) do collection :songs, :extend => song_representer, :class => Song end it { album= Struct.new(:songs).new(songs = [ Song.new(1, "The Answer Is Still No")]) album. extend(representer). from_hash("songs" => [{"title" => "Invincible"}]). songs.must_equal [ Song.new(nil, "Invincible")] songs.object_id.wont_equal album.songs.object_id } end endrepresentable-3.0.4/test/config/0000755000175000017500000000000013137322612015621 5ustar pravipravirepresentable-3.0.4/test/config/inherit_test.rb0000644000175000017500000000704213137322612020652 0ustar pravipravirequire 'test_helper' # tests defining representers in modules, decorators and classes and the inheritance when combined. class ConfigInheritTest < MiniTest::Spec def assert_cloned(child, parent, property) child_def = child.representable_attrs.get(property) parent_def = parent.representable_attrs.get(property) child_def.merge!(:alias => property) child_def[:alias].wont_equal parent_def[:alias] child_def.object_id.wont_equal parent_def.object_id end # class Object # end module GenreModule include Representable::Hash property :genre end # in Decorator ------------------------------------------------ class Decorator < Representable::Decorator include Representable::Hash property :title property :artist do property :id end end it { Decorator.definitions.keys.must_equal ["title", "artist"] } # in inheriting Decorator class InheritingDecorator < Decorator property :location end it { InheritingDecorator.definitions.keys.must_equal ["title", "artist", "location"] } it { assert_cloned(InheritingDecorator, Decorator, "title") } it do InheritingDecorator.representable_attrs.get(:artist).representer_module.object_id.wont_equal Decorator.representable_attrs.get(:artist).representer_module.object_id end # in inheriting and including Decorator class InheritingAndIncludingDecorator < Decorator include GenreModule property :location end it { InheritingAndIncludingDecorator.definitions.keys.must_equal ["title", "artist", "genre", "location"] } it { assert_cloned(InheritingAndIncludingDecorator, GenreModule, :genre) } # in module --------------------------------------------------- module Module include Representable property :title end it { Module.definitions.keys.must_equal ["title"] } # in module including module module SubModule include Representable include Module property :location end it { SubModule.definitions.keys.must_equal ["title", "location"] } it { assert_cloned(SubModule, Module, :title) } # including preserves order module IncludingModule include Representable property :genre include Module property :location end it { IncludingModule.definitions.keys.must_equal ["genre", "title", "location"] } # included in class ------------------------------------------- class Class include Representable include IncludingModule end it { Class.definitions.keys.must_equal ["genre", "title", "location"] } it { assert_cloned(Class, IncludingModule, :title) } it { assert_cloned(Class, IncludingModule, :location) } it { assert_cloned(Class, IncludingModule, :genre) } # included in class with order class DefiningClass include Representable property :street_cred include IncludingModule end it { DefiningClass.definitions.keys.must_equal ["street_cred", "genre", "title", "location"] } # in class class RepresenterClass include Representable property :title end it { RepresenterClass.definitions.keys.must_equal ["title"] } # in inheriting class class InheritingClass < RepresenterClass include Representable property :location end it { InheritingClass.definitions.keys.must_equal ["title", "location"] } it { assert_cloned(InheritingClass, RepresenterClass, :title) } # in inheriting class and including class InheritingAndIncludingClass < RepresenterClass property :location include GenreModule end it { InheritingAndIncludingClass.definitions.keys.must_equal ["title", "location", "genre"] } endrepresentable-3.0.4/test/is_representable_test.rb0000644000175000017500000000371713137322612021276 0ustar pravipravirequire 'test_helper' class IsRepresentableTest < BaseTest describe "representable: false, extend:" do representer!(:inject => :song_representer) do property :song, :representable => false, :extend => song_representer end it "does extend but doesn't call #to_hash" do Struct.new(:song).new(song = Object.new).extend(representer). to_hash.must_equal("song" => song) song.must_be_kind_of Representable::Hash end end describe "representable: true, no extend:" do representer!(:inject => :song_representer) do property :song, :representable => true end it "doesn't extend but calls #to_hash" do song = Object.new song.instance_eval do def to_hash(*) 1 end end Struct.new(:song).new(song).extend(representer). to_hash.must_equal("song" => 1) song.wont_be_kind_of Representable::Hash end end # TODO: TEST implement for from_hash. describe "representable: false, with class:" do representer!(:inject => :song_representer) do property :song, :representable => false, :class => OpenStruct, :extend => song_representer end it "does extend but doesn't call #from_hash" do hit = Struct.new(:song).new.extend(representer). from_hash("song" => 1) hit.song.must_equal OpenStruct.new hit.song.must_be_kind_of Representable::Hash end end describe "representable: true, without extend: but class:" do SongReader = Class.new do def from_hash(*) "Piano?" end end representer!(:inject => :song_representer) do property :song, :representable => true, :class => SongReader end it "doesn't extend but calls #from_hash" do hit = Struct.new(:song).new.extend(representer). from_hash("song" => "Sonata No.2") hit.song.must_equal "Piano?" hit.song.wont_be_kind_of Representable::Hash end end endrepresentable-3.0.4/test/if_test.rb0000644000175000017500000000444013137322612016340 0ustar pravipravirequire 'test_helper' class IfTest < MiniTest::Spec let(:band_class) { Class.new do include Representable::Hash attr_accessor :fame self end } it "respects property when condition true" do band_class.class_eval { property :fame, :if => lambda { |*| true } } band = band_class.new band.from_hash({"fame"=>"oh yes"}) assert_equal "oh yes", band.fame end it "ignores property when condition false" do band_class.class_eval { property :fame, :if => lambda { |*| false } } band = band_class.new band.from_hash({"fame"=>"oh yes"}) assert_nil band.fame end it "ignores property when :exclude'ed even when condition is true" do band_class.class_eval { property :fame, :if => lambda { |*| true } } band = band_class.new band.from_hash({"fame"=>"oh yes"}, {:exclude => [:fame]}) assert_nil band.fame end it "executes block in instance context" do band_class.class_eval { property :fame, :if => lambda { |*| groupies }; attr_accessor :groupies } band = band_class.new band.groupies = true band.from_hash({"fame"=>"oh yes"}) assert_equal "oh yes", band.fame end describe "executing :if lambda in represented instance context" do representer! do property :label, :if => lambda { |*| signed_contract } end subject { OpenStruct.new(:signed_contract => false, :label => "Fat") } it "skips when false" do subject.extend(representer).to_hash.must_equal({}) end it "represents when true" do subject.signed_contract= true subject.extend(representer).to_hash.must_equal({"label"=>"Fat"}) end it "works with decorator" do rpr = representer Class.new(Representable::Decorator) do include Representable::Hash include rpr end.new(subject).to_hash.must_equal({}) end end describe "propagating user options to the block" do representer! do property :name, :if => lambda { |opts| opts[:user_options][:include_name] } end subject { OpenStruct.new(:name => "Outbound").extend(representer) } it "works without specifying options" do subject.to_hash.must_equal({}) end it "passes user options to block" do subject.to_hash(user_options: { include_name: true }).must_equal({"name" => "Outbound"}) end end endrepresentable-3.0.4/test/examples/0000755000175000017500000000000013137322612016172 5ustar pravipravirepresentable-3.0.4/test/examples/object.rb0000644000175000017500000000106713137322612017771 0ustar pravipravirequire "test_helper" require "ostruct" require "pp" source = OpenStruct.new(name: "30 Years Live", songs: [ OpenStruct.new(id: 1, title: "Dear Beloved"), OpenStruct.new(id: 2, title: "Fuck Armageddon")]) require "representable/object" class AlbumRepresenter < Representable::Decorator include Representable::Object property :name collection :songs, instance: ->(fragment, *) { Song.new } do property :title end end Album = Struct.new(:name, :songs) Song = Struct.new(:title) target = Album.new AlbumRepresenter.new(target).from_object(source)representable-3.0.4/test/wrap_test.rb0000644000175000017500000001120113137322612016704 0ustar pravipravirequire "test_helper" class WrapTest < MiniTest::Spec class HardcoreBand include Representable::Hash end class SoftcoreBand < HardcoreBand end let(:band) { HardcoreBand.new } it "returns false per default" do assert_nil SoftcoreBand.new.send(:representation_wrap) end it "infers a printable class name if set to true" do HardcoreBand.representation_wrap = true assert_equal "hardcore_band", band.send(:representation_wrap) end it "can be set explicitely" do HardcoreBand.representation_wrap = "breach" assert_equal "breach", band.send(:representation_wrap) end for_formats( :hash => [Representable::Hash, {"Blink182"=>{"genre"=>"Pop"}}, {"Blink182"=>{"genre"=>"Poppunk"}}], :json => [Representable::JSON, "{\"Blink182\":{\"genre\":\"Pop\"}}", "{\"Blink182\":{\"genre\":\"Poppunk\"}}"], :xml => [Representable::XML, "Pop", "Poppunk"], # :yaml => [Representable::YAML, "---\nBlink182:\n"], # TODO: fix YAML. ) do |format, mod, output, input| describe "[#{format}] dynamic wrap" do let(:band) { representer.prepare(Struct.new(:name, :genre).new("Blink", "Pop")) } let(:format) { format } representer!(:module => mod) do self.representation_wrap = lambda { |args| "#{name}#{args[:number]}" } property :genre end it { render(band, {:number => 182}).must_equal_document(output) } it { parse(band, input, {:number => 182}).genre.must_equal "Poppunk" } # TODO: better test. also, xml parses _any_ wrap. end end end class HashDisableWrapTest < MiniTest::Spec Band = Struct.new(:name, :label) Album = Struct.new(:band) Label = Struct.new(:name) class BandDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :bands property :name property :label do # this should have a wrap! self.representation_wrap = :important property :name end end let(:band) { BandDecorator.prepare(Band.new("Social Distortion")) } # direct, local api. it do band.to_hash.must_equal({"bands" => {"name"=>"Social Distortion"}}) band.to_hash(wrap: false).must_equal({"name"=>"Social Distortion"}) band.to_hash(wrap: :band).must_equal(:band=>{"name"=>"Social Distortion"}) end it do band.from_hash({"bands" => {"name"=>"Social Distortion"}}).name.must_equal "Social Distortion" band.from_hash({"name"=>"Social Distortion"}, wrap: false).name.must_equal "Social Distortion" band.from_hash({band: {"name"=>"Social Distortion"}}, wrap: :band).name.must_equal "Social Distortion" end class AlbumDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :albums property :band, decorator: BandDecorator, wrap: false, class: Band end let(:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) } # band has wrap turned off per property definition, however, label still has wrap. it "renders" do album.to_hash.must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}}) end it "parses" do album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name.must_equal "Rvivr" end end class XMLDisableWrapTest < MiniTest::Spec Band = Struct.new(:name, :label) Album = Struct.new(:band) Label = Struct.new(:name) class BandDecorator < Representable::Decorator include Representable::XML self.representation_wrap = :bands # when nested, it uses this. property :name # property :label do # this should have a wrap! # self.representation_wrap = :important # property :name # end end let(:band) { BandDecorator.prepare(Band.new("Social Distortion")) } it do band.to_xml.must_equal_xml "Social Distortion" band.to_xml(wrap: "combo").must_equal_xml "Social Distortion" end class AlbumDecorator < Representable::Decorator include Representable::XML self.representation_wrap = :albums property :band, decorator: BandDecorator, wrap: "po", class: Band end let(:album) { AlbumDecorator.prepare(Album.new(Band.new("Social Distortion", Label.new("Epitaph")))) } # band has wrap turned of per property definition, however, label still has wrap. it "rendersxx" do skip album.to_xml.must_equal({"albums" => {"band" => {"name"=>"Social Distortion", "label"=>{"important"=>{"name"=>"Epitaph"}}}}}) end # it "parses" do # album.from_hash({"albums" => {"band" => {"name"=>"Rvivr"}}}).band.name.must_equal "Rvivr" # end end representable-3.0.4/test/config_test.rb0000644000175000017500000000621613137322612017212 0ustar pravipravirequire 'test_helper' class ConfigTest < MiniTest::Spec subject { Representable::Config.new(Representable::Definition) } PunkRock = Class.new Definition = Representable::Definition describe "wrapping" do it "returns false per default" do assert_nil subject.wrap_for("Punk", nil) end # it "infers a printable class name if set to true" do # subject.wrap = true # assert_equal "punk_rock", subject.wrap_for(PunkRock, nil) # end # it "can be set explicitely" do # subject.wrap = "Descendents" # assert_equal "Descendents", subject.wrap_for(PunkRock, nil) # end end describe "#[]" do # does return nil for non-existent it { subject[:hello].must_be_nil } end # describe "#[]" do # before { subject.add(:title, {:me => true}) } # it { subject[:unknown].must_be_nil } # it { subject.get(:title)[:me].must_equal true } # it { subject["title"][:me].must_equal true } # end # []= # []=(... inherit: true) # forwarded to Config#definitions # that goes to ConfigDefinitionsTest describe "#add" do describe "returns" do it do # #add returns Definition.` subject = Representable::Config.new(Representable::Definition).add(:title, {:me => true}) subject.must_be_kind_of Representable::Definition subject[:me].must_equal true end end before { subject.add(:title, {:me => true}) } # must be kind of Definition it { subject.size.must_equal 1 } it { subject.get(:title).name.must_equal "title" } it { subject.get(:title)[:me].must_equal true } # this is actually tested in context in inherit_test. it "overrides former definition" do subject.add(:title, {:peer => Module}) subject.get(:title)[:me].must_be_nil subject.get(:title)[:peer].must_equal Module end describe "inherit: true" do before { subject.add(:title, {:me => true}) subject.add(:title, {:peer => Module, :inherit => true}) } it { subject.get(:title)[:me].must_equal true } it { subject.get(:title)[:peer].must_equal Module } end end describe "#remove" do subject { Representable::Config.new(Representable::Definition) } it do subject.add(:title, {:me => true}) subject.add(:genre, {}) subject.get(:genre).must_be_kind_of Representable::Definition subject.remove(:genre) subject.get(:genre).must_be_nil end end describe "#each" do before { subject.add(:title, {:me => true}) } it "what" do definitions = [] subject.each { |dfn| definitions << dfn } definitions.size.must_equal 1 definitions[0][:me].must_equal true end end describe "#options" do it { subject.options.must_equal({}) } it do subject.options[:namespacing] = true subject.options[:namespacing].must_equal true end end describe "#get" do subject { Representable::Config.new(Representable::Definition) } it do title = subject.add(:title, {}) length = subject.add(:length, {}) subject.get(:title).must_equal title subject.get(:length).must_equal length end end endrepresentable-3.0.4/test/yaml_test.rb0000644000175000017500000001025313137322612016703 0ustar pravipravirequire 'test_helper' class YamlPublicMethodsTest < Minitest::Spec #--- # from_yaml class BandRepresenter < Representable::Decorator include Representable::YAML property :id property :name end let(:data) { %{--- id: 1 name: Rancid } } it { BandRepresenter.new(Band.new).from_yaml(data)[:id, :name].must_equal [1, "Rancid"] } it { BandRepresenter.new(Band.new).parse(data)[:id, :name].must_equal [1, "Rancid"] } #--- # to_yaml let(:band) { Band.new("1", "Rancid") } it { BandRepresenter.new(band).to_yaml.must_equal data } it { BandRepresenter.new(band).render.must_equal data } end class YamlTest < MiniTest::Spec def self.yaml_representer(&block) Module.new do include Representable::YAML instance_exec(&block) end end def yaml_representer(&block) self.class.yaml_representer(&block) end describe "property" do let(:yaml) { yaml_representer do property :best_song end } let(:album) { Album.new.tap do |album| album.best_song = "Liar" end } describe "#to_yaml" do it "renders plain property" do album.extend(yaml).to_yaml.must_equal( "--- best_song: Liar ") end it "always renders values into strings" do Album.new.tap { |a| a.best_song = 8675309 }.extend(yaml).to_yaml.must_equal( "--- best_song: 8675309 " ) end end describe "#from_yaml" do it "parses plain property" do album.extend(yaml).from_yaml( "--- best_song: This Song Is Recycled ").best_song.must_equal "This Song Is Recycled" end end describe "with :class and :extend" do yaml_song = yaml_representer do property :name end let(:yaml_album) { Module.new do include Representable::YAML property :best_song, :extend => yaml_song, :class => Song end } let(:album) { Album.new.tap do |album| album.best_song = Song.new("Liar") end } describe "#to_yaml" do it "renders embedded typed property" do album.extend(yaml_album).to_yaml.must_equal "--- best_song: name: Liar " end end describe "#from_yaml" do it "parses embedded typed property" do album.extend(yaml_album).from_yaml("--- best_song: name: Go With Me ").must_equal Album.new(nil,Song.new("Go With Me")) end end end end describe "collection" do let(:yaml) { yaml_representer do collection :songs end } let(:album) { Album.new.tap do |album| album.songs = ["Jackhammer", "Terrible Man"] end } describe "#to_yaml" do it "renders a block style list per default" do album.extend(yaml).to_yaml.must_equal "--- songs: - Jackhammer - Terrible Man " end it "renders a flow style list when :style => :flow set" do yaml = yaml_representer { collection :songs, :style => :flow } album.extend(yaml).to_yaml.must_equal "--- songs: [Jackhammer, Terrible Man] " end end describe "#from_yaml" do it "parses a block style list" do album.extend(yaml).from_yaml("--- songs: - Off Key Melody - Sinking").must_equal Album.new(["Off Key Melody", "Sinking"]) end it "parses a flow style list" do album.extend(yaml).from_yaml("--- songs: [Off Key Melody, Sinking]").must_equal Album.new(["Off Key Melody", "Sinking"]) end end describe "with :class and :extend" do let(:yaml_album) { Module.new do include Representable::YAML collection :songs, :class => Song do property :name property :track end end } let(:album) { Album.new([Song.new("Liar", 1), Song.new("What I Know", 2)]) } describe "#to_yaml" do it "renders collection of typed property" do album.extend(yaml_album).to_yaml.must_equal "--- songs: - name: Liar track: 1 - name: What I Know track: 2 " end end describe "#from_yaml" do it "parses collection of typed property" do album.extend(yaml_album).from_yaml("--- songs: - name: One Shot Deal track: 4 - name: Three Way Dance track: 5").must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)]) end end end end end representable-3.0.4/test/skip_test.rb0000644000175000017500000000543113137322612016711 0ustar pravipravirequire 'test_helper' class SkipParseTest < MiniTest::Spec representer! do property :title, skip_parse: lambda { |options| options[:user_options][:skip?] and options[:fragment] == "skip me" } property :band, skip_parse: lambda { |options| options[:user_options][:skip?] and options[:fragment]["name"].nil? }, class: OpenStruct do property :name end collection :airplays, skip_parse: lambda { |options| options[:user_options][:skip?] and options[:fragment]["station"].nil? }, class: OpenStruct do property :station end end let(:song) { OpenStruct.new.extend(representer) } # do parse. it do song.from_hash({ "title" => "Victim Of Fate", "band" => {"name" => "Mute 98"}, "airplays" => [{"station" => "JJJ"}] }, user_options: { skip?: true }) song.title.must_equal "Victim Of Fate" song.band.name.must_equal "Mute 98" song.airplays[0].station.must_equal "JJJ" end # skip parsing. let(:airplay) { OpenStruct.new(station: "JJJ") } it do song.from_hash({ "title" => "skip me", "band" => {}, "airplays" => [{}, {"station" => "JJJ"}, {}], }, user_options: { skip?: true }) song.title.must_be_nil song.band.must_be_nil song.airplays.must_equal [airplay] end end class SkipRenderTest < MiniTest::Spec representer! do property :title property :band, skip_render: lambda { |options| options[:user_options][:skip?] and options[:input].name == "Rancid" } do property :name end collection :airplays, skip_render: lambda { |options| options[:user_options][:skip?] and options[:input].station == "Radio Dreyeckland" } do property :station end end let(:song) { OpenStruct.new(title: "Black Night", band: OpenStruct.new(name: "Time Again")).extend(representer) } let(:skip_song) { OpenStruct.new(title: "Time Bomb", band: OpenStruct.new(name: "Rancid")).extend(representer) } # do render. it { song.to_hash(user_options: { skip?: true }).must_equal({"title"=>"Black Night", "band"=>{"name"=>"Time Again"}}) } # skip. it { skip_song.to_hash(user_options: { skip?: true }).must_equal({"title"=>"Time Bomb"}) } # do render all collection items. it do song = OpenStruct.new(airplays: [OpenStruct.new(station: "JJJ"), OpenStruct.new(station: "ABC")]).extend(representer) song.to_hash(user_options: { skip?: true }).must_equal({"airplays"=>[{"station"=>"JJJ"}, {"station"=>"ABC"}]}) end # skip middle item. it do song = OpenStruct.new(airplays: [OpenStruct.new(station: "JJJ"), OpenStruct.new(station: "Radio Dreyeckland"), OpenStruct.new(station: "ABC")]).extend(representer) song.to_hash(user_options: { skip?: true }).must_equal({"airplays"=>[{"station"=>"JJJ"}, {"station"=>"ABC"}]}) end endrepresentable-3.0.4/test/exec_context_test.rb0000644000175000017500000000665613137322612020445 0ustar pravipravirequire 'test_helper' class ExecContextTest < MiniTest::Spec for_formats( :hash => [Representable::Hash, {Song => "Rebel Fate"}, {Song=>"Timing"}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input, output| let(:song) { representer.prepare(Song.new("Timing")) } let(:format) { format } describe "exec_context: nil" do representer!(:module => mod) do property :name, :as => lambda { |*| self.class } end it { render(song).must_equal_document output } it { parse(song, input).name.must_equal "Rebel Fate" } end describe "exec_context: :decorator" do representer!(:module => mod) do property :name, :as => lambda { |*| self.class }, :exec_context => :decorator end it { render(song).must_equal_document output } it { parse(song, input).name.must_equal "Rebel Fate" } end describe "exec_context: :binding" do representer!(:module => mod) do property :name, :as => lambda { |*| self.class }, # to actually test :exec_context => :binding, :setter => lambda { |options| options[:represented].name = options[:fragment] # to make parsing work. } end it { render(song).must_equal_document({Representable::Hash::Binding => "name"}) } it { parse(song, {Representable::Hash::Binding => "Rebel Fate"}).name.must_equal "Rebel Fate" } end describe "Decorator" do # DISCUSS: do we need this test? describe "exec_context: nil" do representer!(:module => mod, :decorator => true) do property :name, :as => lambda { |*| self.class } end it { render(song).must_equal_document output } it { parse(song, input).name.must_equal "Rebel Fate" } end describe "exec_context: :decorator" do # this tests if lambdas are run in the right context, if methods are called in the right context and if we can access the represented object. representer!(:module => mod, :decorator => true) do property :name, :as => lambda { |*| self.class.superclass }, :exec_context => :decorator define_method :name do # def in Decorator class. "Timebomb" end define_method :"name=" do |v| # def in Decorator class. represented.name = v end end it { render(song).must_equal_document({Representable::Decorator=>"Timebomb"}) } it { parse(song, {Representable::Decorator=>"Listless"}).name.must_equal "Listless" } end # DISCUSS: do we need this test? describe "exec_context: :binding" do representer!(:module => mod, :decorator => true) do property :name, :as => lambda { |*| self.class }, # to actually test :exec_context => :binding, :setter => lambda { |options| options[:represented].name = options[:fragment ] # to make parsing work. } end it { render(song).must_equal_document({Representable::Hash::Binding => "name"}) } it("xxx") { parse(song, {Representable::Hash::Binding => "Rebel Fate"}).name.must_equal "Rebel Fate" } end end end endrepresentable-3.0.4/test/defaults_options_test.rb0000644000175000017500000000515213137322612021325 0ustar pravipravirequire 'test_helper' class DefaultsOptionsTest < BaseTest let(:format) { :hash } let(:song) { Struct.new(:title, :author_name, :song_volume, :description).new("Revolution", "Some author", 20, nil) } let(:prepared) { representer.prepare song } describe "hash options combined with dynamic options" do representer! do defaults render_nil: true do |name| { as: name.to_s.upcase } end property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "DESCRIPTION" => nil, "SONG_VOLUME" => 20}) } end describe "with only dynamic property options" do representer! do defaults do |name| { as: name.to_s.upcase } end property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "SONG_VOLUME" => 20}) } end describe "with only hashes" do representer! do defaults render_nil: true property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"title" => "Revolution", "author_name" => "Some author", "description" => nil, "song_volume" => 20}) } end describe "direct defaults hash" do representer! do defaults render_nil: true property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"title" => "Revolution", "author_name" => "Some author", "description" => nil, "song_volume" => 20}) } end describe "direct defaults hash with dynamic options" do representer! do defaults render_nil: true do |name| { as: name.to_s.upcase } end property :title property :author_name property :description property :song_volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "DESCRIPTION" => nil, "SONG_VOLUME" => 20}) } end describe "prioritizes specific options" do representer! do defaults render_nil: true do |name| { as: name.to_s.upcase } end property :title property :author_name property :description, render_nil: false property :song_volume, as: :volume end it { render(prepared).must_equal_document({"TITLE" => "Revolution", "AUTHOR_NAME" => "Some author", "volume" => 20}) } end end representable-3.0.4/test/represent_test.rb0000644000175000017500000000536313137322612017756 0ustar pravipravirequire 'test_helper' class RepresentTest < MiniTest::Spec let(:songs) { [song, Song.new("Can't Take Them All")] } let(:song) { Song.new("Days Go By") } for_formats( :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out], # :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out], # :xml => [Representable::XML, out="", out] ) do |format, mod, output, input| # Representer.represents detects collection. describe "Module#to_/from_#{format}" do let(:format) { format } let(:representer) { Module.new do include mod property :name collection_representer :class => Song # TODOOOOOOOOOOOO: test without Song and fix THIS FUCKINGNoMethodError: undefined method `name=' for {"name"=>"Days Go By"}:Hash ERROR!!!!!!!!!!!!!!! end } it { render(representer.represent(songs)).must_equal_document output } it { parse(representer.represent([]), input).must_equal songs } end # Decorator.represents detects collection. describe "Decorator#to_/from_#{format}" do let(:format) { format } let(:representer) { Class.new(Representable::Decorator) do include mod property :name collection_representer :class => Song end } it { render(representer.represent(songs)).must_equal_document output } it("ficken") { parse(representer.represent([]), input).must_equal songs } end end for_formats( :hash => [Representable::Hash, out={"name" => "Days Go By"}, out], :json => [Representable::JSON, out="{\"name\":\"Days Go By\"}", out], # :xml => [Representable::XML, out="", out] ) do |format, mod, output, input| # Representer.represents detects singular. describe "Module#to_/from_#{format}" do let(:format) { format } let(:representer) { Module.new do include mod property :name collection_representer :class => Song end } it { render(representer.represent(song)).must_equal_document output } it { parse(representer.represent(Song.new), input).must_equal song } end # Decorator.represents detects singular. describe "Decorator#to_/from_#{format}" do let(:format) { format } let(:representer) { Class.new(Representable::Decorator) do include mod property :name collection_representer :class => Song end } it { render(representer.represent(song)).must_equal_document output } it { parse(representer.represent(Song.new), input).must_equal song } end end endrepresentable-3.0.4/test/serialize_deserialize_test.rb0000644000175000017500000000204513137322612022310 0ustar pravipravirequire 'test_helper' class SerializeDeserializeTest < BaseTest subject { Struct.new(:song).new.extend(representer) } describe "deserialize" do representer! do property :song, :instance => lambda { |options| options[:input].to_s.upcase }, :prepare => lambda { |options| options[:input] }, :deserialize => lambda { |options| "#{options[:input]} #{options[:fragment]} #{options[:user_options].inspect}" } end it { subject.from_hash({"song" => Object}, user_options: {volume: 9}).song.must_equal "OBJECT Object {:volume=>9}" } end describe "serialize" do representer! do property :song, :representable => true, :prepare => lambda { |options| options[:fragment] }, :serialize => lambda { |options| "#{options[:input]} #{options[:user_options].inspect}" } end before { subject.song = "Arrested In Shanghai" } it { subject.to_hash(user_options: {volume: 9}).must_equal({"song"=>"Arrested In Shanghai {:volume=>9}"}) } end endrepresentable-3.0.4/test/as_test.rb0000644000175000017500000000376513137322612016356 0ustar pravipravirequire 'test_helper' class AsTest < MiniTest::Spec for_formats( :hash => [Representable::Hash, {"title" => "Wie Es Geht"}, {"title" => "Revolution"}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input, output| let(:song) { representer.prepare(Song.new("Revolution")) } let(:format) { format } describe "as: with :symbol" do representer!(:module => mod) do property :name, :as => :title end it { render(song).must_equal_document output } it { parse(song, input).name.must_equal "Wie Es Geht" } end describe "as: with lambda" do representer!(:module => mod) do property :name, :as => lambda { |*| "#{self.class}" } end it { render(song).must_equal_document({"Song" => "Revolution"}) } it { parse(song, {"Song" => "Wie Es Geht"}).name.must_equal "Wie Es Geht" } end describe "lambda arguments" do representer! do property :name, :as => lambda { |options| options[:user_options].inspect } end it { render(song, user_options:{volume: 1}).must_equal_document({"{:volume=>1}" => "Revolution"}) } it { parse(song, {"{:volume=>1}" => "Wie Es Geht"}, user_options: {volume: 1}).name.must_equal "Wie Es Geht" } end end end # hash: to_hash(wrap: ) is representation_wrap class AsXmlTest < MiniTest::Spec Band = Struct.new(:name, :label) Album = Struct.new(:band) Label = Struct.new(:name) representer!(module: Representable::XML, decorator: true) do self.representation_wrap = :album property :band, as: :combo do self.representation_wrap = :band property :name end end it do skip representer.new(Album.new(Band.new("Offspring"))).to_xml.must_equal "" end end representable-3.0.4/test/xml_test.rb0000644000175000017500000003747513137322612016560 0ustar pravipravirequire 'test_helper' class XmlPublicMethodsTest < Minitest::Spec #--- # from_hash class BandRepresenter < Representable::Decorator include Representable::XML property :id property :name end let(:data) { %{1Rancid} } it { BandRepresenter.new(Band.new).from_xml(data)[:id, :name].must_equal ["1", "Rancid"] } it { BandRepresenter.new(Band.new).parse(data)[:id, :name].must_equal ["1", "Rancid"] } #--- # to_hash let(:band) { Band.new("1", "Rancid") } it { BandRepresenter.new(band).to_xml.must_equal_xml data } it { BandRepresenter.new(band).render.must_equal_xml data } end class XmlTest < MiniTest::Spec class Band include Representable::XML property :name attr_accessor :name def initialize(name=nil) name and self.name = name end end XML = Representable::XML Def = Representable::Definition describe "Xml module" do before do @Band = Class.new do include Representable::XML self.representation_wrap = :band property :name property :label attr_accessor :name, :label end @band = @Band.new end describe "#from_xml" do before do @band = @Band.new @xml = %{Nofx} end it "parses XML and assigns properties" do @band.from_xml(@xml) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#from_xml with remove_namespaces! and xmlns present" do before do @Band.remove_namespaces! @band = @Band.new @xml = %{Nofx} end it "parses with xmlns present" do @band.from_xml(@xml) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#from_node" do before do @band = @Band.new @xml = Nokogiri::XML(%{Nofx}).root end it "receives Nokogiri node and assigns properties" do @band.from_node(@xml) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#to_xml" do it "delegates to #to_node and returns string" do assert_xml_equal "Rise Against", Band.new("Rise Against").to_xml end end describe "#to_node" do it "returns Nokogiri node" do node = Band.new("Rise Against").to_node assert_kind_of Nokogiri::XML::Element, node end it "wraps with infered class name per default" do node = Band.new("Rise Against").to_node assert_xml_equal "Rise Against", node.to_s end it "respects #representation_wrap=" do klass = Class.new(Band) do include Representable property :name end klass.representation_wrap = :group assert_xml_equal "Rise Against", klass.new("Rise Against").to_node.to_s end end describe "XML::Binding#build_for" do it "returns AttributeBinding" do assert_kind_of XML::Binding::Attribute, XML::Binding.build_for(Def.new(:band, :as => "band", :attribute => true)) end it "returns Binding" do assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :class => Hash)) assert_kind_of XML::Binding, XML::Binding.build_for(Def.new(:band, :as => :content)) end it "returns CollectionBinding" do assert_kind_of XML::Binding::Collection, XML::Binding.build_for(Def.new(:band, :collection => :true)) end it "returns HashBinding" do assert_kind_of XML::Binding::Hash, XML::Binding.build_for(Def.new(:band, :hash => :true)) end end describe "DCI" do module SongRepresenter include Representable::XML property :name end module AlbumRepresenter include Representable::XML property :best_song, :class => Song, :extend => SongRepresenter collection :songs, :class => Song, :as => :song, :extend => SongRepresenter end it "allows adding the representer by using #extend" do module BandRepresenter include Representable::XML property :name end civ = Object.new civ.instance_eval do def name; "CIV"; end def name=(v) @name = v end end civ.extend(BandRepresenter) assert_xml_equal "CIV", civ.to_xml end it "extends contained models when serializing" do @album = Album.new( [Song.new("I Hate My Brain"), mr=Song.new("Mr. Charisma")], mr) @album.extend(AlbumRepresenter) @album.to_xml.must_equal_xml " Mr. Charisma I Hate My Brain Mr. Charisma " end it "extends contained models when deserializing" do @album = Album.new @album.extend(AlbumRepresenter) @album.from_xml("Mr. CharismaI Hate My BrainMr. Charisma") assert_equal "Mr. Charisma", @album.best_song.name end end end end class AttributesTest < MiniTest::Spec describe ":as => rel, :attribute => true" do class Link include Representable::XML property :href, :as => "href", :attribute => true property :title, :as => "title", :attribute => true attr_accessor :href, :title end it "#from_xml creates correct accessors" do link = Link.new.from_xml(%{ }) assert_equal "http://apotomo.de", link.href assert_equal "Home, sweet home", link.title end it "#to_xml serializes correctly" do link = Link.new link.href = "http://apotomo.de/" assert_xml_equal %{}, link.to_xml end end end class CDataBand class CData < Representable::XML::Binding def serialize_node(parent, value) parent << Nokogiri::XML::CDATA.new(parent, represented.name) end end include Representable::XML property :name, :binding => lambda { |*args| CData.new(*args) }#getter: lambda { |opt| Nokogiri::XML::CDATA.new(opt[:doc], name) } attr_accessor :name def initialize(name=nil) name and self.name = name end end class TypedPropertyTest < MiniTest::Spec class Band include Representable::XML property :name attr_accessor :name def initialize(name=nil) name and self.name = name end end module AlbumRepresenter include Representable::XML property :band, :class => Band end class Album attr_accessor :band def initialize(band=nil) @band = band end end # TODO:property :group, :class => Band # :class # where to mixin DCI? describe ":class => Item" do it "#from_xml creates one Item instance" do album = Album.new.extend(AlbumRepresenter).from_xml(%{ Bad Religion }) assert_equal "Bad Religion", album.band.name end describe "#to_xml" do it "doesn't escape xml from Band#to_xml" do band = Band.new("Bad Religion") album = Album.new(band).extend(AlbumRepresenter) assert_xml_equal %{ Bad Religion }, album.to_xml end it "doesn't escape and wrap string from Band#to_node" do band = Band.new("Bad Religion") band.instance_eval do def to_node(*) "Baaaad Religion" end end assert_xml_equal %{Baaaad Religion}, Album.new(band).extend(AlbumRepresenter).to_xml end end describe "#to_xml with CDATA" do it "wraps Band name in CDATA#to_xml" do band = CDataBand.new("Bad Religion") album = Album.new(band).extend(AlbumRepresenter) assert_xml_equal %{ }, album.to_xml end end end end # TODO: add parsing tests. class XMLPropertyTest < Minitest::Spec Band = Struct.new(:name, :genre) Manager = Struct.new(:managed) #--- #- :as with scalar class BandRepresenter < Representable::Decorator include Representable::XML property :name, as: :theyCallUs property :genre, attribute: true end it { BandRepresenter.new(Band.new("Mute")).to_xml.must_equal_xml %{Mute} } class ManagerRepresenter < Representable::Decorator include Representable::XML property :managed, as: :band, decorator: BandRepresenter end #- :as with nested property it { ManagerRepresenter.new(Manager.new(Band.new("Mute", "Punkrock"))).to_xml.must_equal_xml %{Mute} } end class XMLCollectionTest < MiniTest::Spec Band = Struct.new(:name) Compilation = Struct.new(:bands) class BandRepresenter < Representable::Decorator include Representable::XML property :name end #--- #- :as, :decorator, :class describe ":class => Band, :as => :band, :collection => true" do class CompilationRepresenter < Representable::Decorator include Representable::XML collection :bands, class: Band, as: :group, decorator: BandRepresenter end describe "#from_xml" do it "pushes collection items to array" do cd = CompilationRepresenter.new(Compilation.new).from_xml(%{ Diesel Boy Cobra Skulls }) assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort end end it "responds to #to_xml" do cd = Compilation.new([Band.new("Diesel Boy"), Band.new("Bad Religion")]) CompilationRepresenter.new(cd).to_xml.must_equal_xml %{ Diesel Boy Bad Religion } end end describe ":as" do let(:xml_doc) { Module.new do include Representable::XML collection :songs, :as => :song end } it "collects untyped items" do album = Album.new.extend(xml_doc).from_xml(%{ Two Kevins Wright and Rong Laundry Basket }) assert_equal ["Laundry Basket", "Two Kevins", "Wright and Rong"].sort, album.songs.sort end end describe ":wrap" do let(:album) { Album.new.extend(xml_doc) } let(:xml_doc) { Module.new do include Representable::XML collection :songs, :as => :song, :wrap => :songs end } describe "#from_xml" do it "finds items in wrapped collection" do album.from_xml(%{ Two Kevins Wright and Rong Laundry Basket }) assert_equal ["Laundry Basket", "Two Kevins", "Wright and Rong"].sort, album.songs.sort end end describe "#to_xml" do it "wraps items" do album.songs = ["Laundry Basket", "Two Kevins", "Wright and Rong"] assert_xml_equal %{ Laundry Basket Two Kevins Wright and Rong }, album.to_xml end end end require 'representable/xml/hash' class LonelyRepresenterTest < MiniTest::Spec # TODO: where is the XML::Hash test? module SongRepresenter include Representable::XML property :name self.representation_wrap = :song end let(:decorator) { rpr = representer; Class.new(Representable::Decorator) { include Representable::XML; include rpr } } describe "XML::Collection" do describe "with contained objects" do representer!(:module => Representable::XML::Collection) do items :class => Song, :extend => SongRepresenter self.representation_wrap= :songs end let(:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] } let(:xml_doc) { "Days Go ByCan't Take Them All" } it "renders array" do songs.extend(representer).to_xml.must_equal_xml xml_doc end it "renders array with decorator" do decorator.new(songs).to_xml.must_equal_xml xml_doc end it "parses array" do [].extend(representer).from_xml(xml_doc).must_equal songs end it "parses array with decorator" do decorator.new([]).from_xml(xml_doc).must_equal songs end end end describe "XML::AttributeHash" do # TODO: move to HashTest. representer!(:module => Representable::XML::AttributeHash) do self.representation_wrap= :songs end let(:songs) { {"one" => "Graveyards", "two" => "Can't Take Them All"} } let(:xml_doc) { "" } describe "#to_xml" do it "renders hash" do songs.extend(representer).to_xml.must_equal_xml xml_doc end it "respects :exclude" do assert_xml_equal "", songs.extend(representer).to_xml(:exclude => [:one]) end it "respects :include" do assert_xml_equal "", songs.extend(representer).to_xml(:include => [:two]) end it "renders hash with decorator" do decorator.new(songs).to_xml.must_equal_xml xml_doc end end describe "#from_json" do it "returns hash" do {}.extend(representer).from_xml(xml_doc).must_equal songs end it "respects :exclude" do assert_equal({"two" => "Can't Take Them All"}, {}.extend(representer).from_xml(xml_doc, :exclude => [:one])) end it "respects :include" do assert_equal({"one" => "Graveyards"}, {}.extend(representer).from_xml(xml_doc, :include => [:one])) end it "parses hash with decorator" do decorator.new({}).from_xml(xml_doc).must_equal songs end end end end end class XmlHashTest < MiniTest::Spec # scalar, no object describe "plain text" do representer!(module: Representable::XML) do hash :songs end let(:doc) { "The GargoyleBronx" } # to_xml it { OpenStruct.new(songs: {"first" => "The Gargoyle", "second" => "Bronx"}).extend(representer).to_xml.must_equal_xml(doc) } # FIXME: this NEVER worked! # it { OpenStruct.new.extend(representer).from_xml(doc).songs.must_equal({"first" => "The Gargoyle", "second" => "Bronx"}) } end describe "with objects" do representer!(module: Representable::XML) do hash :songs, class: OpenStruct do property :title end end let(:doc) { " The Gargoyle Bronx " } # to_xml it { OpenStruct.new(songs: {"first" => OpenStruct.new(title: "The Gargoyle"), "second" => OpenStruct.new(title: "Bronx")}).extend(representer).to_xml.must_equal_xml(doc) } # FIXME: this NEVER worked! # it { OpenStruct.new.extend(representer).from_xml(doc).songs.must_equal({"first" => "The Gargoyle", "second" => "Bronx"}) } end end representable-3.0.4/test/xml_namespace_test.rb0000644000175000017500000001315113137322612020555 0ustar pravipravirequire "test_helper" # # # 0836217462 # Being a Dog Is a Full-Time Job # # Charles M Schulz # 1922-11-26 # 2000-02-12 # # # Peppermint Patty # 1966-08-22 # bold, brash and tomboyish # # # Snoopy # 1950-10-04 # extroverted beagle # # # Schroeder # 1951-05-30 # brought classical music to the Peanuts strip # # # # Lucy # 1952-03-03 # bossy, crabby and selfish # # # # Theoretically, every property (scalar) needs/should have a namespace. # property :name, namespace: "http://eric.van-der-vlist.com/ns/person" # # This is implicit if contained: # class Person < Decorator # namespace: "http://eric.van-der-vlist.com/ns/person" # property :name #, namespace: "http://eric.van-der-vlist.com/ns/person" # end # class Library # namespace "http://eric.van-der-vlist.com/ns/library" # namespace_def lib: "http://eric.van-der-vlist.com/ns/library" # namespace_def person: "http://eric.van-der-vlist.com/ns/person" # # namespace_def person: Person # property :person, decorator: Person # end class NamespaceXMLTest < Minitest::Spec module Model Library = Struct.new(:book) Book = Struct.new(:id, :isbn) end let (:book) { Model::Book.new(1, 666) } #:simple-class class Library < Representable::Decorator feature Representable::XML feature Representable::XML::Namespace namespace "http://eric.van-der-vlist.com/ns/library" property :book do namespace "http://eric.van-der-vlist.com/ns/library" property :id, attribute: true property :isbn end end #:simple-class end # default namespace for library it "what" do Library.new(Model::Library.new(book)).to_xml.must_xml( #:simple-xml %{ 666 } #:simple-xml end ) end end class Namespace2XMLTest < Minitest::Spec module Model Library = Struct.new(:book) Book = Struct.new(:id, :isbn, :author, :character) Character = Struct.new(:name, :born, :qualification) end let (:book) { Model::Book.new(1, 666, Model::Character.new("Fowler"), [Model::Character.new("Frau Java", 1991, "typed")]) } #:map-class class Library < Representable::Decorator feature Representable::XML feature Representable::XML::Namespace namespace "http://eric.van-der-vlist.com/ns/library" namespace_def lib: "http://eric.van-der-vlist.com/ns/library" namespace_def hr: "http://eric.van-der-vlist.com/ns/person" property :book, class: Model::Book do namespace "http://eric.van-der-vlist.com/ns/library" property :id, attribute: true property :isbn property :author, class: Model::Character do namespace "http://eric.van-der-vlist.com/ns/person" property :name property :born end collection :character, class: Model::Character do namespace "http://eric.van-der-vlist.com/ns/library" property :qualification property :name, namespace: "http://eric.van-der-vlist.com/ns/person" property :born, namespace: "http://eric.van-der-vlist.com/ns/person" end end end #:map-class end it "renders" do Library.new(Model::Library.new(book)).to_xml.must_xml( #:map-xml %{ 666 Fowler typed Frau Java 1991 } #:map-xml end ) end it "parses" do lib = Model::Library.new #:parse-call Library.new(lib).from_xml(%{ 666 Fowler typed Frau Java Mr. Ruby Dr. Elixir 1991 } #:parse-call end ) lib.book.inspect.must_equal %{#, character=[#]>} #:parse-res lib.book.character[0].name #=> "Frau Java" #:parse-res end end end representable-3.0.4/test/hash_test.rb0000644000175000017500000001235313137322612016667 0ustar pravipravirequire 'test_helper' class HashPublicMethodsTest < Minitest::Spec #--- # from_hash class BandRepresenter < Representable::Decorator include Representable::Hash property :id property :name end let(:data) { {"id"=>1,"name"=>"Rancid"} } it { BandRepresenter.new(Band.new).from_hash(data)[:id, :name].must_equal [1, "Rancid"] } it { BandRepresenter.new(Band.new).parse(data)[:id, :name].must_equal [1, "Rancid"] } #--- # to_hash let(:band) { Band.new(1, "Rancid") } it { BandRepresenter.new(band).to_hash.must_equal data } it { BandRepresenter.new(band).render.must_equal data } end class HashWithScalarPropertyTest < MiniTest::Spec Album = Struct.new(:title) representer! do property :title end let(:album) { Album.new("Liar") } describe "#to_hash" do it "renders plain property" do album.extend(representer).to_hash.must_equal("title" => "Liar") end end describe "#from_hash" do it "parses plain property" do album.extend(representer).from_hash("title" => "This Song Is Recycled").title.must_equal "This Song Is Recycled" end # Fixes issue #115 it "allows nil value in the incoming document and corresponding nil value for the represented" do album = Album.new album.title.must_be_nil album.extend(representer).from_hash("title" => nil) end end end class HashWithTypedPropertyTest < MiniTest::Spec Album = Struct.new(:best_song) representer! do property :best_song, :class => Song do property :name end end let(:album) { Album.new(Song.new("Liar")) } describe "#to_hash" do it "renders embedded typed property" do album.extend(representer).to_hash.must_equal("best_song" => {"name" => "Liar"}) end end describe "#from_hash" do it "parses embedded typed property" do album.extend(representer).from_hash("best_song" => {"name" => "Go With Me"}) album.best_song.name.must_equal "Go With Me" end # nested nil removes nested object. it do album = Album.new(Song.new("Pre-medicated Murder")) album.extend(representer).from_hash("best_song" => nil) album.best_song.must_be_nil end # nested blank hash creates blank object when not populated. it do album = Album.new#(Song.new("Pre-medicated Murder")) album.extend(representer).from_hash("best_song" => {}) album.best_song.name.must_be_nil end # Fixes issue #115 it "allows nil value in the incoming document and corresponding nil value for the represented" do album = Album.new album.extend(representer).from_hash("best_song" => nil) album.best_song.must_be_nil end end end # TODO: move to AsTest. class HashWithTypedPropertyAndAs < MiniTest::Spec representer! do property :song, :class => Song, :as => :hit do property :name end end let(:album) { OpenStruct.new(:song => Song.new("Liar")).extend(representer) } it { album.to_hash.must_equal("hit" => {"name" => "Liar"}) } it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) } end # # describe "FIXME COMBINE WITH ABOVE with :extend and :as" do # # hash_song = Module.new do # # include Representable::XML # # self.representation_wrap = :song # # property :name # # end # # let(:hash_album) { Module.new do # # include Representable::XML # # self.representation_wrap = :album # # property :song, :extend => hash_song, :class => Song, :as => :hit # # end } # # let(:album) { OpenStruct.new(:song => Song.new("Liar")).extend(hash_album) } # # it { album.to_xml.must_equal_xml("Liar") } # # #it { album.from_hash("hit" => {"name" => "Go With Me"}).must_equal OpenStruct.new(:song => Song.new("Go With Me")) } # # end # end class HashWithTypedCollectionTest < MiniTest::Spec Album = Struct.new(:songs) representer! do collection :songs, class: Song do property :name property :track end end let(:album) { Album.new([Song.new("Liar", 1), Song.new("What I Know", 2)]) } describe "#to_hash" do it "renders collection of typed property" do album.extend(representer).to_hash.must_equal("songs" => [{"name" => "Liar", "track" => 1}, {"name" => "What I Know", "track" => 2}]) end end describe "#from_hash" do it "parses collection of typed property" do album.extend(representer).from_hash("songs" => [{"name" => "One Shot Deal", "track" => 4}, {"name" => "Three Way Dance", "track" => 5}]).must_equal Album.new([Song.new("One Shot Deal", 4), Song.new("Three Way Dance", 5)]) end end end class HashWithScalarCollectionTest < MiniTest::Spec Album = Struct.new(:songs) representer! { collection :songs } let(:album) { Album.new(["Jackhammer", "Terrible Man"]) } describe "#to_hash" do it "renders a block style list per default" do album.extend(representer).to_hash.must_equal("songs" => ["Jackhammer", "Terrible Man"]) end end describe "#from_hash" do it "parses a block style list" do album.extend(representer).from_hash("songs" => ["Off Key Melody", "Sinking"]).must_equal Album.new(["Off Key Melody", "Sinking"]) end end end representable-3.0.4/test/example.rb0000644000175000017500000001530613137322612016341 0ustar pravipravirequire 'bundler' Bundler.setup require 'representable/yaml' require 'ostruct' def reset_representer(*module_name) module_name.each do |mod| mod.module_eval do @representable_attrs = nil end end end class Song < OpenStruct end song = Song.new(:title => "Fallout", :track => 1) module SongRepresenter include Representable::JSON property :title property :track end puts song.extend(SongRepresenter).to_json rox = Song.new.extend(SongRepresenter).from_json(%{ {"title":"Roxanne"} }) puts rox.inspect module SongRepresenter include Representable::JSON self.representation_wrap= :hit property :title property :track end puts song.extend(SongRepresenter).to_json ######### collections reset_representer(SongRepresenter) module SongRepresenter include Representable::JSON property :title property :track collection :composers end song = Song.new(:title => "Fallout", :composers => ["Stewart Copeland", "Sting"]) puts song.extend(SongRepresenter).to_json ######### nesting types class Album < OpenStruct def name puts @table.inspect #@attributes @table[:name] end end module AlbumRepresenter include Representable::JSON property :name property :song, :extend => SongRepresenter, :class => Song end album = Album.new(:name => "The Police", :song => song) puts album.extend(AlbumRepresenter).to_json reset_representer(AlbumRepresenter) module AlbumRepresenter include Representable::JSON property :name collection :songs, :extend => SongRepresenter, :class => Song end album = Album.new(:name => "The Police", :songs => [song, Song.new(:title => "Synchronicity")]) puts album.extend(AlbumRepresenter).to_json album = Album.new.extend(AlbumRepresenter).from_json(%{{"name":"Offspring","songs":[{"title":"Genocide"},{"title":"Nitro","composers":["Offspring"]}]} }) puts album.inspect reset_representer(SongRepresenter) ######### using helpers (customizing the rendering/parsing) module AlbumRepresenter include Representable::JSON def name super.upcase end end puts Album.new(:name => "The Police"). extend(AlbumRepresenter).to_json reset_representer(SongRepresenter) ######### inheritance module SongRepresenter include Representable::JSON property :title property :track end module CoverSongRepresenter include Representable::JSON include SongRepresenter property :covered_by end song = Song.new(:title => "Truth Hits Everybody", :covered_by => "No Use For A Name") puts song.extend(CoverSongRepresenter).to_json ### XML require 'representable/xml' module SongRepresenter include Representable::XML property :title property :track collection :composers end song = Song.new(:title => "Fallout", :composers => ["Stewart Copeland", "Sting"]) puts song.extend(SongRepresenter).to_xml reset_representer(SongRepresenter) ### YAML require 'representable/yaml' module SongRepresenter include Representable::YAML property :title property :track collection :composers end puts song.extend(SongRepresenter).to_yaml SongRepresenter.module_eval do @representable_attrs = nil end ### YAML module SongRepresenter include Representable::YAML property :title property :track collection :composers, :style => :flow end puts song.extend(SongRepresenter).to_yaml SongRepresenter.module_eval do @representable_attrs = nil end # R/W support song = Song.new(:title => "You're Wrong", :track => 4) module SongRepresenter include Representable::Hash property :title, :readable => false property :track end puts song.extend(SongRepresenter).to_hash SongRepresenter.module_eval do @representable_attrs = nil end module SongRepresenter include Representable::Hash property :title, :writeable => false property :track end song = Song.new.extend(SongRepresenter) song.from_hash({:title => "Fallout", :track => 1}) puts song ######### custom methods in representer (using helpers) ######### conditions ######### :as ######### XML::Collection ######### polymorphic :extend and :class, instance context!, :instance class CoverSong < Song end songs = [Song.new(title: "Weirdo", track: 5), CoverSong.new(title: "Truth Hits Everybody", track: 6, copyright: "The Police")] album = Album.new(name: "Incognito", songs: songs) reset_representer(SongRepresenter, AlbumRepresenter) module SongRepresenter include Representable::Hash property :title property :track end module CoverSongRepresenter include Representable::Hash include SongRepresenter property :copyright end module AlbumRepresenter include Representable::Hash property :name collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter } end puts album.extend(AlbumRepresenter).to_hash reset_representer(AlbumRepresenter) module AlbumRepresenter include Representable::Hash property :name collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }, :class => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong : Song } #=> {"title"=>"Weirdo", "track"=>5} end album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]}) puts album.inspect reset_representer(AlbumRepresenter) module AlbumRepresenter include Representable::Hash property :name collection :songs, :extend => lambda { |song| song.is_a?(CoverSong) ? CoverSongRepresenter : SongRepresenter }, :instance => lambda { |hsh| hsh.has_key?("copyright") ? CoverSong.new : Song.new(original: true) } end album = Album.new.extend(AlbumRepresenter).from_hash({"name"=>"Incognito", "songs"=>[{"title"=>"Weirdo", "track"=>5}, {"title"=>"Truth Hits Everybody", "track"=>6, "copyright"=>"The Police"}]}) puts album.inspect ######### #hash reset_representer(SongRepresenter) module SongRepresenter include Representable::JSON property :title hash :ratings end puts Song.new(title: "Bliss", ratings: {"Rolling Stone" => 4.9, "FryZine" => 4.5}).extend(SongRepresenter).to_json ######### JSON::Hash module FavoriteSongsRepresenter include Representable::JSON::Hash end puts( {"Nick" => "Hyper Music", "El" => "Blown In The Wind"}.extend(FavoriteSongsRepresenter).to_json) require 'representable/json/hash' module FavoriteSongsRepresenter include Representable::JSON::Hash values extend: SongRepresenter, class: Song end puts( {"Nick" => Song.new(title: "Hyper Music")}.extend(FavoriteSongsRepresenter).to_json) require 'representable/json/collection' module SongsRepresenter include Representable::JSON::Collection items extend: SongRepresenter, class: Song end puts [Song.new(title: "Hyper Music"), Song.new(title: "Screenager")].extend(SongsRepresenter).to_json representable-3.0.4/test/default_test.rb0000644000175000017500000000217613137322612017372 0ustar pravipravirequire "test_helper" class DefaultTest < MiniTest::Spec Song = Struct.new(:id, :title) representer! do property :id property :title, default: "Huber Breeze" #->(options) { options[:default] } end describe "#from_hash" do let(:song) { Song.new.extend(representer) } it { song.from_hash({}).must_equal Song.new(nil, "Huber Breeze") } # default doesn't apply when empty string. it { song.from_hash({"title"=>""}).must_equal Song.new(nil, "") } it { song.from_hash({"title"=>nil}).must_equal Song.new(nil, nil) } it { song.from_hash({"title"=>"Blindfold"}).must_equal Song.new(nil, "Blindfold") } end describe "#to_json" do it "uses :default when not available from object" do Song.new.extend(representer).to_hash.must_equal({"title"=>"Huber Breeze"}) end it "uses value from represented object when present" do Song.new(nil, "After The War").extend(representer).to_hash.must_equal({"title"=>"After The War"}) end it "uses value from represented object when emtpy string" do Song.new(nil, "").extend(representer).to_hash.must_equal({"title"=>""}) end end endrepresentable-3.0.4/test/definition_test.rb0000644000175000017500000001637413137322612020103 0ustar pravipravirequire 'test_helper' class DefinitionTest < MiniTest::Spec Definition = Representable::Definition # TODO: test that we DON'T clone options, that must happen in describe "#initialize" do it do # new yields the defaultized options HASH. definition = Definition.new(:song, :extend => Module) do |options| options[:awesome] = true options[:parse_filter] << 1 # default variables options[:as].must_be_nil options[:extend].must_equal Module end definition.name.must_equal "song" # definition[:awesome].must_equal true definition[:parse_filter].must_equal Representable::Pipeline[1] definition[:render_filter].must_equal Representable::Pipeline[] end end describe "#[]" do let(:definition) { Definition.new(:song) } # default is nil. it { definition[:bla].must_be_nil } end # merge! describe "#merge!" do let(:definition) { Definition.new(:song, :whatever => true) } # merges new options. it { definition.merge!(:something => true)[:something].must_equal true } # doesn't override original options. it { definition.merge!({:something => true})[:whatever].must_equal true } # override original when passed in #merge!. it { definition.merge!({:whatever => false})[:whatever].must_equal false } # with block it do definition = Definition.new(:song, :extend => Module).merge!({:something => true}) do |options| options[:awesome] = true options[:render_filter] << 1 # default variables # options[:as].must_equal "song" # options[:extend].must_equal Module end definition[:awesome].must_equal true definition[:something].must_equal true definition[:render_filter].must_equal Representable::Pipeline[1] definition[:parse_filter].must_equal Representable::Pipeline[] end describe "with :parse_filter" do let(:definition) { Definition.new(:title, :parse_filter => 1) } # merges :parse_filter and :render_filter. it do merged = definition.merge!(:parse_filter => 2)[:parse_filter] merged.must_be_kind_of Representable::Pipeline merged.size.must_equal 2 end # :parse_filter can also be array. it { definition.merge!(:parse_filter => [2, 3])[:parse_filter].size.must_equal 3 } end # does not change arguments it do Definition.new(:title).merge!(options = {:whatever => 1}) options.must_equal(:whatever => 1) end end # delete! describe "#delete!" do let(:definition) { Definition.new(:song, serialize: "remove me!") } before { definition[:serialize].(nil).must_equal "remove me!" } it { definition.delete!(:serialize)[:serialize].must_be_nil } end # #inspect describe "#inspect" do it { Definition.new(:songs).inspect.must_equal "#songs @options={:name=>\"songs\", :parse_filter=>[], :render_filter=>[]}>" } end describe "generic API" do before do @def = Representable::Definition.new(:songs) end it "responds to #representer_module" do assert_nil Representable::Definition.new(:song).representer_module assert_equal Hash, Representable::Definition.new(:song, :extend => Hash).representer_module end describe "#typed?" do it "is false per default" do assert ! @def.typed? end it "is true when :class is present" do assert Representable::Definition.new(:songs, :class => Hash).typed? end it "is true when :extend is present, only" do assert Representable::Definition.new(:songs, :extend => Hash).typed? end it "is true when :instance is present, only" do assert Representable::Definition.new(:songs, :instance => Object.new).typed? end end describe "#representable?" do it { assert Definition.new(:song, :representable => true).representable? } it { Definition.new(:song, :representable => true, :extend => Object).representable?.must_equal true } it { refute Definition.new(:song, :representable => false, :extend => Object).representable? } it { assert Definition.new(:song, :extend => Object).representable? } it { refute Definition.new(:song).representable? } end it "responds to #getter and returns string" do assert_equal "songs", @def.getter end it "responds to #name" do assert_equal "songs", @def.name end it "responds to #setter" do assert_equal :"songs=", @def.setter end describe "nested: FIXME" do it do dfn = Representable::Definition.new(:songs, nested: Module) assert dfn.typed? dfn[:extend].(nil).must_equal Module end end describe "#clone" do subject { Representable::Definition.new(:title, :volume => 9, :clonable => Declarative::Option(1)) } it { subject.clone.must_be_kind_of Representable::Definition } it { subject.clone[:clonable].(nil).must_equal 1 } it "clones @options" do @def.merge!(:volume => 9) cloned = @def.clone cloned.merge!(:volume => 8) assert_equal @def[:volume], 9 assert_equal cloned[:volume], 8 end end end describe "#has_default?" do it "returns false if no :default set" do assert_equal false, Representable::Definition.new(:song).has_default? end it "returns true if :default set" do assert_equal true, Representable::Definition.new(:song, :default => nil).has_default? end end describe "#binding" do it "returns true when :binding is set" do assert Representable::Definition.new(:songs, :binding => Object)[:binding] end it "returns false when :binding is not set" do assert !Representable::Definition.new(:songs)[:binding] end end describe "#create_binding" do it "executes the block (without special context)" do definition = Representable::Definition.new(:title, :binding => lambda { |*args| @binding = Representable::Binding.new(*args) }) definition.create_binding.must_equal @binding end end describe ":collection => true" do before do @def = Representable::Definition.new(:songs, :collection => true, :tag => :song) end it "responds to #array?" do assert @def.array? end end describe ":default => value" do it "responds to #default" do @def = Representable::Definition.new(:song) assert_nil @def[:default] end it "accepts a default value" do @def = Representable::Definition.new(:song, :default => "Atheist Peace") assert_equal "Atheist Peace", @def[:default] end end describe ":hash => true" do before do @def = Representable::Definition.new(:songs, :hash => true) end it "responds to #hash?" do assert @def.hash? assert ! Representable::Definition.new(:songs).hash? end end describe ":binding => Object" do subject do Representable::Definition.new(:songs, :binding => Object) end it "responds to #binding" do assert_equal subject[:binding], Object end end describe "#[]=" do it "raises exception since it's deprecated" do assert_raises NoMethodError do Definition.new(:title)[:extend] = Module.new # use merge! after initialize. end end end end representable-3.0.4/test/user_options_test.rb0000644000175000017500000000131513137322612020471 0ustar pravipravirequire "test_helper" class UserOptionsTest < Minitest::Spec Song = Struct.new(:title) representer! do property :title, if: ->(options) { options[:user_options][:visible] } end it { Song.new("Run With It").extend(representer).to_hash.must_equal({}) } it { Song.new("Run With It").extend(representer).to_hash(user_options: {visible: true}).must_equal({"title"=>"Run With It"}) } it { Song.new("Run With It").extend(representer).from_hash("title"=>"Business Conduct").title.must_equal "Run With It" } it { Song.new("Run With It").extend(representer).from_hash({"title"=>"Business Conduct"}, user_options: {visible: true}).title.must_equal "Business Conduct" } end # Representable.deprecations=falserepresentable-3.0.4/test/json_test.rb0000644000175000017500000002534113137322612016716 0ustar pravipravirequire 'test_helper' require 'json' module JsonTest class JSONPublicMethodsTest < Minitest::Spec #--- # from_json class BandRepresenter < Representable::Decorator include Representable::JSON property :id property :name end let(:json) { '{"id":1,"name":"Rancid"}' } it { BandRepresenter.new(Band.new).from_json(json)[:id, :name].must_equal [1, "Rancid"] } it { BandRepresenter.new(Band.new).parse(json)[:id, :name].must_equal [1, "Rancid"] } #--- # to_json let(:band) { Band.new(1, "Rancid") } it { BandRepresenter.new(band).to_json.must_equal json } it { BandRepresenter.new(band).render.must_equal json } end class APITest < MiniTest::Spec Json = Representable::JSON Def = Representable::Definition describe "JSON module" do before do @Band = Class.new do include Representable::JSON property :name property :label attr_accessor :name, :label def initialize(name=nil) self.name = name if name end end @band = @Band.new end describe "#from_json" do before do @band = @Band.new @json = {:name => "Nofx", :label => "NOFX"}.to_json end it "parses JSON and assigns properties" do @band.from_json(@json) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end end describe "#from_hash" do before do @band = @Band.new @hash = {"name" => "Nofx", "label" => "NOFX"} end it "receives hash and assigns properties" do @band.from_hash(@hash) assert_equal ["Nofx", "NOFX"], [@band.name, @band.label] end it "respects :wrap option" do @band.from_hash({"band" => {"name" => "This Is A Standoff"}}, :wrap => :band) assert_equal "This Is A Standoff", @band.name end it "respects :wrap option over representation_wrap" do @Band.class_eval do self.representation_wrap = :group end @band.from_hash({"band" => {"name" => "This Is A Standoff"}}, :wrap => :band) assert_equal "This Is A Standoff", @band.name end end describe "#to_json" do it "delegates to #to_hash and returns string" do assert_json "{\"name\":\"Rise Against\"}", @Band.new("Rise Against").to_json end end describe "#to_hash" do it "returns unwrapped hash" do hash = @Band.new("Rise Against").to_hash assert_equal({"name"=>"Rise Against"}, hash) end it "respects :wrap option" do assert_equal({:band=>{"name"=>"NOFX"}}, @Band.new("NOFX").to_hash(:wrap => :band)) end it "respects :wrap option over representation_wrap" do @Band.class_eval do self.representation_wrap = :group end assert_equal({:band=>{"name"=>"Rise Against"}}, @Band.new("Rise Against").to_hash(:wrap => :band)) end end describe "#build_for" do it "returns TextBinding" do assert_kind_of Representable::Hash::Binding, Representable::Hash::Binding.build_for(Def.new(:band)) end it "returns CollectionBinding" do assert_kind_of Representable::Hash::Binding::Collection, Representable::Hash::Binding.build_for(Def.new(:band, :collection => true)) end end end describe "DCI" do module SongRepresenter include Representable::JSON property :name end module AlbumRepresenter include Representable::JSON property :best_song, :class => Song, :extend => SongRepresenter collection :songs, :class => Song, :extend => SongRepresenter end it "allows adding the representer by using #extend" do module BandRepresenter include Representable::JSON property :name end civ = Object.new civ.instance_eval do def name; "CIV"; end def name=(v) @name = v end end civ.extend(BandRepresenter) assert_json "{\"name\":\"CIV\"}", civ.to_json end it "extends contained models when serializing" do @album = Album.new([Song.new("I Hate My Brain"), mr=Song.new("Mr. Charisma")], mr) @album.extend(AlbumRepresenter) assert_json "{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}", @album.to_json end it "extends contained models when deserializing" do #@album = Album.new(Song.new("I Hate My Brain"), Song.new("Mr. Charisma")) @album = Album.new @album.extend(AlbumRepresenter) @album.from_json("{\"best_song\":{\"name\":\"Mr. Charisma\"},\"songs\":[{\"name\":\"I Hate My Brain\"},{\"name\":\"Mr. Charisma\"}]}") assert_equal "Mr. Charisma", @album.best_song.name end end end class PropertyTest < MiniTest::Spec describe "property :name" do class Band include Representable::JSON property :name attr_accessor :name end it "#from_json creates correct accessors" do band = Band.new.from_json({:name => "Bombshell Rocks"}.to_json) assert_equal "Bombshell Rocks", band.name end it "#to_json serializes correctly" do band = Band.new band.name = "Cigar" assert_json '{"name":"Cigar"}', band.to_json end end describe ":class => Item" do class Label include Representable::JSON property :name attr_accessor :name end class Album include Representable::JSON property :label, :class => Label attr_accessor :label end it "#from_json creates one Item instance" do album = Album.new.from_json('{"label":{"name":"Fat Wreck"}}') assert_equal "Fat Wreck", album.label.name end it "#to_json serializes" do label = Label.new; label.name = "Fat Wreck" album = Album.new; album.label = label assert_json '{"label":{"name":"Fat Wreck"}}', album.to_json end describe ":different_name, :class => Label" do before do @Album = Class.new do include Representable::JSON property :seller, :class => Label attr_accessor :seller end end it "#to_xml respects the different name" do label = Label.new; label.name = "Fat Wreck" album = @Album.new; album.seller = label assert_json "{\"seller\":{\"name\":\"Fat Wreck\"}}", album.to_json(:wrap => false) end end end describe ":as => :songName" do class Song include Representable::JSON property :name, :as => :songName attr_accessor :name end it "respects :as in #from_json" do song = Song.new.from_json({:songName => "Run To The Hills"}.to_json) assert_equal "Run To The Hills", song.name end it "respects :as in #to_json" do song = Song.new; song.name = "22 Acacia Avenue" assert_json '{"songName":"22 Acacia Avenue"}', song.to_json end end end class CollectionTest < MiniTest::Spec describe "collection :name" do class CD include Representable::JSON collection :songs attr_accessor :songs end it "#from_json creates correct accessors" do cd = CD.new.from_json({:songs => ["Out in the cold", "Microphone"]}.to_json) assert_equal ["Out in the cold", "Microphone"], cd.songs end it "zzz#to_json serializes correctly" do cd = CD.new cd.songs = ["Out in the cold", "Microphone"] assert_json '{"songs":["Out in the cold","Microphone"]}', cd.to_json end end describe "collection :name, :class => Band" do class Band include Representable::JSON property :name attr_accessor :name def initialize(name="") self.name = name end end class Compilation include Representable::JSON collection :bands, :class => Band attr_accessor :bands end describe "#from_json" do it "pushes collection items to array" do cd = Compilation.new.from_json({:bands => [ {:name => "Cobra Skulls"}, {:name => "Diesel Boy"}]}.to_json) assert_equal ["Cobra Skulls", "Diesel Boy"], cd.bands.map(&:name).sort end end it "responds to #to_json" do cd = Compilation.new cd.bands = [Band.new("Diesel Boy"), Band.new("Bad Religion")] assert_json '{"bands":[{"name":"Diesel Boy"},{"name":"Bad Religion"}]}', cd.to_json end end describe ":as => :songList" do class Songs include Representable::JSON collection :tracks, :as => :songList attr_accessor :tracks end it "respects :as in #from_json" do songs = Songs.new.from_json({:songList => ["Out in the cold", "Microphone"]}.to_json) assert_equal ["Out in the cold", "Microphone"], songs.tracks end it "respects option in #to_json" do songs = Songs.new songs.tracks = ["Out in the cold", "Microphone"] assert_json '{"songList":["Out in the cold","Microphone"]}', songs.to_json end end end class HashTest < MiniTest::Spec describe "hash :songs" do representer!(:module => Representable::JSON) do hash :songs end subject { OpenStruct.new.extend(representer) } it "renders with #to_json" do subject.songs = {:one => "65", :two => "Emo Boy"} assert_json "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}", subject.to_json end it "parses with #from_json" do assert_equal({"one" => "65", "two" => ["Emo Boy"]}, subject.from_json("{\"songs\":{\"one\":\"65\",\"two\":[\"Emo Boy\"]}}").songs) end end describe "hash :songs, :class => Song" do representer!(:module => Representable::JSON) do hash :songs, :extend => Module.new { include Representable::JSON; property :name }, :class => Song end it "renders" do OpenStruct.new(:songs => {"7" => Song.new("Contemplation")}).extend(representer).to_hash.must_equal("songs"=>{"7"=>{"name"=>"Contemplation"}}) end describe "parsing" do subject { OpenStruct.new.extend(representer) } let(:hsh) { {"7"=>{"name"=>"Contemplation"}} } it "parses incoming hash" do subject.from_hash("songs"=>hsh).songs.must_equal({"7"=>Song.new("Contemplation")}) end it "doesn't modify the incoming hash" do subject.from_hash("songs"=> incoming_hash = hsh.dup) hsh.must_equal incoming_hash end end end end end representable-3.0.4/test/uncategorized_test.rb0000644000175000017500000000503513137322612020606 0ustar pravipravirequire "test_helper" class StopWhenIncomingObjectFragmentIsNilTest < MiniTest::Spec Album = Struct.new(:id, :songs) Song = Struct.new(:title) representer!(decorator: true) do property :id collection :songs, class: Song, parse_pipeline: ->(input, options) { # TODO: test if :doc is set for parsing. test if options are ok and contain :user_options! Representable::Pipeline[*parse_functions.insert(3, Representable::StopOnNil)] } do property :title end end it do album = Album.new representer.new(album).from_hash({"id"=>1, "songs"=>[{"title"=>"Walkie Talkie"}]}).songs.must_equal [Song.new("Walkie Talkie")] end it do album = Album.new(2, ["original"]) representer.new(album).from_hash({"id"=>1, "songs"=>nil}).songs.must_equal ["original"] end end class RenderPipelineOptionTest < MiniTest::Spec Album = Struct.new(:id, :songs) NilToNA = ->(input, options) { input.nil? ? "n/a" : input } representer!(decorator: true) do property :id, render_pipeline: ->(input, options) do Representable::Pipeline[*render_functions.insert(2, options[:options][:user_options][:function])] end end it { representer.new(Album.new).to_hash(user_options: {function: NilToNA}).must_equal({"id"=>"n/a"}) } it { representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA}).must_equal({"id"=>1}) } it "is cached" do decorator = representer.new(Album.new) decorator.to_hash(user_options: {function: NilToNA}).must_equal({"id"=>"n/a"}) decorator.to_hash(user_options: {function: nil}).must_equal({"id"=>"n/a"}) # non-sense function is not applied. end end class ParsePipelineOptionTest < MiniTest::Spec Album = Struct.new(:id, :songs) NilToNA = ->(input, options) { input.nil? ? "n/a" : input } representer!(decorator: true) do property :id, parse_pipeline: ->(input, options) do Representable::Pipeline[*parse_functions.insert(3, options[:options][:user_options][:function])].extend(Representable::Pipeline::Debug) end end it { representer.new(Album.new).from_hash({"id"=>nil}, user_options: {function: NilToNA}).id.must_equal "n/a" } it { representer.new(Album.new(1)).to_hash(user_options: {function: NilToNA}).must_equal({"id"=>1}) } it "is cached" do decorator = representer.new(Album.new) decorator.from_hash({"id"=>nil}, user_options: {function: NilToNA}).id.must_equal "n/a" decorator.from_hash({"id"=>nil}, user_options: {function: "nonsense"}).id.must_equal "n/a" # non-sense function is not applied. end endrepresentable-3.0.4/test/class_test.rb0000644000175000017500000000616313137322612017053 0ustar pravipravirequire 'test_helper' class ClassTest < BaseTest class RepresentingSong attr_reader :name def from_hash(doc, *) @name = doc["__name__"] self # DISCUSS: do we wanna be able to return whatever we want here? this is a trick to replace the actual object end end describe "class: ClassName, only" do representer! do property :song, :class => RepresentingSong # supposed this class exposes #from_hash itself. end it "creates fresh instance and doesn't extend" do song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song song.must_be_instance_of RepresentingSong song.name.must_equal "Captured" end end describe "class: lambda, only" do representer! do property :song, :class => lambda { |*| RepresentingSong } end it "creates fresh instance and doesn't extend" do song = representer.prepare(OpenStruct.new).from_hash({"song" => {"__name__" => "Captured"}}).song song.must_be_instance_of RepresentingSong song.name.must_equal "Captured" end end # this throws a DeserializationError now. describe "lambda { nil }" do representer! do property :title, :class => nil end it do assert_raises Representable::DeserializeError do OpenStruct.new.extend(representer).from_hash({"title" => {}}) end end end describe "lambda receiving fragment and args" do let(:klass) { Class.new do class << self attr_accessor :args end def from_hash(*) self.class.new end end } representer!(:inject => :klass) do _klass = klass property :song, :class => lambda { |options| _klass.args=([options[:fragment],options[:user_options]]); _klass } end it { representer.prepare(OpenStruct.new).from_hash({"song" => {"name" => "Captured"}}, user_options: {volume: true}).song.class.args. must_equal([{"name"=>"Captured"}, {:volume=>true}]) } end describe "collection: lambda receiving fragment and args" do let(:klass) { Class.new do class << self attr_accessor :args end def from_hash(*) self.class.new end end } representer!(:inject => :klass) do _klass = klass collection :songs, :class => lambda { |options| _klass.args=([options[:fragment],options[:index],options[:user_options]]); _klass } end it { representer.prepare(OpenStruct.new).from_hash({"songs" => [{"name" => "Captured"}]}, user_options: {volume: true}).songs.first.class.args. must_equal([{"name"=>"Captured"}, 0, {:volume=>true}]) } end describe "class: implementing #from_hash" do let(:parser) do Class.new do def from_hash(*) [1,2,3,4] end end end representer!(:inject => :parser) do property :song, :class => parser # supposed this class exposes #from_hash itself. end it "allows returning arbitrary objects in #from_hash" do representer.prepare(OpenStruct.new).from_hash({"song" => 1}).song.must_equal [1,2,3,4] end end end #TODO: test fragment, # `class: Song` only, no :extend. representable-3.0.4/test/include_exclude_test.rb0000644000175000017500000000532113137322612021075 0ustar pravipravirequire "test_helper" class IncludeExcludeTest < Minitest::Spec Song = Struct.new(:title, :artist, :id) Artist = Struct.new(:name, :id, :songs) representer!(decorator: true) do property :title property :artist, class: Artist do property :name property :id collection :songs, class: Song do property :title property :id end end end let(:song) { Song.new("Listless", Artist.new("7yearsbadluck", 1 )) } let(:decorator) { representer.new(song) } describe "#from_hash" do it "accepts :exclude option" do decorator.from_hash({"title"=>"Don't Smile In Trouble", "artist"=>{"id"=>2}}, exclude: [:title]) song.title.must_equal "Listless" song.artist.must_equal Artist.new(nil, 2) end it "accepts :include option" do decorator.from_hash({"title"=>"Don't Smile In Trouble", "artist"=>{"id"=>2}}, include: [:title]) song.title.must_equal "Don't Smile In Trouble" song.artist.must_equal Artist.new("7yearsbadluck", 1) end it "accepts nested :exclude/:include option" do decorator.from_hash({"title"=>"Don't Smile In Trouble", "artist"=>{"name"=>"Foo", "id"=>2, "songs"=>[{"id"=>1, "title"=>"Listless"}]}}, exclude: [:title], artist: { exclude: [:id], songs: { include: [:title] } } ) song.title.must_equal "Listless" song.artist.must_equal Artist.new("Foo", nil, [Song.new("Listless", nil, nil)]) end end describe "#to_hash" do it "accepts :exclude option" do decorator.to_hash(exclude: [:title]).must_equal({"artist"=>{"name"=>"7yearsbadluck", "id"=>1}}) end it "accepts :include option" do decorator.to_hash(include: [:title]).must_equal({"title"=>"Listless"}) end it "accepts nested :exclude/:include option" do decorator = representer.new(Song.new("Listless", Artist.new("7yearsbadluck", 1, [Song.new("C.O.A.B.I.E.T.L.")]))) decorator.to_hash( exclude: [:title], artist: { exclude: [:id], songs: { include: [:title] } } ).must_equal({"artist"=>{"name"=>"7yearsbadluck", "songs"=>[{"title"=>"C.O.A.B.I.E.T.L."}]}}) end end it "xdoes not propagate private options to nested objects" do Cover = Struct.new(:title, :original) cover_rpr = Module.new do include Representable::Hash property :title property :original, extend: self end # FIXME: we should test all representable-options (:include, :exclude, ?) Cover.new("Roxanne", Cover.new("Roxanne (Don't Put On The Red Light)")).extend(cover_rpr). to_hash(:include => [:original]).must_equal({"original"=>{"title"=>"Roxanne (Don't Put On The Red Light)"}}) end end representable-3.0.4/test/schema_test.rb0000644000175000017500000000706213137322612017205 0ustar pravipravirequire 'test_helper' # Include Inherit Module And Decorator Test class SchemaTest < MiniTest::Spec module Genre include Representable property :genre end module LinkFeature def self.included(base) base.extend(Link) end module Link def link end end end module Module include Representable::Hash feature LinkFeature property :title property :label do # extend: LabelModule property :name link # feature property :location do property :city link # feature. end end property :album, :extend => lambda { raise "don't manifest me!" } # this is not an inline decorator, don't manifest it. include Genre # Schema::Included::included is called! end class WithLocationStreetRepresenter < Representable::Decorator include Representable::Hash feature LinkFeature property :title property :label do # extend: LabelModule property :name link # feature property :location do property :city link # feature. end end end describe "3-level deep with features" do let(:label) { OpenStruct.new(:name => "Epitaph", :location => OpenStruct.new(:city => "Sanfran", :name => "DON'T SHOW ME!")) } # Module does correctly include features in inlines. it { band.extend(Module).to_hash.must_equal({"label"=>{"name"=>"Epitaph", "location"=>{"city"=>"Sanfran"}}, "genre"=>"Punkrock"}) } # Decorator does correctly include features in inlines. it { Decorator.new(band).to_hash.must_equal({"label"=>{"name"=>"Epitaph", "location"=>{"city"=>"Sanfran"}}, "genre"=>"Punkrock"}) } end class Decorator < Representable::Decorator feature Representable::Hash include Module end # puts Decorator.representable_attrs[:definitions].inspect let(:label) { OpenStruct.new(:name => "Fat Wreck", :city => "San Francisco", :employees => [OpenStruct.new(:name => "Mike")], :location => OpenStruct.new(:city => "Sanfran")) } let(:band) { OpenStruct.new(:genre => "Punkrock", :label => label) } # it { FlatlinersDecorator.new( OpenStruct.new(label: OpenStruct.new) ). # to_hash.must_equal({}) } it do Decorator.new(band).to_hash.must_equal({"genre"=>"Punkrock", "label"=>{"name"=>"Fat Wreck", "location"=>{"city"=>"Sanfran"}}}) end class InheritDecorator < Representable::Decorator include Representable::Hash include Module property :label, inherit: true do # decorator.rb:27:in `initialize': superclass must be a Class (Module given) property :city property :location, :inherit => true do property :city end end end it do InheritDecorator.new(band).to_hash.must_equal({"genre"=>"Punkrock", "label"=>{"name"=>"Fat Wreck", "city"=>"San Francisco", "location"=>{"city"=>"Sanfran"}}}) end class InheritFromDecorator < InheritDecorator property :label, inherit: true do collection :employees do property :name end end end it do InheritFromDecorator.new(band).to_hash.must_equal({"genre"=>"Punkrock", "label"=>{"name"=>"Fat Wreck", "city"=>"San Francisco", "employees"=>[{"name"=>"Mike"}], "location"=>{"city"=>"Sanfran"}}}) end end class ApplyTest < MiniTest::Spec class AlbumDecorator < Representable::Decorator include Representable::Hash property :title property :hit do property :title end collection :songs do property :title end property :band do # yepp, people do crazy stuff like that. property :label do property :name end end end endrepresentable-3.0.4/test/lonely_test.rb0000644000175000017500000001705513137322612017252 0ustar pravipravirequire 'test_helper' require 'representable/json/hash' class LonelyRepresenterTest < MiniTest::Spec # test ::items without arguments, render-only. for_formats( :hash => [Representable::Hash::Collection, [{"name"=>"Resist Stance"}, {"name"=>"Suffer"}]], :json => [Representable::JSON::Collection, "[{\"name\":\"Resist Stance\"},{\"name\":\"Suffer\"}]"], :xml => [Representable::XML::Collection, "Resist StanceSuffer"], ) do |format, mod, output, input| describe "[#{format}] lonely collection, render-only" do # TODO: introduce :representable option? let(:format) { format } representer!(module: mod) do items do property :name end end let(:album) { [Song.new("Resist Stance"), Song.new("Suffer")].extend(representer) } it "calls #to_hash on song instances, nothing else" do render(album).must_equal_document(output) end end end module SongRepresenter include Representable::JSON property :name end let(:decorator) { rpr = representer; Class.new(Representable::Decorator) { include Representable::Hash; include rpr } } describe "JSON::Collection" do let(:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] } let(:json) { "[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]" } describe "with contained objects" do let(:representer) { Module.new do include Representable::JSON::Collection items :class => Song, :extend => SongRepresenter end } it "renders array" do assert_json json, songs.extend(representer).to_json end it "renders array with decorator" do assert_json json, decorator.new(songs).to_json end it "parses array" do [].extend(representer).from_json(json).must_equal songs end it "parses array with decorator" do decorator.new([]).from_json(json).must_equal songs end end describe "with inline representer" do representer!(:module => Representable::JSON::Collection) do items :class => Song do property :name end end it { songs.extend(representer).to_json.must_equal json } it { [].extend(representer).from_json(json).must_equal songs } end describe "with contained text" do let(:representer) { Module.new do include Representable::JSON::Collection end } let(:songs) { ["Days Go By", "Can't Take Them All"] } let(:json) { "[\"Days Go By\",\"Can't Take Them All\"]" } it "renders contained items #to_json" do assert_json json, songs.extend(representer).to_json end it "returns objects array from #from_json" do [].extend(representer).from_json(json).must_equal songs end end end describe "Hash::Collection with dynamic options" do class One < Representable::Decorator def to_hash(*); "One: #{represented}"; end end class Two < Representable::Decorator def to_hash(*); "Two: #{represented}"; end end representer!(module: Representable::Hash::Collection) do items extend: ->(options) { options[:input] == 1 ? options[:user_options][:one] : options[:user_options][:two] } end it { [1,2].extend(representer).to_hash(user_options: {one: One, two: Two}).must_equal(["One: 1", "Two: 2"]) } end describe "JSON::Hash" do # TODO: move to HashTest. describe "with contained objects" do let(:representer) { Module.new do include Representable::JSON::Hash values :class => Song, :extend => SongRepresenter end } let(:json) { "{\"one\":{\"name\":\"Days Go By\"},\"two\":{\"name\":\"Can't Take Them All\"}}" } let(:songs) { {"one" => Song.new("Days Go By"), "two" => Song.new("Can't Take Them All")} } describe "#to_json" do it "renders hash" do songs.extend(representer).to_json.must_equal json end it "renders hash with decorator" do decorator.new(songs).to_json.must_equal json end it "respects :exclude" do assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:exclude => [:one]) end it "respects :include" do assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:include => [:two]) end end describe "#from_json" do it "returns objects array" do {}.extend(representer).from_json(json).must_equal songs end it "parses hash with decorator" do decorator.new({}).from_json(json).must_equal songs end it "respects :exclude" do assert_equal({"two" => Song.new("Can't Take Them All")}, {}.extend(representer).from_json(json, :exclude => [:one])) end it "respects :include" do assert_equal({"one" => Song.new("Days Go By")}, {}.extend(representer).from_json(json, :include => [:one])) end end describe "with inline representer" do representer!(:module => Representable::JSON::Hash) do values :class => Song do property :name end end it { songs.extend(representer).to_json.must_equal json } it { {}.extend(representer).from_json(json).must_equal songs } end end describe "with scalar" do let(:representer) { Module.new do include Representable::JSON::Hash end } let(:json) { %{{"one":1,"two":2}} } let(:data) { {one: 2, two: 3} } describe "#to_json" do it { data.extend(representer).to_json.must_equal %{{"one":2,"two":3}} } # it "respects :exclude" do # assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:exclude => [:one]) # end # it "respects :include" do # assert_json "{\"two\":{\"name\":\"Can't Take Them All\"}}", {:one => Song.new("Days Go By"), :two => Song.new("Can't Take Them All")}.extend(representer).to_json(:include => [:two]) # end end describe "#from_json" do # FIXME: what's the point of this? it { data.extend(representer).from_hash(data).must_equal data } end end describe "with contained text" do before do @songs_representer = Module.new do include Representable::JSON::Collection end end it "renders contained items #to_json" do assert_json "[\"Days Go By\",\"Can't Take Them All\"]", ["Days Go By", "Can't Take Them All"].extend(@songs_representer).to_json end it "returns objects array from #from_json" do assert_equal ["Days Go By", "Can't Take Them All"], [].extend(@songs_representer).from_json("[\"Days Go By\",\"Can't Take Them All\"]") end end end end # describe "Hash::Collection with :include" do class CollectionWithIncludeTest < MiniTest::Spec Song = Struct.new(:id, :title) representer!(decorator: true, module: Representable::Hash::Collection) do items do property :id property :title end end it { representer.new([Song.new(1, "ACAB")]).to_hash.must_equal([{"id"=>1, "title"=>"ACAB"}]) } it { representer.new([Song.new(1, "ACAB")]).to_hash(include: [:title]).must_equal([{"title"=>"ACAB"}]) } end representable-3.0.4/test/pipeline_test.rb0000644000175000017500000002126113137322612017547 0ustar pravipravirequire "test_helper" class PipelineTest < MiniTest::Spec Song = Struct.new(:title, :artist) Artist = Struct.new(:name) Album = Struct.new(:ratings, :artists) R = Representable P = R::Pipeline Getter = ->(input, options) { "Yo" } StopOnNil = ->(input, options) { input } SkipRender = ->(input, *) { input == "Yo" ? input : P::Stop } Prepare = ->(input, options) { "Prepare(#{input})" } Deserialize = ->(input, options) { "Deserialize(#{input}, #{options[:fragment]})" } SkipParse = ->(input, options) { input } CreateObject = ->(input, options) { OpenStruct.new } Setter = ->(input, options) { "Setter(#{input})" } AssignFragment = ->(input, options) { options[:fragment] = input } it "linear" do P[SkipParse, Setter].("doc", {fragment: 1}).must_equal "Setter(doc)" # parse style. P[AssignFragment, SkipParse, CreateObject, Prepare].("Bla", {}).must_equal "Prepare(#)" # render style. P[Getter, StopOnNil, SkipRender, Prepare, Setter].(nil, {}). must_equal "Setter(Prepare(Yo))" # pipeline = Representable::Pipeline[SkipParse , SetResult, ModifyResult] # pipeline.(fragment: "yo!").must_equal "modified object from yo!" end Stopping = ->(input, options) { return P::Stop if options[:fragment] == "stop!"; input } it "stopping" do pipeline = Representable::Pipeline[SkipParse, Stopping, Prepare] pipeline.(nil, fragment: "oy!").must_equal "Prepare()" pipeline.(nil, fragment: "stop!").must_equal Representable::Pipeline::Stop end describe "Collect" do Reverse = ->(input, options) { input.reverse } Add = ->(input, options) { "#{input}+" } let(:pipeline) { R::Collect[Reverse, Add] } it { pipeline.(["yo!", "oy!"], {}).must_equal ["!oy+", "!yo+"] } describe "Pipeline with Collect" do let(:pipeline) { P[Reverse, R::Collect[Reverse, Add]] } it { pipeline.(["yo!", "oy!"], {}).must_equal ["!yo+", "!oy+"] } end end ######### scalar property let(:title) { dfn = R::Definition.new(:title) R::Hash::Binding.new(dfn) } it "rendering scalar property" do doc = {} P[ R::GetValue, R::StopOnSkipable, R::AssignName, R::WriteFragment ].(nil, {represented: Song.new("Lime Green"), binding: title, doc: doc}).must_equal "Lime Green" doc.must_equal({"title"=>"Lime Green"}) end it "parsing scalar property" do P[ R::AssignName, R::ReadFragment, R::StopOnNotFound, R::OverwriteOnNil, # R::SkipParse, R::SetValue, ].extend(P::Debug).(doc={"title"=>"Eruption"}, {represented: song=Song.new("Lime Green"), binding: title, doc: doc}).must_equal "Eruption" song.title.must_equal "Eruption" end module ArtistRepresenter include Representable::Hash property :name end let(:artist) { dfn = R::Definition.new(:artist, extend: ArtistRepresenter, class: Artist) R::Hash::Binding.new(dfn) } let(:song_model) { Song.new("Lime Green", Artist.new("Diesel Boy")) } it "rendering typed property" do doc = {} P[ R::GetValue, R::StopOnSkipable, R::StopOnNil, R::Decorate, R::Serialize, R::AssignName, R::WriteFragment ].extend(P::Debug).(nil, {represented: song_model, binding: artist, doc: doc, options: {}}).must_equal({"name" => "Diesel Boy"}) doc.must_equal({"artist"=>{"name"=>"Diesel Boy"}}) end it "parsing typed property" do P[ R::AssignName, R::ReadFragment, R::StopOnNotFound, R::OverwriteOnNil, R::AssignFragment, R::CreateObject::Class, R::Decorate, R::Deserialize, R::SetValue, ].extend(P::Debug).(doc={"artist"=>{"name"=>"Doobie Brothers"}}, {represented: song_model, binding: artist, doc: doc, options: {}}).must_equal model=Artist.new("Doobie Brothers") song_model.artist.must_equal model end ######### collection :ratings let(:ratings) { dfn = R::Definition.new(:ratings, collection: true, skip_render: ->(*) { false }) R::Hash::Binding::Collection.new(dfn) } it "render scalar collection" do doc = {} P[ R::GetValue, R::StopOnSkipable, R::Collect[ R::SkipRender, ], R::AssignName, R::WriteFragment ].extend(P::Debug).(nil, {represented: Album.new([1,2,3]), binding: ratings, doc: doc, options: {}}).must_equal([1,2,3]) doc.must_equal({"ratings"=>[1,2,3]}) end ######### collection :songs, extend: SongRepresenter let(:artists) { dfn = R::Definition.new(:artists, collection: true, extend: ArtistRepresenter, class: Artist) R::Hash::Binding::Collection.new(dfn) } it "render typed collection" do doc = {} P[ R::GetValue, R::StopOnSkipable, R::Collect[ R::Decorate, R::Serialize, ], R::AssignName, R::WriteFragment ].extend(P::Debug).(nil, {represented: Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]), binding: artists, doc: doc, options: {}}).must_equal([{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]) doc.must_equal({"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]}) end let(:album_model) { Album.new(nil, [Artist.new("Diesel Boy"), Artist.new("Van Halen")]) } it "parse typed collection" do doc = {"artists"=>[{"name"=>"Diesel Boy"}, {"name"=>"Van Halen"}]} P[ R::AssignName, R::ReadFragment, R::StopOnNotFound, R::OverwriteOnNil, # R::SkipParse, R::Collect[ R::AssignFragment, R::CreateObject::Class, R::Decorate, R::Deserialize, ], R::SetValue, ].extend(P::Debug).(doc, {represented: album_model, binding: artists, doc: doc, options: {}}).must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")]) album_model.artists.must_equal([Artist.new("Diesel Boy"), Artist.new("Van Halen")]) end # TODO: test with arrays, too, not "only" Pipeline instances. describe "#Insert Pipeline[], Function, replace: OldFunction" do let(:pipeline) { P[R::GetValue, R::StopOnSkipable, R::StopOnNil] } it "returns Pipeline instance when passing in Pipeline instance" do P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_be_instance_of(R::Pipeline) end it "replaces if exists" do # pipeline.insert!(R::Default, replace: R::StopOnSkipable) P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).must_equal P[R::GetValue, R::Default, R::StopOnNil] pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil] end it "replaces Function instance" do pipeline = P[R::Prepare, R::StopOnSkipable, R::StopOnNil] P::Insert.(pipeline, R::Default, replace: R::Prepare).must_equal P[R::Default, R::StopOnSkipable, R::StopOnNil] pipeline.must_equal P[R::Prepare, R::StopOnSkipable, R::StopOnNil] end it "does not replace when not existing" do P::Insert.(pipeline, R::Default, replace: R::Prepare) pipeline.must_equal P[R::GetValue, R::StopOnSkipable, R::StopOnNil] end it "applies on nested Collect" do pipeline = P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] P::Insert.(pipeline, R::Default, replace: R::StopOnSkipable).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]" pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] P::Insert.(pipeline, R::Default, replace: R::StopOnNil).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], Default]" end it "applies on nested Collect with Function::CreateObject" do pipeline = P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil] P::Insert.(pipeline, R::Default, replace: R::CreateObject).extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, Default], StopOnNil]" pipeline.must_equal P[R::GetValue, R::Collect[R::GetValue, R::CreateObject], R::StopOnNil] end end describe "Insert.(delete: true)" do let(:pipeline) { P[R::GetValue, R::StopOnNil] } it do P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[StopOnNil]" pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, StopOnNil]" end end describe "Insert.(delete: true) with Collect" do let(:pipeline) { P[R::GetValue, R::Collect[R::GetValue, R::StopOnSkipable], R::StopOnNil] } it do P::Insert.(pipeline, R::GetValue, delete: true).extend(P::Debug).inspect.must_equal "Pipeline[Collect[StopOnSkipable], StopOnNil]" pipeline.extend(P::Debug).inspect.must_equal "Pipeline[GetValue, Collect[GetValue, StopOnSkipable], StopOnNil]" end end endrepresentable-3.0.4/test/realistic_benchmark.rb0000644000175000017500000000475513137322612020705 0ustar pravipravirequire 'test_helper' require 'benchmark' SONG_PROPERTIES = 50.times.collect do |i| "song_property_#{i}" end module SongRepresenter include Representable::JSON SONG_PROPERTIES.each { |p| property p } end class NestedProperty < Representable::Decorator include Representable::JSON SONG_PROPERTIES.each { |p| property p } end class SongDecorator < Representable::Decorator include Representable::JSON SONG_PROPERTIES.each { |p| property p, extend: NestedProperty } end class AlbumRepresenter < Representable::Decorator include Representable::JSON # collection :songs, extend: SongRepresenter collection :songs, extend: SongDecorator end Song = Struct.new(*SONG_PROPERTIES.map(&:to_sym)) Album = Struct.new(:songs) def random_song Song.new(*SONG_PROPERTIES.collect { |p| Song.new(*SONG_PROPERTIES) }) end times = [] 3.times.each do album = Album.new(100.times.collect { random_song }) times << Benchmark.measure do puts "================ next!" AlbumRepresenter.new(album).to_json end end puts times.join("") album = Album.new(100.times.collect { random_song }) require 'ruby-prof' RubyProf.start AlbumRepresenter.new(album).to_hash res = RubyProf.stop printer = RubyProf::FlatPrinter.new(res) printer.print(array = []) array[0..60].each { |a| puts a } # 100 songs, 100 attrs # 0.050000 0.000000 0.050000 ( 0.093157) ## 100 songs, 1000 attrs # 0.470000 0.010000 0.480000 ( 0.483708) ### without binding cache: # 2.790000 0.030000 2.820000 ( 2.820190) ### with extend: on Song, with binding cache> # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0 ### without skip? # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3 ### without :writer # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2 ### without :render_filter # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0 ###without default_for and skipable? # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7 ### without :serialize # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7 ### using decorator # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6 ### with prepare AFTER representable? # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3 # representable 2.0 # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0 # no method missing # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5 # no def_delegator in Definition # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1 representable-3.0.4/test/representable_test.rb0000644000175000017500000003607113137322612020602 0ustar pravipravirequire 'test_helper' class RepresentableTest < MiniTest::Spec class Band include Representable::Hash property :name attr_accessor :name end class PunkBand < Band property :street_cred attr_accessor :street_cred end module BandRepresentation include Representable property :name end module PunkBandRepresentation include Representable include BandRepresentation property :street_cred end describe "#representable_attrs" do describe "in module" do it "allows including the concrete representer module later" do vd = class VD attr_accessor :name, :street_cred include Representable::JSON include PunkBandRepresentation end.new vd.name = "Vention Dention" vd.street_cred = 1 assert_json "{\"name\":\"Vention Dention\",\"street_cred\":1}", vd.to_json end #it "allows including the concrete representer module only" do # require 'representable/json' # module RockBandRepresentation # include Representable::JSON # property :name # end # vd = class VH # include RockBandRepresentation # end.new # vd.name = "Van Halen" # assert_equal "{\"name\":\"Van Halen\"}", vd.to_json #end end end describe "inheritance" do class CoverSong < OpenStruct end module SongRepresenter include Representable::Hash property :name end module CoverSongRepresenter include Representable::Hash include SongRepresenter property :by end it "merges properties from all ancestors" do props = {"name"=>"The Brews", "by"=>"Nofx"} assert_equal(props, CoverSong.new(props).extend(CoverSongRepresenter).to_hash) end it "allows mixing in multiple representers" do class Bodyjar include Representable::XML include Representable::JSON include PunkBandRepresentation self.representation_wrap = "band" attr_accessor :name, :street_cred end band = Bodyjar.new band.name = "Bodyjar" assert_json "{\"band\":{\"name\":\"Bodyjar\"}}", band.to_json assert_xml_equal "Bodyjar", band.to_xml end it "allows extending with different representers subsequentially" do module SongXmlRepresenter include Representable::XML property :name, :as => "name", :attribute => true end module SongJsonRepresenter include Representable::JSON property :name end @song = Song.new("Days Go By") assert_xml_equal "", @song.extend(SongXmlRepresenter).to_xml assert_json "{\"name\":\"Days Go By\"}", @song.extend(SongJsonRepresenter).to_json end # test if we call super in # ::inherited # ::included # ::extended module Representer include Representable # overrides ::inherited. end class BaseClass def self.inherited(subclass) super subclass.instance_eval { def other; end } end include Representable # overrides ::inherited. include Representer end class SubClass < BaseClass # triggers Representable::inherited, then OtherModule::inherited. end # test ::inherited. it do BaseClass.respond_to?(:other).must_equal false SubClass.respond_to?(:other).must_equal true end module DifferentIncluded def included(includer) includer.instance_eval { def different; end } end end module CombinedIncluded extend DifferentIncluded # defines ::included. include Representable # overrides ::included. end class IncludingClass include Representable include CombinedIncluded end # test ::included. it do IncludingClass.respond_to?(:representable_attrs) # from Representable IncludingClass.respond_to?(:different) end end describe "#property" do it "doesn't modify options hash" do options = {} representer.property(:title, options) options.must_equal({}) end representer! {} it "returns the Definition instance" do representer.property(:name).must_be_kind_of Representable::Definition end end describe "#collection" do class RockBand < Band collection :albums end it "creates correct Definition" do assert_equal "albums", RockBand.representable_attrs.get(:albums).name assert RockBand.representable_attrs.get(:albums).array? end end describe "#hash" do it "also responds to the original method" do assert_kind_of Integer, BandRepresentation.hash end end class Hometown attr_accessor :name end module HometownRepresentable include Representable::JSON property :name end # DISCUSS: i don't like the JSON requirement here, what about some generic test module? class PopBand include Representable::JSON property :name property :groupies property :hometown, class: Hometown, extend: HometownRepresentable attr_accessor :name, :groupies, :hometown end describe "#update_properties_from" do before do @band = PopBand.new end it "copies values from document to object" do @band.from_hash({"name"=>"No One's Choice", "groupies"=>2}) assert_equal "No One's Choice", @band.name assert_equal 2, @band.groupies end it "ignores non-writeable properties" do @band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new @band.from_hash("name" => "Iron Maiden", "groupies" => 2, "founders" => ["Steve Harris"]) assert_equal "Iron Maiden", @band.name assert_nil @band.founders end it "always returns the represented" do assert_equal @band, @band.from_hash({"name"=>"Nofx"}) end it "includes false attributes" do @band.from_hash({"groupies"=>false}) assert_equal false, @band.groupies end it "ignores properties not present in the incoming document" do @band.instance_eval do def name=(*); raise "I should never be called!"; end end @band.from_hash({}) end # FIXME: do we need this test with XML _and_ JSON? it "ignores (no-default) properties not present in the incoming document" do { Representable::Hash => [:from_hash, {}], Representable::XML => [:from_xml, xml(%{}).to_s] }.each do |format, config| nested_repr = Module.new do # this module is never applied. # FIXME: can we make that a simpler test? include format property :created_at end repr = Module.new do include format property :name, :class => Object, :extend => nested_repr end @band = Band.new.extend(repr) @band.send(config.first, config.last) assert_nil @band.name, "Failed in #{format}" end end describe "passing options" do module TrackRepresenter include Representable::Hash end representer! do property :track, class: OpenStruct do property :nr property :length, class: OpenStruct do def to_hash(options) {seconds: options[:user_options][:nr]} end def from_hash(hash, options) super.tap do self.seconds = options[:user_options][:nr] end end end def to_hash(options) super.merge({"nr" => options[:user_options][:nr]}) end def from_hash(data, options) super.tap do self.nr = options[:user_options][:nr] end end end end it "#to_hash propagates to nested objects" do OpenStruct.new(track: OpenStruct.new(nr: 1, length: OpenStruct.new(seconds: nil))).extend(representer).extend(Representable::Debug). to_hash(user_options: {nr: 9}).must_equal({"track"=>{"nr"=>9, "length"=>{seconds: 9}}}) end it "#from_hash propagates to nested objects" do song = OpenStruct.new.extend(representer).from_hash({"track"=>{"nr" => "replace me", "length"=>{"seconds"=>"replacing"}}}, user_options: {nr: 9}) song.track.nr.must_equal 9 song.track.length.seconds.must_equal 9 end end end describe "#create_representation_with" do before do @band = PopBand.new @band.name = "No One's Choice" @band.groupies = 2 end it "compiles document from properties in object" do assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.to_hash) end it "ignores non-readable properties" do @band = Class.new(Band) { property :name; collection :founder_ids, :readable => false; attr_accessor :founder_ids }.new @band.name = "Iron Maiden" @band.founder_ids = [1,2,3] hash = @band.to_hash assert_equal({"name" => "Iron Maiden"}, hash) end it "does not write nil attributes" do @band.groupies = nil assert_equal({"name"=>"No One's Choice"}, @band.to_hash) end it "writes false attributes" do @band.groupies = false assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.to_hash) end end describe ":extend and :class" do module UpcaseRepresenter include Representable def to_hash(*); upcase; end def from_hash(hsh, *args); replace hsh.upcase; end # DISCUSS: from_hash must return self. end module DowncaseRepresenter include Representable def to_hash(*); downcase; end def from_hash(hsh, *args); replace hsh.downcase; end end class UpcaseString < String; end describe "lambda blocks" do representer! do property :name, :extend => lambda { |name, *| compute_representer(name) } end it "executes lambda in represented instance context" do Song.new("Carnage").instance_eval do def compute_representer(name) UpcaseRepresenter end self end.extend(representer).to_hash.must_equal({"name" => "CARNAGE"}) end end describe ":instance" do obj = String.new("Fate") mod = Module.new { include Representable; def from_hash(*); self; end } representer! do property :name, :extend => mod, :instance => lambda { |*| obj } end it "uses object from :instance but still extends it" do song = Song.new.extend(representer).from_hash("name" => "Eric's Had A Bad Day") song.name.must_equal obj song.name.must_be_kind_of mod end end describe "property with :extend" do representer! do property :name, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String end it "uses lambda when rendering" do assert_equal({"name" => "you make me thick"}, Song.new("You Make Me Thick").extend(representer).to_hash ) assert_equal({"name" => "STEPSTRANGER"}, Song.new(UpcaseString.new "Stepstranger").extend(representer).to_hash ) end it "uses lambda when parsing" do Song.new.extend(representer).from_hash({"name" => "You Make Me Thick"}).name.must_equal "you make me thick" Song.new.extend(representer).from_hash({"name" => "Stepstranger"}).name.must_equal "stepstranger" # DISCUSS: we compare "".is_a?(UpcaseString) end describe "with :class lambda" do representer! do property :name, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => lambda { |options| options[:fragment] == "Still Failing?" ? String : UpcaseString } end it "creates instance from :class lambda when parsing" do song = OpenStruct.new.extend(representer).from_hash({"name" => "Quitters Never Win"}) song.name.must_be_kind_of UpcaseString song.name.must_equal "QUITTERS NEVER WIN" song = OpenStruct.new.extend(representer).from_hash({"name" => "Still Failing?"}) song.name.must_be_kind_of String song.name.must_equal "still failing?" end end end describe "collection with :extend" do representer! do collection :songs, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String end it "uses lambda for each item when rendering" do Album.new([UpcaseString.new("Dean Martin"), "Charlie Still Smirks"]).extend(representer).to_hash.must_equal("songs"=>["DEAN MARTIN", "charlie still smirks"]) end it "uses lambda for each item when parsing" do album = Album.new.extend(representer).from_hash("songs"=>["DEAN MARTIN", "charlie still smirks"]) album.songs.must_equal ["dean martin", "charlie still smirks"] # DISCUSS: we compare "".is_a?(UpcaseString) end describe "with :class lambda" do representer! do collection :songs, :extend => lambda { |options| options[:input].is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => lambda { |options| options[:input] == "Still Failing?" ? String : UpcaseString } end it "creates instance from :class lambda for each item when parsing" do album = Album.new.extend(representer).from_hash("songs"=>["Still Failing?", "charlie still smirks"]) album.songs.must_equal ["still failing?", "CHARLIE STILL SMIRKS"] end end end describe ":decorator" do let(:extend_rpr) { Module.new { include Representable::Hash; collection :songs, :extend => SongRepresenter } } let(:decorator_rpr) { Module.new { include Representable::Hash; collection :songs, :decorator => SongRepresenter } } let(:songs) { [Song.new("Bloody Mary")] } it "is aliased to :extend" do Album.new(songs).extend(extend_rpr).to_hash.must_equal Album.new(songs).extend(decorator_rpr).to_hash end end # TODO: Move to global place since it's used twice. class SongRepresentation < Representable::Decorator include Representable::JSON property :name end class AlbumRepresentation < Representable::Decorator include Representable::JSON collection :songs, :class => Song, :extend => SongRepresentation end describe "::prepare" do let(:song) { Song.new("Still Friends In The End") } let(:album) { Album.new([song]) } describe "module including Representable" do it "uses :extend strategy" do album_rpr = Module.new { include Representable::Hash; collection :songs, :class => Song, :extend => SongRepresenter} album_rpr.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]}) album.must_respond_to :to_hash end end describe "Decorator subclass" do it "uses :decorate strategy" do AlbumRepresentation.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]}) album.wont_respond_to :to_hash end end end end end representable-3.0.4/test/private_options_test.rb0000644000175000017500000000066713137322612021176 0ustar pravipravirequire "test_helper" class PrivateOptionsTest < MiniTest::Spec # TODO: move me to separate file. representer!(decorator: true) do end options = {exclude: "name"} it "render: doesn't modify options" do representer.new(nil).to_hash(options) options.must_equal({exclude: "name"}) end it "parse: doesn't modify options" do representer.new(nil).from_hash(options) options.must_equal({exclude: "name"}) end endrepresentable-3.0.4/test/inherit_test.rb0000644000175000017500000001227613137322612017412 0ustar pravipravirequire 'test_helper' class InheritTest < MiniTest::Spec module SongRepresenter # it's important to have a global module so we can test if stuff gets overridden in the original module. include Representable::Hash property :name, :as => :title do property :string, :as => :str end property :track, :as => :no end let(:song) { Song.new(Struct.new(:string).new("Roxanne"), 1) } describe ":inherit plain property" do representer! do include SongRepresenter property :track, :inherit => true, :getter => lambda { |*| "n/a" } end it { SongRepresenter.prepare(song).to_hash.must_equal({"title"=>{"str"=>"Roxanne"}, "no"=>1}) } it { representer.prepare(song).to_hash.must_equal({"title"=>{"str"=>"Roxanne"}, "no"=>"n/a"}) } # as: inherited. end describe ":inherit with empty inline representer" do representer! do include SongRepresenter property :name, :inherit => true do # inherit as: title # that doesn't make sense. end end it { SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } # the block doesn't override the inline representer. it { representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } end describe ":inherit with overriding inline representer" do representer! do include SongRepresenter # passing block property :name, :inherit => true do # inherit as: title property :string, :as => :s property :length end end it { representer.prepare( Song.new(Struct.new(:string, :length).new("Believe It", 10), 1)).to_hash.must_equal({"title"=>{"s"=>"Believe It","length"=>10}, "no"=>1}) } end describe ":inherit with empty inline and options" do representer! do include SongRepresenter property :name, inherit: true, as: :name do # inherit module, only. # that doesn't make sense. but it should simply inherit the old nested properties. end end it { SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } it { representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"name"=>{"str"=>"Believe It"}, "no"=>1}) } end describe ":inherit with inline without block but options" do representer! do include SongRepresenter property :name, :inherit => true, :as => :name # FIXME: add :getter or something else dynamic since this is double-wrapped. end it { SongRepresenter.prepare(Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"title"=>{"str"=>"Believe It"}, "no"=>1}) } it { representer.prepare( Song.new(Struct.new(:string).new("Believe It"), 1)).to_hash.must_equal({"name"=>{"str"=>"Believe It"}, "no"=>1}) } end # no :inherit describe "overwriting without :inherit" do representer! do include SongRepresenter property :track, :representable => true end it "replaces inherited property" do representer.representable_attrs.size.must_equal 2 definition = representer.representable_attrs.get(:track) # TODO: find a better way to assert Definition identity. # definition.keys.size.must_equal 2 definition[:representable]. must_equal true definition.name.must_equal "track" # was "no". end end # decorator describe ":inherit with decorator" do representer!(:decorator => true) do property :hit do property :title, exec_context: :decorator def title "Cheap Transistor Radio" end end end let(:inheriting) { class InheritingDecorator < representer include Representable::Debug property :hit, :inherit => true do include Representable::Debug property :length end self end } it { representer.new(OpenStruct.new(hit: OpenStruct.new(title: "I WILL BE OVERRIDDEN", :length => "2:59"))).to_hash.must_equal( {"hit"=>{"title"=>"Cheap Transistor Radio"}}) } # inheriting decorator inherits inline representer class (InlineRepresenter#title). # inheriting decorator adds :length. it { inheriting.new(OpenStruct.new(:hit => OpenStruct.new(:title => "Hole In Your Soul", :length => "2:59"))).to_hash.must_equal( {"hit"=>{"title"=>"Cheap Transistor Radio", "length"=>"2:59"}}) } end # :inherit when property doesn't exist, yet. describe ":inherit without inheritable property" do representer! do property :name, :inherit => true end it { representer.prepare(Song.new("The Beginning")).to_hash.must_equal({"name"=>"The Beginning"})} end end # class InheritancingTest < MiniTest::Spec # class SongDecorator < Representable::Decorator # include Representable::Hash # property :album do # # does have Hash. # property :title # end # end # class JsonSongDecorator < SongDecorator # include Representable::XML # end # it do # puts JsonSongDecorator.new(OpenStruct.new(:album => OpenStruct.new(:title => "Erotic Cakes", :tracks => nil))).to_xml # end # end representable-3.0.4/test/render_nil_test.rb0000644000175000017500000000075713137322612020072 0ustar pravipravirequire "test_helper" class RenderNilTest < MiniTest::Spec Song = Struct.new(:title) describe "render_nil: true" do representer! do property :title, render_nil: true end it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) } end describe "with :extend it shouldn't extend nil" do representer! do property :title, render_nil: true, extend: Class end it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) } end end representable-3.0.4/test/decorator_scope_test.rb0000644000175000017500000000141113137322612021110 0ustar pravipravirequire 'test_helper' # TODO: remove in 2.0. class DecoratorScopeTest < MiniTest::Spec representer! do property :title, :getter => lambda { |*| title_from_representer }, :decorator_scope => true end let(:representer_with_method) { Module.new do include Representable::Hash property :title, :decorator_scope => true def title; "Crystal Planet"; end end } it "executes lambdas in represented context" do Class.new do def title_from_representer "Sounds Of Silence" end end.new.extend(representer).to_hash.must_equal({"title"=>"Sounds Of Silence"}) end it "executes method in represented context" do Object.new.extend(representer_with_method).to_hash.must_equal({"title"=>"Crystal Planet"}) end endrepresentable-3.0.4/test/prepare_test.rb0000644000175000017500000000333313137322612017400 0ustar pravipravirequire 'test_helper' class PrepareTest < BaseTest class PreparerClass def initialize(object) @object = object end def ==(b) return unless b.instance_of?(PreparerClass) object == b.object end attr_reader :object end describe "#to_hash" do # TODO: introduce :representable option? representer! do property :song, :prepare => lambda { |options| options[:binding][:arbitrary].new(options[:input]) }, :arbitrary => PreparerClass, :extend => true, :representable => false # don't call #to_hash. end let(:hit) { Struct.new(:song).new(song).extend(representer) } it "calls prepare:, nothing else" do # render(hit).must_equal_document(output) hit.to_hash.must_equal({"song" => PreparerClass.new(song)}) end # it "calls #from_hash on the existing song instance, nothing else" do # song_id = hit.song.object_id # parse(hit, input) # hit.song.title.must_equal "Suffer" # hit.song.object_id.must_equal song_id # end end describe "#from_hash" do representer! do property :song, :prepare => lambda { |options| options[:binding][:arbitrary].new(options[:input]) }, :arbitrary => PreparerClass, #:extend => true, # TODO: typed: true would be better. :instance => String.new, # pass_fragment :pass_options => true, :representable => false # don't call #to_hash. end let(:hit) { Struct.new(:song).new.extend(representer) } it "calls prepare:, nothing else" do # render(hit).must_equal_document(output) hit.from_hash("song" => {}) hit.song.must_equal(PreparerClass.new(String.new)) end end endrepresentable-3.0.4/test/hash_bindings_test.rb0000644000175000017500000000527413137322612020550 0ustar pravipravirequire 'test_helper' class HashBindingTest < MiniTest::Spec module SongRepresenter include Representable::JSON property :name end class SongWithRepresenter < ::Song include Representable include SongRepresenter end describe "PropertyBinding" do describe "#read" do before do @property = Representable::Hash::Binding.new(Representable::Definition.new(:song)) end it "returns fragment if present" do assert_equal "Stick The Flag Up Your Goddamn Ass, You Sonofabitch", @property.read({"song" => "Stick The Flag Up Your Goddamn Ass, You Sonofabitch"}, "song") assert_equal "", @property.read({"song" => ""}, "song") assert_nil @property.read({"song" => nil}, "song") end it "returns FRAGMENT_NOT_FOUND if not in document" do assert_equal Representable::Binding::FragmentNotFound, @property.read({}, "song") end end end describe "CollectionBinding" do describe "with plain text items" do before do @property = Representable::Hash::Binding::Collection.new(Representable::Definition.new(:songs, :collection => true)) end it "extracts with #read" do assert_equal ["The Gargoyle", "Bronx"], @property.read({"songs" => ["The Gargoyle", "Bronx"]}, "songs") end it "inserts with #write" do doc = {} assert_equal(["The Gargoyle", "Bronx"], @property.write(doc, ["The Gargoyle", "Bronx"], "songs")) assert_equal({"songs"=>["The Gargoyle", "Bronx"]}, doc) end end end describe "HashBinding" do describe "with plain text items" do before do @property = Representable::Hash::Binding.new(Representable::Definition.new(:songs, :hash => true)) end it "extracts with #read" do assert_equal({"first" => "The Gargoyle", "second" => "Bronx"} , @property.read({"songs" => {"first" => "The Gargoyle", "second" => "Bronx"}}, "songs")) end it "inserts with #write" do doc = {} assert_equal({"first" => "The Gargoyle", "second" => "Bronx"}, @property.write(doc, {"first" => "The Gargoyle", "second" => "Bronx"}, "songs")) assert_equal({"songs"=>{"first" => "The Gargoyle", "second" => "Bronx"}}, doc) end end describe "with objects" do before do @property = Representable::Hash::Binding.new(Representable::Definition.new(:songs, :hash => true, :class => Song, :extend => SongRepresenter)) end it "doesn't change the represented hash in #write" do song = Song.new("Better Than That") hash = {"first" => song} @property.write({}, hash, "song") assert_equal({"first" => song}, hash) end end end end representable-3.0.4/test/coercion_test.rb0000644000175000017500000000333013137322612017540 0ustar pravipravirequire 'test_helper' require 'representable/coercion' class VirtusCoercionTest < MiniTest::Spec representer! do include Representable::Coercion property :title # no coercion. property :length, :type => Float property :band, :class => OpenStruct do property :founded, :type => Integer end collection :songs, :class => OpenStruct do property :ok, :type => Virtus::Attribute::Boolean end end let(:album) { OpenStruct.new(:title => "Dire Straits", :length => 41.34, :band => OpenStruct.new(:founded => "1977"), :songs => [OpenStruct.new(:ok => 1), OpenStruct.new(:ok => 0)]) } it { album.extend(representer).to_hash.must_equal({"title"=>"Dire Straits", "length"=>41.34, "band"=>{"founded"=>1977}, "songs"=>[{"ok"=>true}, {"ok"=>false}]}) } it { album = OpenStruct.new album.extend(representer) album.from_hash({"title"=>"Dire Straits", "length"=>"41.34", "band"=>{"founded"=>"1977"}, "songs"=>[{"ok"=>1}, {"ok"=>0}]}) # it album.length.must_equal 41.34 album.band.founded.must_equal 1977 album.songs[0].ok.must_equal true } describe "with user :parse_filter and :render_filter" do representer! do include Representable::Coercion property :length, :type => Float, :parse_filter => lambda { |input, options| "#{input}.1" }, # happens BEFORE coercer. :render_filter => lambda { |fragment,*| "#{fragment}.1" } end # user's :parse_filter(s) are run before coercion. it { OpenStruct.new.extend(representer).from_hash("length"=>"1").length.must_equal 1.1 } # user's :render_filter(s) are run before coercion. it { OpenStruct.new(:length=>1).extend(representer).to_hash.must_equal({"length" => 1.1}) } end endrepresentable-3.0.4/test/inline_test.rb0000644000175000017500000002076013137322612017223 0ustar pravipravirequire 'test_helper' class InlineTest < MiniTest::Spec let(:song) { Song.new("Alive") } let(:request) { representer.prepare(OpenStruct.new(:song => song)) } { :hash => [Representable::Hash, {"song"=>{"name"=>"Alive"}}, {"song"=>{"name"=>"You've Taken Everything"}}], :json => [Representable::JSON, "{\"song\":{\"name\":\"Alive\"}}", "{\"song\":{\"name\":\"You've Taken Everything\"}}"], :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], }.each do |format, cfg| mod, output, input = cfg describe "[#{format}] with :class" do representer!(:module => mod) do property :song, :class => Song do property :name end end let(:format) { format } it { render(request).must_equal_document output } it { parse(request, input).song.name.must_equal "You've Taken Everything"} end end { :hash => [Representable::Hash, {"songs"=>[{"name"=>"Alive"}]}, {"songs"=>[{"name"=>"You've Taken Everything"}]}], :json => [Representable::JSON, "{\"songs\":[{\"name\":\"Alive\"}]}", "{\"songs\":[{\"name\":\"You've Taken Everything\"}]}"], :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything", { :as => :song }], :yaml => [Representable::YAML, "---\nsongs:\n- name: Alive\n", "---\nsongs:\n- name: You've Taken Everything\n"], }.each do |format, cfg| mod, output, input, collection_options = cfg collection_options ||= {} describe "[#{format}] collection with :class" do let(:request) { representer.prepare(OpenStruct.new(:songs => [song])) } representer!(:module => mod) do collection :songs, collection_options.merge(:class => Song) do property :name end end let(:format) { format } # FIXME: why do we have to define this? it { render(request).must_equal_document output } it { parse(request, input).songs.first.name.must_equal "You've Taken Everything"} end end describe "without :class" do representer! do property :song do property :name end end it { request.to_hash.must_equal({"song"=>{"name"=>"Alive"}}) } end for_formats( :hash => [Representable::Hash, {}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input| describe "parsing [#{format}] where nested property missing" do representer!(:module => mod) do property :song do property :name end end it "doesn't change represented object" do request.send("from_#{format}", input).song.must_equal song end end end describe "inheriting from outer representer" do let(:request) { Struct.new(:song, :requester).new(song, "Josephine") } [false, true].each do |is_decorator| # test for module and decorator. representer!(:decorator => is_decorator) do property :requester property :song, :class => Song do property :name end end let(:decorator) { representer.prepare(request) } it { decorator.to_hash.must_equal({"requester"=>"Josephine", "song"=>{"name"=>"Alive"}}) } it { decorator.from_hash({"song"=>{"name"=>"You've Taken Everything"}}).song.name.must_equal "You've Taken Everything"} end end describe "object pollution" do representer!(:decorator => true) do property :song do property :name end end it "uses an inline decorator and doesn't alter represented" do representer.prepare(Struct.new(:song).new(song)).to_hash song.wont_be_kind_of Representable end end # TODO: should be in extend:/decorator: test. # FIXME: this tests :getter{represented}+:extend - represented gets extended twice and the inline decorator overrides original config. # for_formats( # :hash => [Representable::Hash, {"album" => {"artist" => {"label"=>"Epitaph"}}}], # # :xml => [Representable::XML, ""], # #:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"] # ) do |format, mod, output, input| # module ArtistRepresenter # include Representable::JSON # property :label # end # describe ":getter with inline representer" do # let(:format) { format } # representer!(:module => mod) do # self.representation_wrap = :album # property :artist, :getter => lambda { |args| represented }, :extend => ArtistRepresenter # end # let(:album) { OpenStruct.new(:label => "Epitaph").extend(representer) } # it "renders nested Album-properties in separate section" do # render(album).must_equal_document output # end # end # end for_formats({ :hash => [Representable::Hash, {"album" => {"artist" => {"label"=>"Epitaph"}}}], # :xml => [Representable::XML, ""], #:yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n"] }) do |format, mod, output, input| class ArtistDecorator < Representable::Decorator include Representable::JSON property :label end describe ":getter with :decorator" do let(:format) { format } representer!(:module => mod) do self.representation_wrap = "album" property :artist, :getter => lambda { |args| represented }, :decorator => ArtistDecorator end let(:album) { OpenStruct.new(:label => "Epitaph").extend(representer) } it "renders nested Album-properties in separate section" do render(album).must_equal_document output end end end # test helper methods within inline representer for_formats({ :hash => [Representable::Hash, {"song"=>{"name"=>"ALIVE"}}], :xml => [Representable::XML, "\n \n ALIVE\n \n"], :yaml => [Representable::YAML, "---\nsong:\n name: ALIVE\n"], }) do |format, mod, output| describe "helper method within inline representer [#{format}]" do let(:format) { format } representer!(:module => mod, :decorator => true) do self.representation_wrap = :request if format == :xml property :requester property :song do property :name, :exec_context => :decorator define_method :name do represented.name.upcase end self.representation_wrap = :song if format == :xml end end let(:request) { representer.prepare(OpenStruct.new(:song => Song.new("Alive"))) } it do render(request).must_equal_document output end end end describe "include module in inline representers" do representer! do extension = Module.new do include Representable::Hash property :title end property :song do include extension property :artist end end it do OpenStruct.new(:song => OpenStruct.new(:title => "The Fever And The Sound", :artist => "Strung Out")).extend(representer). to_hash. must_equal({"song"=>{"artist"=>"Strung Out", "title"=>"The Fever And The Sound"}}) end end # define method in inline representer describe "define method in inline representer" do Mod = Module.new do include Representable::Hash def song "Object.new" end property :song do property :duration def duration "6:53" end end end it { Object.new.extend(Mod).to_hash.must_equal("song"=>{"duration"=>"6:53"}) } end # define method inline with Decorator describe "define method inline with Decorator" do dec = Class.new(Representable::Decorator) do include Representable::Hash def song "Object.new" end property :song, :exec_context => :decorator do property :duration, :exec_context => :decorator def duration "6:53" end end end it { dec.new(Object.new).to_hash.must_equal("song"=>{"duration"=>"6:53"}) } end end representable-3.0.4/test/binding_test.rb0000644000175000017500000000337513137322612017362 0ustar pravipravirequire 'test_helper' class BindingTest < MiniTest::Spec Binding = Representable::Binding let(:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) } describe "#skipable_empty_value?" do let(:binding) { Binding.new(render_nil_definition) } # don't skip when present. it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false } # don't skip when it's nil and render_nil: true it { binding.skipable_empty_value?(nil).must_equal false } # skip when nil and :render_nil undefined. it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?(nil).must_equal true } # don't skip when nil and :render_nil undefined. it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?("Fatal Flu").must_equal false } end describe "#default_for" do let(:definition) { Representable::Definition.new(:song, :default => "Insider") } let(:binding) { Binding.new(definition) } # return value when value present. it { binding.default_for("Black And Blue").must_equal "Black And Blue" } # return false when value false. it { binding.default_for(false).must_equal false } # return default when value nil. it { binding.default_for(nil).must_equal "Insider" } # return nil when value nil and render_nil: true. it { Binding.new(render_nil_definition).default_for(nil).must_be_nil } # return nil when value nil and render_nil: true, even when :default is set" do it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")).default_for(nil).must_be_nil } # return nil if no :default it { Binding.new(Representable::Definition.new(:song)).default_for(nil).must_be_nil } end endrepresentable-3.0.4/test/getter_setter_test.rb0000644000175000017500000000147613137322612020630 0ustar pravipravirequire 'test_helper' class GetterSetterTest < BaseTest representer! do property :name, # key under :name. :getter => lambda { |options| "#{options[:user_options][:welcome]} #{song_name}" }, :setter => lambda { |options| self.song_name = "#{options[:user_options][:welcome]} #{options[:input]}" } end subject { Struct.new(:song_name).new("Mony Mony").extend(representer) } it "uses :getter when rendering" do subject.instance_eval { def name; raise; end } subject.to_hash(user_options: {welcome: "Hi"}).must_equal({"name" => "Hi Mony Mony"}) end it "uses :setter when parsing" do subject.instance_eval { def name=(*); raise; end; self } subject.from_hash({"name" => "Eyes Without A Face"}, user_options: {welcome: "Hello"}).song_name.must_equal "Hello Eyes Without A Face" end endrepresentable-3.0.4/test/nested_test.rb0000644000175000017500000000710113137322612017221 0ustar pravipravirequire 'test_helper' class NestedTest < MiniTest::Spec Album = Struct.new(:label, :owner, :amount) for_formats( :hash => [Representable::Hash, {"label" => {"label"=>"Epitaph", "owner"=>"Brett Gurewitz", "releases"=>{"amount"=>19}}}], # :xml => [Representable::XML, ""], :yaml => [Representable::YAML, "---\nlabel:\n label: Epitaph\n owner: Brett Gurewitz\n releases:\n amount: 19\n"] ) do |format, mod, output, input| [false, true].each do |is_decorator| describe "::nested with (inline representer|decorator): #{is_decorator}" do let(:format) { format } representer!(:module => mod, :decorator => is_decorator) do nested :label do property :label property :owner # self.representation_wrap = nil if format == :xml nested :releases do property :amount end end # self.representation_wrap = :album if format == :xml end let(:album) { Album.new("Epitaph", "Brett Gurewitz", 19) } let(:decorator) { representer.prepare(album) } it "renders nested Album-properties in separate section" do render(decorator).must_equal_document output # do not use extend on the nested object. # FIXME: make this a proper test with two describes instead of this pseudo-meta stuff. if is_decorator==true album.wont_be_kind_of(Representable::Hash) end end it "parses nested properties to Album instance" do album = parse(representer.prepare(Album.new), output) album.label.must_equal "Epitaph" album.owner.must_equal "Brett Gurewitz" end end end describe "Decorator ::nested with extend:" do let(:format) { format } representer!(:name => :label_rpr) do include mod property :label property :owner nested :releases do # DISCUSS: do we need to test this? property :amount end end representer!(:module => mod, :decorator => true, :inject => :label_rpr) do nested :label, :extend => label_rpr self.representation_wrap = :album if format == :xml end let(:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) } # TODO: shared example with above. it "renders nested Album-properties in separate section" do render(album).must_equal_document output end it "parses nested properties to Album instance" do album = parse(representer.prepare(Album.new), output) album.label.must_equal "Epitaph" album.owner.must_equal "Brett Gurewitz" album.amount.must_equal 19 end end end describe "::nested without block but with inherit:" do representer!(:name => :parent) do include Representable::Hash nested :label do property :owner end end representer!(:module => Representable::Hash, :inject => :parent) do include parent nested :label, :inherit => true, :as => "Label" end let(:album) { representer.prepare(Album.new("Epitaph", "Brett Gurewitz", 19)) } it "renders nested Album-properties in separate section" do representer.prepare(album).to_hash.must_equal({"Label"=>{"owner"=>"Brett Gurewitz"}}) end # it "parses nested properties to Album instance" do # album = parse(representer.prepare(Album.new), output) # album.label.must_equal "Epitaph" # album.owner.must_equal "Brett Gurewitz" # album.amount.must_equal 19 # end end endrepresentable-3.0.4/test/test_helper_test.rb0000644000175000017500000000155013137322612020257 0ustar pravipravirequire 'test_helper' class TestHelperTest < MiniTest::Spec describe "#assert_json" do it "tests for equality" do assert_json "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}", "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}" end it "allows different key orders" do assert_json "{\"songs\":{\"one\":\"65\",\"two\":\"Emo Boy\"}}", "{\"songs\":{\"two\":\"Emo Boy\",\"one\":\"65\"}}" end it "complains when expected hash is subset" do assert_raises MiniTest::Assertion do assert_json "{\"songs\":{\"one\":\"65\"}}", "{\"songs\":{\"two\":\"Emo Boy\",\"one\":\"65\"}}" end end it "complains when source hash is subset" do assert_raises MiniTest::Assertion do assert_json "{\"songs\":{\"two\":\"Emo Boy\",\"one\":\"65\"}}", "{\"songs\":{\"one\":\"65\"}}" end end end end representable-3.0.4/test/for_collection_test.rb0000644000175000017500000000427313137322612020747 0ustar pravipravirequire 'test_helper' class ForCollectionTest < MiniTest::Spec module SongRepresenter include Representable::JSON property :name end let(:songs) { [Song.new("Days Go By"), Song.new("Can't Take Them All")] } let(:json) { "[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]" } # Module.for_collection # Decorator.for_collection for_formats( :hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out], :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out], # :xml => [Representable::XML, out="", out] ) do |format, mod, output, input| describe "Module::for_collection [#{format}]" do let(:format) { format } let(:representer) { Module.new do include mod property :name#, :as => :title collection_representer :class => Song # self.representation_wrap = :songs if format == :xml end } it { render(songs.extend(representer.for_collection)).must_equal_document output } it { render(representer.for_collection.prepare(songs)).must_equal_document output } # parsing needs the class set, at least it { parse([].extend(representer.for_collection), input).must_equal songs } end describe "Module::for_collection without configuration [#{format}]" do let(:format) { format } let(:representer) { Module.new do include mod property :name end } # rendering works out of the box, no config necessary it { render(songs.extend(representer.for_collection)).must_equal_document output } end describe "Decorator::for_collection [#{format}]" do let(:format) { format } let(:representer) { Class.new(Representable::Decorator) do include mod property :name collection_representer :class => Song end } it { render(representer.for_collection.new(songs)).must_equal_document output } it { parse(representer.for_collection.new([]), input).must_equal songs } end end # with module including module endrepresentable-3.0.4/test/test_helper.rb0000644000175000017500000000662413137322612017227 0ustar pravipravirequire 'representable' require 'minitest/autorun' require 'test_xml/mini_test' require "representable/debug" module MiniTest::Assertions def assert_equal_xml(text, subject) subject.gsub("\n", "").gsub(/(\s\s+)/, "").must_equal(text.gsub("\n", "").gsub(/(\s\s+)/, "")) end end String.infect_an_assertion :assert_equal_xml, :must_xml # TODO: delete all that in 4.0: class Album attr_accessor :songs, :best_song def initialize(songs=nil, best_song=nil) @songs = songs @best_song = best_song end def ==(other) songs == other.songs and best_song == other.best_song end end class Song attr_accessor :name, :track # never change this, track rendered with Rails#to_json. def initialize(name=nil, track=nil) @name = name @track = track end def ==(other) name == other.name and track == other.track end end module XmlHelper def xml(document) Nokogiri::XML(document).root end end module AssertJson module Assertions def assert_json(expected, actual, msg=nil) msg = message(msg, "") { diff expected, actual } assert(expected.split("").sort == actual.split("").sort, msg) end end end MiniTest::Spec.class_eval do include AssertJson::Assertions include XmlHelper def self.for_formats(formats) formats.each do |format, cfg| mod, output, input = cfg yield format, mod, output, input end end def render(object, *args) AssertableDocument.new(object.send("to_#{format}", *args), format) end def parse(object, input, *args) object.send("from_#{format}", input, *args) end class AssertableDocument attr_reader :document def initialize(document, format) @document, @format = document, format end def must_equal_document(*args) return document.must_equal_xml(*args) if @format == :xml document.must_equal(*args) end end def self.representer!(options={}, &block) fmt = options # we need that so the 2nd call to ::let(within a ::describe) remembers the right format. name = options[:name] || :representer format = options[:module] || Representable::Hash let(name) do mod = options[:decorator] ? Class.new(Representable::Decorator) : Module.new inject_representer(mod, fmt) mod.module_eval do include format instance_exec(&block) end mod end undef :inject_representer if method_defined? :inject_representer def inject_representer(mod, options) return unless options[:inject] injected_name = options[:inject] injected = send(injected_name) # song_representer mod.singleton_class.instance_eval do define_method(injected_name) { injected } end end end module TestMethods def representer_for(modules=[Representable::Hash], &block) Module.new do extend TestMethods include(*modules) module_exec(&block) end end alias_method :representer!, :representer_for end include TestMethods end class BaseTest < MiniTest::Spec let(:new_album) { OpenStruct.new.extend(representer) } let(:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) } let(:song) { OpenStruct.new(:title => "Resist Stance") } let(:song_representer) { Module.new do include Representable::Hash; property :title end } end Band = Struct.new(:id, :name) do def [](*attrs) attrs.collect { |attr| send(attr) } end end representable-3.0.4/test/generic_test.rb0000644000175000017500000000725613137322612017366 0ustar pravipravirequire 'test_helper' class GenericTest < MiniTest::Spec # TODO: rename/restructure to CollectionTest. let(:new_album) { OpenStruct.new.extend(representer) } let(:album) { OpenStruct.new(:songs => ["Fuck Armageddon"]).extend(representer) } let(:song) { OpenStruct.new(:title => "Resist Stance") } let(:song_representer) { Module.new do include Representable::Hash; property :title end } describe "::collection" do representer! do collection :songs end it "doesn't initialize property" do new_album.from_hash({}) new_album.songs.must_be_nil end it "leaves properties untouched" do album.from_hash({}) # TODO: test property. album.songs.must_equal ["Fuck Armageddon"] # when the collection is not present in the incoming hash, this propery stays untouched. end # when collection is nil, it doesn't get rendered: for_formats( :hash => [Representable::Hash, {}], :xml => [Representable::XML, ""], :yaml => [Representable::YAML, "--- {}\n"], # FIXME: this doesn't look right. ) do |format, mod, output, input| describe "nil collections" do let(:format) { format } representer!(:module => mod) do collection :songs self.representation_wrap = :album if format == :xml end let(:album) { Album.new.extend(representer) } it "doesn't render collection in #{format}" do render(album).must_equal_document output end end end # when collection is set but empty, render the empty collection. for_formats( :hash => [Representable::Hash, {"songs" => []}], #:xml => [Representable::XML, ""], :yaml => [Representable::YAML, "---\nsongs: []\n"], ) do |format, mod, output, input| describe "empty collections" do let(:format) { format } representer!(:module => mod) do collection :songs self.representation_wrap = :album if format == :xml end let(:album) { OpenStruct.new(:songs => []).extend(representer) } it "renders empty collection in #{format}" do render(album).must_equal_document output end end end # when collection is [], suppress rendering when render_empty: false. for_formats( :hash => [Representable::Hash, {}], #:xml => [Representable::XML, ""], :yaml => [Representable::YAML, "--- {}\n"], ) do |format, mod, output, input| describe "render_empty [#{format}]" do let(:format) { format } representer!(:module => mod) do collection :songs, :render_empty => false self.representation_wrap = :album if format == :xml end let(:album) { OpenStruct.new(:songs => []).extend(representer) } it { render(album).must_equal_document output } end end end # wrap_test for_formats( :hash => [Representable::Hash, {}], # :xml => [Representable::XML, "\n \n Alive\n \n", "You've Taken Everything/open_struct>"], # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"], ) do |format, mod, input| describe "parsing [#{format}] with wrap where wrap is missing" do representer!(:module => mod) do self.representation_wrap = :song property :title end it "doesn't change represented object" do song.extend(representer).send("from_#{format}", input).title.must_equal "Resist Stance" end end end endrepresentable-3.0.4/test/heritage_test.rb0000644000175000017500000000225413137322612017533 0ustar pravipravirequire "test_helper" class HeritageTest < Minitest::Spec module Hello def hello "Hello!" end end module Ciao def ciao "Ciao!" end end class A < Representable::Decorator include Representable::Hash feature Hello property :id do end end class B < A feature Ciao # does NOT extend id, of course. property :id, inherit: true do end end class C < A property :id do end # overwrite old :id. end it "B must inherit Hello! feature from A" do B.representable_attrs.get(:id)[:extend].(nil).new(nil).hello.must_equal "Hello!" end it "B must have Ciao from module (feauture) Ciao" do B.representable_attrs.get(:id)[:extend].(nil).new(nil).ciao.must_equal "Ciao!" end it "C must inherit Hello! feature from A" do C.representable_attrs.get(:id)[:extend].(nil).new(nil).hello.must_equal "Hello!" end module M include Representable feature Hello end module N include Representable include M feature Ciao end let(:obj_extending_N) { Object.new.extend(N) } it "obj should inherit from N, and N from M" do obj_extending_N.hello.must_equal "Hello!" end end representable-3.0.4/test/features_test.rb0000644000175000017500000000317713137322612017566 0ustar pravipravirequire 'test_helper' class FeaturesTest < MiniTest::Spec module Title def title; "Is It A Lie"; end end module Length def length; "2:31"; end end definition = lambda { feature Title feature Length # exec_context: :decorator, so the readers are called on the Decorator instance (which gets readers from features!). property :title, exec_context: :decorator property :length, exec_context: :decorator property :details do property :title, exec_context: :decorator end } let(:song) { OpenStruct.new(:details => Object.new) } describe "Module" do representer! do instance_exec(&definition) end it { song.extend(representer).to_hash.must_equal({"title"=>"Is It A Lie", "length"=>"2:31", "details"=>{"title"=>"Is It A Lie"}}) } end describe "Decorator" do representer!(:decorator => true) do instance_exec(&definition) end it { representer.new(song).to_hash.must_equal({"title"=>"Is It A Lie", "length"=>"2:31", "details"=>{"title"=>"Is It A Lie"}}) } end end class FeatureInclusionOrderTest < MiniTest::Spec module Title def title "I was first!" end end module OverridingTitle def title "I am number two, " + super end end representer!(decorator: true) do feature Title feature OverridingTitle property :title, exec_context: :decorator property :song do property :title, exec_context: :decorator end end it do representer.new(OpenStruct.new(song: Object)).to_hash.must_equal({"title"=>"I am number two, I was first!", "song"=>{"title"=>"I am number two, I was first!"}}) end endrepresentable-3.0.4/test/reader_writer_test.rb0000644000175000017500000000123213137322612020574 0ustar pravipravirequire 'test_helper' class ReaderWriterTest < BaseTest representer! do property :name, :writer => lambda { |options| options[:doc]["title"] = "#{options[:user_options][:nr]}) #{options[:input]}" }, :reader => lambda { |options| self.name = options[:doc]["title"].split(") ").last } end subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) } it "uses :writer when rendering" do subject.to_hash(user_options: {nr: 14}).must_equal({"title" => "14) Disorder And Disarray"}) end it "uses :reader when parsing" do subject.from_hash({"title" => "15) The Wars End"}).name.must_equal "The Wars End" end endrepresentable-3.0.4/test/stringify_hash_test.rb0000644000175000017500000000225213137322612020762 0ustar pravipravirequire 'test_helper' class StringifyHashTest < MiniTest::Spec describe "#from_hash" do representer!(:name => :song_representer) do include Representable::Hash include Representable::Hash::AllowSymbols property :title end representer!(:inject => :song_representer) do include Representable::Hash::AllowSymbols property :song, :extend => song_representer, :class => OpenStruct end it "parses symbols, too" do OpenStruct.new.extend(representer).from_hash({:song => {:title => "Der Optimist"}}).song.title.must_equal "Der Optimist" end it "still parses strings" do OpenStruct.new.extend(representer).from_hash({"song" => {"title" => "Der Optimist"}}).song.title.must_equal "Der Optimist" end describe "with :wrap" do representer!(:inject => :song_representer) do include Representable::Hash::AllowSymbols self.representation_wrap = :album property :song, :extend => song_representer, :class => OpenStruct end it "parses symbols, too" do OpenStruct.new.extend(representer).from_hash({:album => {:song => {:title => "Der Optimist"}}}).song.title.must_equal "Der Optimist" end end end endrepresentable-3.0.4/test/benchmarking.rb0000644000175000017500000000401113137322612017325 0ustar pravipravirequire 'test_helper' require 'benchmark' SONG_PROPERTIES = 1000.times.collect do |i| "property_#{i}" end module SongRepresenter include Representable::JSON SONG_PROPERTIES.each { |p| property p } end class SongDecorator < Representable::Decorator include Representable::JSON SONG_PROPERTIES.each { |p| property p } end module AlbumRepresenter include Representable::JSON # collection :songs, extend: SongRepresenter collection :songs, extend: SongDecorator end def random_song attrs = Hash[SONG_PROPERTIES.collect { |n| [n,n] }] OpenStruct.new(attrs) end times = [] 3.times.each do album = OpenStruct.new(songs: 100.times.collect { random_song }) times << Benchmark.measure do puts "================ next!" album.extend(AlbumRepresenter).to_json end end puts times.join("") # 100 songs, 100 attrs # 0.050000 0.000000 0.050000 ( 0.093157) ## 100 songs, 1000 attrs # 0.470000 0.010000 0.480000 ( 0.483708) ### without binding cache: # 2.790000 0.030000 2.820000 ( 2.820190) ### with extend: on Song, with binding cache> # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0 ### without skip? # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3 ### without :writer # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2 ### without :render_filter # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0 ###without default_for and skipable? # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7 ### without :serialize # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7 ### using decorator # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6 ### with prepare AFTER representable? # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3 # representable 2.0 # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0 # no method missing # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5 # no def_delegator in Definition # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1 representable-3.0.4/test/object_test.rb0000644000175000017500000000324613137322612017213 0ustar pravipravirequire "test_helper" require "representable/object" class ObjectTest < MiniTest::Spec Song = Struct.new(:title, :album) Album = Struct.new(:name, :songs) representer!(module: Representable::Object) do property :title property :album, instance: lambda { |options| options[:fragment].name.upcase!; options[:fragment] } do property :name collection :songs, instance: lambda { |options| options[:fragment].title.upcase!; options[:fragment] } do property :title end end # TODO: collection end let(:source) { Song.new("The King Is Dead", Album.new("Ruiner", [Song.new("In Vino Veritas II")])) } let(:target) { Song.new } it do representer.prepare(target).from_object(source) target.title.must_equal "The King Is Dead" target.album.name.must_equal "RUINER" target.album.songs[0].title.must_equal "IN VINO VERITAS II" end # ignore nested object when nil it do representer.prepare(Song.new("The King Is Dead")).from_object(Song.new) target.title.must_be_nil # scalar property gets overridden when nil. target.album.must_be_nil # nested property stays nil. end # to_object describe "#to_object" do representer!(module: Representable::Object) do property :title property :album, render_filter: lambda { |input, options|input.name = "Live";input } do property :name collection :songs, render_filter: lambda { |input, options|input[0].title = 1;input } do property :title end end end it do representer.prepare(source).to_object source.album.name.must_equal "Live" source.album.songs[0].title.must_equal 1 end end endrepresentable-3.0.4/test/decorator_test.rb0000644000175000017500000000622313137322612017725 0ustar pravipravirequire 'test_helper' class DecoratorTest < MiniTest::Spec class SongRepresentation < Representable::Decorator include Representable::JSON property :name end class AlbumRepresentation < Representable::Decorator include Representable::JSON collection :songs, :class => Song, :extend => SongRepresentation end class RatingRepresentation < Representable::Decorator include Representable::JSON property :system property :value end let(:song) { Song.new("Mama, I'm Coming Home") } let(:album) { Album.new([song]) } let(:rating) { OpenStruct.new(system: 'MPAA', value: 'R') } describe "inheritance" do let(:inherited_decorator) do Class.new(AlbumRepresentation) do property :best_song end.new(Album.new([song], "Stand Up")) end it { inherited_decorator.to_hash.must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}], "best_song"=>"Stand Up"}) } end let(:decorator) { AlbumRepresentation.new(album) } let(:rating_decorator) { RatingRepresentation.new(rating) } it "renders" do decorator.to_hash.must_equal({"songs"=>[{"name"=>"Mama, I'm Coming Home"}]}) album.wont_respond_to :to_hash song.wont_respond_to :to_hash # DISCUSS: weak test, how to assert blank slate? # no @representable_attrs in decorated objects song.wont_be(:instance_variable_defined?, :@representable_attrs) rating_decorator.to_hash.must_equal({"system" => "MPAA", "value" => "R"}) end describe "#from_hash" do it "returns represented" do decorator.from_hash({"songs"=>[{"name"=>"Mama, I'm Coming Home"}]}).must_equal album end it "parses" do decorator.from_hash({"songs"=>[{"name"=>"Atomic Garden"}]}) album.songs.first.must_be_kind_of Song album.songs.must_equal [Song.new("Atomic Garden")] album.wont_respond_to :to_hash song.wont_respond_to :to_hash # DISCUSS: weak test, how to assert blank slate? end end describe "#decorated" do it "is aliased to #represented" do AlbumRepresentation.prepare(album).decorated.must_equal album end end describe "inline decorators" do representer!(decorator: true) do collection :songs, :class => Song do property :name end end it "does not pollute represented" do representer.new(album).from_hash({"songs"=>[{"name"=>"Atomic Garden"}]}) # no @representable_attrs in decorated objects song.wont_be(:instance_variable_defined?, :@representable_attrs) album.wont_be(:instance_variable_defined?, :@representable_attrs) end end end require "uber/inheritable_attr" class InheritanceWithDecoratorTest < MiniTest::Spec class Twin extend Uber::InheritableAttr inheritable_attr :representer_class self.representer_class = Class.new(Representable::Decorator){ include Representable::Hash } end class Album < Twin representer_class.property :title # Twin.representer_class.clone end class Song < Twin # Twin.representer_class.clone end it do Twin.representer_class.definitions.size.must_equal 0 Album.representer_class.definitions.size.must_equal 1 Song.representer_class.definitions.size.must_equal 0 end endrepresentable-3.0.4/test/cached_test.rb0000644000175000017500000001265113137322612017154 0ustar pravipravirequire "test_helper" class Profiler def self.profile(&block) case RUBY_ENGINE when "ruby" require 'ruby-prof' output = StringIO.new profile_result = RubyProf.profile(&block) printer = RubyProf::FlatPrinter.new(profile_result) printer.print(output) output.string when "jruby" require 'jruby/profiler' output_stream = java.io.ByteArrayOutputStream.new print_stream = java.io.PrintStream.new(output_stream) profile_result = JRuby::Profiler.profile(&block) printer = JRuby::Profiler::FlatProfilePrinter.new(profile_result) printer.printProfile(print_stream) output_stream.toString end end end class CachedTest < MiniTest::Spec # TODO: also test with feature(Cached) module Model Song = Struct.new(:title, :composer) Album = Struct.new(:name, :songs, :artist) Artist = Struct.new(:name, :hidden_taste) end class SongRepresenter < Representable::Decorator include Representable::Hash feature Representable::Cached property :title, render_filter: lambda { |input, options| "#{input}:#{options[:options][:user_options]}" } property :composer, class: Model::Artist do property :name end end class AlbumRepresenter < Representable::Decorator include Representable::Hash include Representable::Cached property :name collection :songs, decorator: SongRepresenter, class: Model::Song end describe "serialization" do let(:album_hash) { {"name"=>"Louder And Even More Dangerous", "songs"=>[{"title"=>"Southbound:{:volume=>10}"}, {"title"=>"Jailbreak:{:volume=>10}"}]} } let(:song) { Model::Song.new("Jailbreak") } let(:song2) { Model::Song.new("Southbound") } let(:album) { Model::Album.new("Live And Dangerous", [song, song2, Model::Song.new("Emerald")]) } let(:representer) { AlbumRepresenter.new(album) } it do # album2 = Model::Album.new("Louder And Even More Dangerous", [song2, song]) # makes sure options are passed correctly. representer.to_hash(user_options: {volume: 9}).must_equal({"name"=>"Live And Dangerous", "songs"=>[{"title"=>"Jailbreak:{:volume=>9}"}, {"title"=>"Southbound:{:volume=>9}"}, {"title"=>"Emerald:{:volume=>9}"}]}) # called in Deserializer/Serializer # representer becomes reusable as it is stateless. # representer.update!(album2) # makes sure options are passed correctly. # representer.to_hash(volume:10).must_equal(album_hash) end # profiling it do representer.to_hash data = Profiler.profile { representer.to_hash } # 3 songs get decorated. data.must_match(/3\s*Representable::Function::Decorate#call/m) # These weird Regexp bellow are a quick workaround to accomodate # the different profiler result formats. # - "3 #prepare" -> At MRI Ruby # - "3 Representable::Decorator.prepare" -> At JRuby # 3 nested decorator is instantiated for 3 Songs, though. data.must_match(/3\s*(?[\#.]prepare/m) # no Binding is instantiated at runtime. data.wont_match "Representable::Binding#initialize" # 2 mappers for Album, Song # data.must_match "2 Representable::Mapper::Methods#initialize" # title, songs, 3x title, composer data.must_match(/8\s*Representable::Binding[#\.]render_pipeline/m) data.wont_match "render_functions" data.wont_match "Representable::Binding::Factories#render_functions" end end describe "deserialization" do let(:album_hash) { { "name"=>"Louder And Even More Dangerous", "songs"=>[ {"title"=>"Southbound", "composer"=>{"name"=>"Lynott"}}, {"title"=>"Jailbreak", "composer"=>{"name"=>"Phil Lynott"}}, {"title"=>"Emerald"} ] } } it do album = Model::Album.new AlbumRepresenter.new(album).from_hash(album_hash) album.songs.size.must_equal 3 album.name.must_equal "Louder And Even More Dangerous" album.songs[0].title.must_equal "Southbound" album.songs[0].composer.name.must_equal "Lynott" album.songs[1].title.must_equal "Jailbreak" album.songs[1].composer.name.must_equal "Phil Lynott" album.songs[2].title.must_equal "Emerald" album.songs[2].composer.must_be_nil # TODO: test options. end it "xxx" do representer = AlbumRepresenter.new(Model::Album.new) representer.from_hash(album_hash) data = Profiler.profile { representer.from_hash(album_hash) } # only 2 nested decorators are instantiated, Song, and Artist. # Didn't like the regexp? # MRI and JRuby has different output formats. See note above. data.must_match(/5\s*(?[#\.]prepare/) # a total of 5 properties in the object graph. data.wont_match "Representable::Binding#initialize" data.wont_match "parse_functions" # no pipeline creation. data.must_match(/10\s*Representable::Binding[#\.]parse_pipeline/) # three mappers for Album, Song, composer # data.must_match "3 Representable::Mapper::Methods#initialize" # # 6 deserializers as the songs collection uses 2. # data.must_match "6 Representable::Deserializer#initialize" # # one populater for every property. # data.must_match "5 Representable::Populator#initialize" # printer.print(STDOUT) end end endrepresentable-3.0.4/test/populator_test.rb0000644000175000017500000000615513137322612017774 0ustar pravipravirequire "test_helper" class PopulatorTest < Minitest::Spec Song = Struct.new(:id) Artist = Struct.new(:name) Album = Struct.new(:songs, :artist) describe "populator: ->{}" do representer! do collection :songs, populator: ->(input, options) { options[:represented].songs << song = Song.new; song } do property :id end property :artist, populator: ->(input, options) { options[:represented].artist = Artist.new } do property :name end end let(:album) { Album.new([]) } it do album.extend(representer).from_hash("songs"=>[{"id"=>1}, {"id"=>2}], "artist"=>{"name"=>"Waste"}) album.inspect.must_equal "#, #], artist=#>" end end describe "populator: ->{}, " do end end class PopulatorFindOrInstantiateTest < Minitest::Spec Song = Struct.new(:id, :title, :uid) do def self.find_by(attributes={}) return new(1, "Resist Stan", "abcd") if attributes[:id]==1# we should return the same object here new end end Composer = Struct.new(:song) Composer.class_eval do def song=(v) @song = v "Absolute nonsense" # this tests that the populator always returns the correct object. end attr_reader :song end describe "FindOrInstantiate with property" do representer! do property :song, populator: Representable::FindOrInstantiate, class: Song do property :id property :title end end let(:album) { Composer.new.extend(representer).extend(Representable::Debug) } it "finds by :id and creates new without :id" do album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance"}}) album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan" album.song.id.must_equal 1 album.song.uid.must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object. end it "creates new without :id" do album.from_hash({"song"=>{"title"=>"Lower"}}) album.song.title.must_equal "Lower" album.song.id.must_be_nil album.song.uid.must_be_nil end end describe "FindOrInstantiate with collection" do representer! do collection :songs, populator: Representable::FindOrInstantiate, class: Song do property :id property :title end end let(:album) { Struct.new(:songs).new([]).extend(representer) } it "finds by :id and creates new without :id" do album.from_hash({"songs"=>[ {"id" => 1, "title"=>"Resist Stance"}, {"title"=>"Suffer"} ]}) album.songs[0].title.must_equal "Resist Stance" # note how title is updated from "Resist Stan" album.songs[0].id.must_equal 1 album.songs[0].uid.must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object. album.songs[1].title.must_equal "Suffer" album.songs[1].id.must_be_nil album.songs[1].uid.must_be_nil end # TODO: test with existing collection end endrepresentable-3.0.4/test/mongoid_test.rb0000644000175000017500000000133313137322612017374 0ustar pravipravi# require 'test_helper' # require 'mongoid' # require 'mongoid/document' # class MongoidTest < MiniTest::Spec # describe "Mongoid compatibility" do # it "allows #to_json" do # class Profile # include Mongoid::Document # field :name # end # class Dude # include Mongoid::Document # embeds_one :profile, :class_name => "MongoidTest::Profile" # end # module ProfileRepresenter # include Representable::JSON # property :name # end # dude = Dude.new # dude.profile = Profile.new # dude.profile.name = "Kofi" # assert_equal "{\"name\":\"Kofi\"}", dude.profile.extend(ProfileRepresenter).to_json # end # end # end representable-3.0.4/test/parse_pipeline_test.rb0000644000175000017500000000403413137322612020740 0ustar pravipravirequire "test_helper" class ParsePipelineTest < MiniTest::Spec Album = Struct.new(:id, :artist, :songs) Artist = Struct.new(:email) Song = Struct.new(:title) describe "transforming nil to [] when parsing" do representer!(decorator: true) do collection :songs, parse_pipeline: ->(*) { Representable::Pipeline.insert( parse_functions, # original function list from Binding#parse_functions. ->(input, options) { input.nil? ? [] : input }, # your new function (can be any callable object).. replace: Representable::OverwriteOnNil # ..that replaces the original function. ) }, class: Song do property :title end end it do representer.new(album = Album.new).from_hash("songs"=>nil) album.songs.must_equal [] end it do representer.new(album = Album.new).from_hash("songs"=>[{"title" => "Business Conduct"}]) album.songs.must_equal [Song.new("Business Conduct")] end end # tests [Collect[Instance, Prepare, Deserialize], Setter] class Representer < Representable::Decorator include Representable::Hash # property :artist, populator: Uber::Options::Value.new(ArtistPopulator.new), pass_options:true do # property :email # end # DISCUSS: rename to populator_pipeline ? collection :songs, parse_pipeline: ->(*) { [Collect[Instance, Prepare, Deserialize], Setter] }, instance: :instance!, exec_context: :decorator, pass_options: true do property :title end def instance!(*options) Song.new end def songs=(array) represented.songs=array end end it do skip "TODO: implement :parse_pipeline and :render_pipeline, and before/after/replace semantics" album = Album.new Representer.new(album).from_hash({"artist"=>{"email"=>"yo"}, "songs"=>[{"title"=>"Affliction"}, {"title"=>"Dream Beater"}]}) album.songs.must_equal([Song.new("Affliction"), Song.new("Dream Beater")]) end endrepresentable-3.0.4/test/xml_bindings_test.rb0000644000175000017500000000313413137322612020416 0ustar pravipravirequire 'test_helper' require 'representable/xml/hash' class XMLBindingTest < MiniTest::Spec module SongRepresenter include Representable::XML property :name self.representation_wrap = :song end class SongWithRepresenter < ::Song include Representable include SongRepresenter self.representation_wrap = :song end before do @doc = Nokogiri::XML::Document.new @song = SongWithRepresenter.new("Thinning the Herd") end describe "AttributeBinding" do describe "with plain text items" do before do @property = Representable::XML::Binding::Attribute.new(Representable::Definition.new(:name, :attribute => true)) end it "extracts with #read" do assert_equal "The Gargoyle", @property.read(Nokogiri::XML("").root, "name") end it "inserts with #write" do parent = Nokogiri::XML::Node.new("song", @doc) @property.write(parent, "The Gargoyle", "name") assert_xml_equal("", parent.to_s) end end end describe "ContentBinding" do before do @property = Representable::XML::Binding::Content.new(Representable::Definition.new(:name, :content => true)) end it "extracts with #read" do assert_equal "The Gargoyle", @property.read(Nokogiri::XML("The Gargoyle").root, "song") end it "inserts with #write" do parent = Nokogiri::XML::Node.new("song", @doc) @property.write(parent, "The Gargoyle", "song") assert_xml_equal("The Gargoyle", parent.to_s) end end end representable-3.0.4/CHANGES.md0000644000175000017500000007146013137322612014777 0ustar pravipravi# 3.0.4 * Add proper XML namespace support. * [internal] Replace `XML::Binding#node_for` with function `XML::Node`. # 3.0.3 * Replace `Uber::Option` with the new [`Declarative::Option`](https://github.com/apotonick/declarative-option). This should result in a significant performance boost. # 3.0.2 * Initialize `Config@wrap` to avoid Ruby's warning. * Add `#render` and `#parse` alias methods to all format modules as a generic entry point. * In `GetValue`, use `public_send` now. # 3.0.1 * Loosen `uber` dependency. # 3.0.0 * Removed deprecations from 2.4. * Removed `:parse_strategy` in favor of `:populator`. * Removed `:binding` in favor of your own pipeline. # 2.4.1 * No need to use Uber::Callable in Pipeline as this object is always invoked via `#call`. # 2.4.0 Full migration guide here: http://trailblazer.to/gems/representable/upgrading-guide.html#to-24 * Breaking API change: `:parse_filter` and `:render_filter` have no deprecation as all the other, they receive one options. render_filter: val, doc, options * `Decorator` always needs a format engine included, e.g. `Representable::JSON` to build bindings at compile-time. * Removed `Representable::Apply`. This is now done via `Schema`. representer_class.representable_attrs is definitions * Removed `:use_decorator` option. Use a decorator instead. * Added `Representable.deprecations = false` to disable slow and weird deprecation code. * Removed `Binding#user_options`. This is now available via `->(options[:user_options])`. * Removed `Binding#as`. * Removed `Binding#parent_decorator`. ## Internal Changes * Removed `Binding@represented` (which was never public anyway). Use `Binding#represented`. * Changed signature: `Binding#get(represented:)`. In now needs a hash `{represented: ..}`. # 2.4.0.rc5 * Fix double definition of `Insert`. * Deprecate `:binding`. # 2.4.0.rc4 * The preferred way of passing user options is now `to_hash(user_options: {})`. * Supports nested options for nested representers. # 2.4.0.rc3 * `Set` is `SetValue`. `Get` is `GetValue`. * `CreateObject` no longer invokes `AssignFragment`. This is now part of the official parse pipeline. # 2.4.0.rc2 * Use Declarative's `::build_definition` interface instead of overwriting `::property`. # 2.3.0 * Remove dependency to Nokogiri and Multi_JSON. You have to add what you need to your `Gemfile`/`gemspec` now. * `to_*`/`from_*` with options do no longer change the hash but work on copies. * `to_*`/`from_*` now respect `wrap: false`. This will suppress the wrapping on the first level. * Introduce `property "name", wrap: false`. This allows reusing existing representers with `representation_wrap` set as nested representers but suppressing the wrapping. ```ruby class BandDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :bands # wrap set! property :name end class AlbumDecorator < Representable::Decorator include Representable::Hash self.representation_wrap = :albums # wrap set! property :band, decorator: BandDecorator, wrap: false # you can now set :wrap to false! end album.to_hash #=> {"albums" => {"band" => {"name"=>"Social Distortion"}}} ``` Thanks to @fighella for inspiring this feature when working on [roarify](https://github.com/fighella/roarify). * `from_hash` no longer crashes with "NoMethodError: undefined method 'has_key?' for nil:NilClass" when an incoming nested object's value is `nil`. This was a problem with documents like `{song: nil}` where `song` is a nested property. Thanks to @mhuggins and @moxley for helping here. # 2.2.3 * Introduce `Decorator::clone` to make sure cloning properly copies representable_attrs. in former versions, this would partially share definitions with subclasses when the decorator was an `inheritable_attr`. # 2.2.2 * Bug fix: In 2.2.1 I accidentially removed a `super` call in `Representable::inherited` which leads to wrong behavior when having Representable mixed into a class along with other classes overriding `inherited`. Thanks to @jrmhaig for an [excellent bug report](https://github.com/apotonick/representable/issues/139#issuecomment-105926608) making it really easy to find the problem. # 2.2.1 ## API change. * Options in `Definition` are now Cloneable. That means they will deep-clone when they contain values that are `Cloneable`. This allows clean cloning of deeply nested configuration hashes, e.g. for `deserializer: {instance: ->{}}` in combination with inheritance across representers. The former behavior was not to clone, which would allow sub-representers to bleed into the parent options, which is _wrong_. However, this fix shouldn't affect anyone but me. # 2.2.0 ## New Stuff * Introduce `Representable::Cached` that will keep the mapper, which in turn will keep the bindings, which in turn will keep their representer, in case they're nested. You have to include this feature manually and you can expect a 50% and more speed-up for rendering and parsing. Not to speak about the reduced memory footprint. ```ruby class SongDecorator < Representable::Decorator include Representable::JSON feature Representable::Cached # .. end ``` * Introduced `Decorator#update!` to re-use a decorator instance between requests. This will inject the represented object, only. ```ruby decorator = SongDecorator.new(song) decorator.to_json(..) decorator.update!(louder_song) decorator.to_json(..) ``` This is quite awesome. ## API change. * The `:extend` option only accepts one module. `extend: [Module, Module]` does no longer work and it actually didn't work in former versions of 2.x, anyway, it just included the first element of an array. * Remove `Binding#representer_module`. # 2.1.8 * API change: features are now included into inline representers in the order they were specified. This used to be the other way round and is, of course, wrong, in case a sub-feature wants to override an existing method introduced by an earlier feature. ```ruby class Album < Representable::Decorator include Representable::Hash feature Title feature Date property :songs # will include R::Hash, Title, then Date. ``` As this is an edge-casey change, I decided _not_ to minor-version bump. # 2.1.7 * Adding `Object#to_object`. This is even faster than using `#from_object` for simple transformations. # 2.1.6 * Introducing `Representable::Object` that allows mapping objects to objects. This is way faster than rendering a hash from the source and then writing the hash to the target object. * Fixed loading issues when requiring `representable/decorator`, only. # 2.1.5 * Using `inherit: true` now works even if the parent property hasn't been defined before. It will simply create a new property. This used to crash with `undefined method `merge!' for nil:NilClass`. ```ruby class SongRepresenter < Representable::Decorator property :title, inherit: true # this will create a brand-new :title property. end ``` # 2.1.4 * Allow lonely collection representers without configuration, with inline representer, only. This is for render-only collection representers and very handy. * Now runs with MagLev. # 2.1.3 * Like 2.1.2 (got yanked) because I thought it's buggy but it's not. What has changed is that `Serializer::Collection#serialize` no longer does `collection.collect` but `collection.each` since this allows filtering out unwanted elements. # 2.1.2 * Added `:skip_render` options. # 2.1.1 * Added `Definition#delete!` to remove options. * Added `Representable::apply` do iterate and change schemas. * Added `Config#remove` to remove properties. * Added `Representable::Debug` which just has to be included into your represented object. ```ruby song.extend(SongRepresenter).extend(Representable::Debug).from_json("..") song.extend(SongRepresenter).extend(Representable::Debug).to_json("..") ``` It can also be included statically into your representer or decorator. ```ruby class SongRepresenter < Representable::Decorator include Representable::JSON include Representable::Debug property :title end ``` It is great. # 2.1.0 ## Breaking Changes * None, unless you messed around with internals like `Binding`. ## Changes * Added `:skip_parse` to skip deserialization of fragments. * It's now `Binding#read_fragment -> Populator -> Deserializer`. Mostly, this got changed to allow better support for complex collection semantics when populating/deserializing as found in Object-HAL. * Likewise, it is `Binding#write_fragment -> Serializer`, clearly separating format-specific and generic logic. * Make `Definition#inspect` more readable by filtering out some instance variables like `@runtime_options`. * Remove `Binding#write_fragment_for`. This is `#render_fragment` now. * Almost 100% speedup for rendering and parsing by removing Ruby's delegation and `method_missing`. * Bindings are now in the following naming format: `Representable::Hash::Binding[::Collection]`. The file name is `representable/hash/binding`. * Move `Definition#skipable_empty_value?` and `Definition#default_for` to `Binding` as it is runtime-specific. # 2.0.4 * Fix implicit rendering of JSON and XML collections where json/collection wasn't loaded properly, resulting in the native JSON's `#to_json` to be called. * Fix `:find_or_instantiate` parse strategy which wouldn't instantiate but raise an error. Thanks to @d4rky-pl. # 2.0.3 * Fixed a bug where `Forwardable` wasn't available (because we didn't require it :). # 2.0.2 * Fixed a bug with `Config#[]` which returned a default value that shouldn't be there. # 2.0.1 * Made is simpler to define your own `Definition` class by passing it to `Config.new(Definition)` in `Representer::build_config`. # 2.0.0 ## Relevant * Removed class methods `::from_json`, `::from_hash`, `::from_yaml` and `::from_xml`. Please build the instance yourself and use something along `Song.new.from_json`. * Inline representers in `Decorator` do *no longer inherit from `self`*. When defining an inline representer, they are always derived from `Representable::Decorator`. The base class can be changed by overriding `Decorator::default_inline_class` in the decorator class that defines an inline representer block. If you need to inherit common methods to all inline decorators, include the module using `::feature`: `Representer.feature(BetterProperty)`. * You can now define methods in inline representers! The block is now `module_eval`ed and not `instance_exec`ed anymore. Same goes for Decorators, note that you need to `exec_context: :decorator`, though. Here, the block is `class_eval`ed. * Removed behaviour for `instance: lambda { |*| nil }` which used to return `binding.get`. Simply do it yourself: `instance: lambda { |fragment, options| options.binding.get }` if you need this behaviour. If you use `:instance` and it returns `nil` it throws a `DeserializeError` now, which is way more understandable than `NoMethodError: undefined method 'title=' for {"title"=>"Perpetual"}:Hash`. * Remove behaviour for `class: lambda { nil }` which used to return the fragment. This now throws a `DeserializeError`. Do it yourself with `class: lambda { |fragment,*| fragment }`. * Coercion now happens inside `:render_filter` and `:parse_filter` (new!) and doesn't block `:getter` and `:setter` anymore. Also, we require virtus >=1.0 now. * `::representation_wrap=` in now properly inherited. * Including modules with representable `property .., inherit: true` into a `Decorator` crashed. This works fine now. ## New Stuff * Added `::for_collection` to automatically generate a collection representer for singular one. Thanks to @timoschilling for inspiring this years ago. * Added `::represent` that will detect collections and render the singular/collection with the respective representer. * Added `Callable` options. * Added `:parse_filter` and `:render_filter`. ## Internals * Added `Representable::feature` to include a module and register it to be included into inline representers. * New signature: `inline_representer(base, features, name, options, &block)`. * Removed `::representer_engine`, the module to include is just another `register_feature` now. * `Config` no longer is a Hash, it's API is limited to a few methods like `#<<`, `#[]` etc. It still supports the `Enumberable` interface. * Moved `Representable::ClassMethods::Declarations` to `Representable::Declarative`. * Moved `Representable::ClassMethods` to `Representable::Declarative`. * Fixed: Inline decorators now work with `inherit: true`. * Remove `:extend` in combination with inline representer. The `:extend` option is no longer considered. Include the module directly into the inline block. * Deprecated class methods `::from_json` and friends. Use the instance version on an instance. * Use uber 0.0.7 so we can use `Uber::Callable`. * Removed `Decorator::Coercion`. * Removed `Definition#skipable_nil_value?`. # 1.8.5 * Binding now uses `#method_missing` instead of SimpleDelegator for a significant performance boost of many 100%s. Thanks to @0x4a616d6573 for figuring this. # 1.8.4 * Make `representable/json` work without having to require `representable/hash`. Thanks to @acuppy!!! # 1.8.3 * Fix `JSON::Collection` and `JSON::Hash` (lonely arrays and hashes), they can now use inline representers. Thanks to @jandudulski for reporting. * Added `:render_empty` option to suppress rendering of empty collections. This will default to true in 2.0. * Remove Virtus deprecations. * Add support for Rubinius. * `Definition#default` is public now, please don't use it anyway, it's a private concept. # 1.8.1 * Add `:serialize` and `:deserialize` options for overriding those steps. # 1.8.0 ## Major Breakage * `:if` receives block arguments just like any other dynamic options. Refer to **Dynamic Options**. * Remove defaults for collections. This fixes a major design flaw - when parsing a document a collection would be reset to `[]` even if it is not present in the parsed document. * The number of arguments per block might have changed. Generally, if you're not interested in block arguments, use `Proc.new` or `lambda { |*| }`. See **Dynamic Options**. ## Dynamic Options * The following options are dynamic now and can either be a static value, a lambda or an instance method symbol: `:as`, `:getter`, `:setter`, `:class`, `:instance`, `:reader`, `:writer`, `:extend`, `:prepare`, `:if`. Please refer to the README to see their signatures. * `representation_wrap` is dynamic, too, allowing you to change the wrap per instance. ## Cool New Stuff * When unsure about the number of arguments passed into an option lambda, use `:pass_options`. This passes all general options in a dedicated `Options` object that responds to `binding`, `decorator`, `represented` and `user_options`. It's always the last argument for the block. * Added `parse_strategy: :find_or_instantiate`. More to come. * Added `parse_strategy: lambda { |fragment, i, options| }` to implement your own deserialization. * Use `representable: false` to prevent calling `to_*/from_*` on a represented object even if the property is `typed?` (`:extend`, `:class` or `:instance` set). * Introduced `:use_decorator` option to force an inline representer to be implemented with a Decorator even in a module. This fixes a bug since we used the `:decorate` option in earlier versions, which was already used for something else. * Autoload `Representable::Hash*` and `Representable::Decorator`. * Added `Representable::Hash::AllowSymbols` to convert symbol keys to strings in `from_hash`. ## Deprecations * `decorator_scope: true` is deprecated, use `exec_context: :decorator` instead. * Using `:extend` in combination with an inline representer is deprecated. Include the module in the block. * `instance: lambda { true }` is deprecated. Use `parse_strategy: :sync`. * Removed `Config#wrap`. Only way to retrieve the evaluated wrap is `Config#wrap_for`. * `class: lambda { nil }` is deprecated. To return the fragment from parsing, use `instance: lambda { |fragment, *args| fragment }` instead. ## Definition * Make `Definition < Hash`, all options can/should now be accessed with `Definition#[]`. * Make `Definition::new` and `#merge!` the only entry points so that a `Definition` becomes an almost *immutual* object. If you happened to modify a definition using `options[..]=` this will break now. Use `definition.merge!(..)` to change it after creation. * Deprecated `#options` as the definition itself is a hash (e.g. `definition[:default]`). * Removed `#sought_type`, `#default`, `#attribute`, `#content`. * `#from` is replaced by `#as` and hardcore deprecated. * `#name` and `#as` are _always_ strings. * A Definition is considered typed as soon as [`:extend`|`:class`|`:instance`] is set. In earlier versions, `property :song, class: Song` was considered typed, whereas `property :song, class: lambda { Song }` was static. h2. 1.7.7 * Parsing an empty hash with a representer having a wrap does no longer throw an exception. * `::nested` now works in modules, too! Nests are implemented as decorator representer, not as modules, so they don't pollute the represented object. * Introduce `:inherit` to allow inheriting+overriding properties and inline representers (and properties in inline representers - it starts getting crazy!!!). h2. 1.7.6 * Add `::nested` to nest blocks in the document whilst still using the same represented object. Use with `Decorator` only. * Fixing a bug (thanks @rsutphin) where inline decorators would inherit the properties from the outer decorator. h2. 1.7.5 * propagate all options for ::property to ::inline_representer. h2. 1.7.3 * Fix segfaulting with XML by passing the document to nested objects. Reported by @timoschilling and fixed by @canadaduane. h2. 1.7.2 * `Representable#update_properties_from` is private now. * Added the `:content` option in XML to map top-level node's content to a property. h2. 1.7.1 * Introduce `Config#options` hash to store per-representer configuration. * The XML representer can now automatically remove namespaces when parsing. Use `XML::remove_namespaces!` in your representer. This is a work-around until namespaces are properly implemented in representable. h2. 1.7.0 * The actual serialization and deserialization (that is, calling `to_hash` etc on the object) now happens in dedicated classes: `ObjectDeserializer` and friends. If you used to override stuff in `Binding`, I'm sorry. * A new option `parse_strategy: :sync`. Instead of creating a new object using the `:class` option when parsing, it uses the original object found in the represented instance. This works for property and collections. * `Config` is now a hash. You may find a particular definition by using `Config#[]`. * Properties are now overridden: when calling `property(:title)` multiple times with the same name, this will override the former `Definition`. While this slightly changes the API, it allows overriding properties cleanly in sub-representers and saves you from manually finding and fiddling with the definitions. h2. 1.6.1 * Using `instance: lambda { nil }` will now treat the property as a representable object without trying to extend it. It simply calls `to_*/from_*` on the property. * You can use an inline representer and still have a `:extend` which will be automatically included in the inline representer. This is handy if you want to "extend" a base representer with an inline block. Thanks to @pixelvitamina for requesting that. * Allow inline representers with `collection`. Thanks to @rsutphin! h2. 1.6.0 * You can define inline representers now if you don't wanna use multiple modules and files. ```ruby property :song, class: Song do property :title end ``` This supersedes the use for `:extend` or `:decorator`, which still works, of course. * Coercion now happens in a dedicated coercion object. This means that in your models virtus no longer creates accessors for coerced properties and thus values get coerced when rendering or parsing a document, only. If you want the old behavior, include `Virtus` into your model class and do the coercion yourself. * `Decorator::Coercion` is deprecated, just use `include Representable::Coercion`. * Introducing `Mapper` which does most of the rendering/parsing process. Be careful, if you used to override private methods like `#compile_fragment` this no longer works, you have to override that in `Mapper` now. * Fixed a bug where inheriting from Decorator wouldn't inherit properties correctly. h2. 1.5.3 * `Representable#update_properties_from` now always returns `represented`, which is `self` in a module representer and the decorated object in a decorator (only the latter changed). * Coercion in decorators should work now as expected. * Fixed a require bug. h2. 1.5.2 * Rename `:representer_exec` to `:decorator_scope` and make it a documented (!) feature. * Accessors for properties defined with `decorator_scope: true` will now be invoked on the decorator, not on the represented instance anymore. This allows having decorators with helper methods. * Use `MultiJson` instead of `JSON` when parsing and rendering. * Make `Representable::Decorator::Coercion` work. h2. 1.5.1 * Make lonely collections and hashes work with decorators. h2. 1.5.0 * All lambdas now receive user options, too. Note that this might break your existing lambdas (especially with `:extend` or `:class`) raising an `ArgumentError: wrong number of arguments (2 for 1)`. Fix this by declaring your block params correctly, e.g. `lambda { |name, *|`. Internally, this happens by running all lambdas through the new `Binding#represented_exec_for`. h2. 1.4.2 * Fix the processing of `:setter`, we called both the setter lambda and the setter method. h2. 1.4.1 * Added `:representer_exec` to have lambdas be executed in decorator instance context. h2. 1.4.0 * We now have two strategies for representing: the old extend approach and the brand-new decorator which leaves represented objects untouched. See "README":https://github.com/apotonick/representable#decorator-vs-extend for details. * Internally, either extending or decorating in the Binding now happens through the representer class method `::prepare` (i.e. `Decorator::prepare` or `Representable::prepare` for modules). That means any representer module or class must expose this class method. h2. 1.3.5 * Added `:reader` and `:writer` to allow overriding rendering/parsing of a property fragment and to give the user access to the entire document. h2. 1.3.4 * Replacing `json` gem with `multi_json` hoping not to cause trouble. h2. 1.3.3 * Added new options: `:binding`, `:setter` and `:getter`. * The `:if` option now eventually receives passed in user options. h2. 1.3.2 * Some minor internal changes. Added `Config#inherit` to encasulate array push behavior. h2. 1.3.1 * Bringing back `:as`. For some strange reasons "we" lost that commit from @csexton!!! h2. 1.3.0 * Remove @:exclude@ option. * Moving all read/write logic to @Binding@. If you did override @#read_fragment@ and friends in your representer/models this won't work anymore. * Options passed to @to_*/from_*@ are now passed to nested objects. h2. 1.2.9 * When @:class@ returns @nil@ we no longer try to create a new instance but use the processed fragment itself. * @:instance@ allows overriding the @ObjectBinding#create_object@ workflow by returning an instance from the lambda. This is particularly helpful when you need to inject additional data into the property object created in #deserialize. * @:extend@ and @:class@ now also accept procs which allows having polymorphic properties and collections where representer and class can be chosen at runtime. h2. 1.2.8 * Reverting all the bullshit from 1.2.7 making it even better. @Binding@s now wrap their @Definition@ instance adopting its API. Moved the binding_for_definition mechanics to the respecting @Binding@ subclass. * Added :readable and :writeable to #property: while @:readable => true@ renders the property into the document @:writeable => true@ allows updating the property's value when consuming a representation. Both default to @true@. h2. 1.2.7 * Moving @Format.binding_for_definition@ to @Format#{format}_binding_for_definition@, making it an instance method in its own "namespace". This allows mixing in multiple representer engines into a user's representer module. h2. 1.2.6 * Extracted @HashRepresenter@ which operates on hash structures. This allows you to "parse" form data, e.g. as in Rails' @params@ hash. Internally, this is used by JSON and partly by YAML. h2. 1.2.5 * Add support for YAML. h2. 1.2.4 * ObjectBinding no longer tries to extend nil values when rendering and @:render_nil@ is set. * In XML you can now use @:wrap@ to define an additional container tag around properties and collections. h2. 1.2.3 * Using virtus for coercion now works in both classes and modules. Thanks to @solnic for a great collaboration. Open-source rocks! h2. 1.2.2 * Added @XML::AttributeHash@ to store hash key-value pairs in attributes instead of dedicated tags. * @JSON::Hash@, @XML::Hash@ and @XML::AttributeHash@ now respect @:exclude@ and @:include@ when parsing and rendering. h2. 1.2.1 * Deprecated @:represent_nil@ favor of @:render_nil@. * API change: if a property is missing in an incoming document and there is no default set it is completely ignored and *not* set in the represented object. h2. 1.2.0 * Deprecated @:except@ in favor of @:exclude@. * A property with @false@ value will now be included in the rendered representation. Same applies to parsing, @false@ values will now be included. That particularly means properties that used to be unset (i.e. @nil@) after parsing might be @false@ now. * You can include @nil@ values now in your representations since @#property@ respects @:represent_nil => true@. h2. 1.1.6 * Added @:if@ option to @property@. h2. 1.1.5 * Definitions are now properly cloned when @Config@ is cloned. h2. 1.1.4 * representable_attrs is now cloned when a representer module is included in an inheriting representer. h2. 1.1.3 * Introduced `#compile_fragment` and friends to make it simpler overriding parsing and rendering steps. h2. 1.1.2 * Allow `Module.hash` to be called without arguments as this seems to be required in Padrino. h2. 1.1.1 * When a representer module is extended we no longer set the @representable_attrs ivar directly but use a setter. This makes it work with mongoid and fixes https://github.com/apotonick/roar/issues/10. h2. 1.1.0 * Added `JSON::Collection` to have plain list representations. And `JSON::Hash` for hashes. * Added the `hash` class method to XML and JSON to represent hashes. * Defining `:extend` only on a property now works for rendering. If you try parsing without a `:class` there'll be an exception, though. h2. 1.0.1 * Allow passing a list of modules to :extend, like @:extend => [Ingredient, IngredientRepresenter]@. h2. 1.0.0 * 1.0.0 release! Party time! h2. 0.13.1 * Removed property :@name from @XML@ in favor of @:attribute => true@. h2. 0.13.0 * We no longer create accessors in @Representable.property@ - you have to do this yourself using @attr_accessors@. h2. 0.12.0 * @:as@ is now @:class@. h2. 0.11.0 * Representer modules can now be injected into objects using @#extend@. * The @:extend@ option allows setting a representer module for a typed property. This will extend the contained object at runtime roughly following the DCI pattern. * Renamed @#representable_property@ and @#representable_collection@ to @#property@ and @#collection@ as we don't have to fear namespace collisions in modules. h2. 0.10.3 * Added @representable_property :default => ...@ option which is considered for both serialization and deserialization. The default is applied when the value is @nil@. Note that an empty string ain't @nil@. * @representable_attrs@ are now pushed to instance level as soon as possible. h2. 0.10.2 * Added @representable_property :accessors => false@ option to suppress adding accessors. * @Representable.representation_wrap@ is no longer inherited. * Representers can now be defined in modules. They inherit to including modules. h2. 0.10.1 * The block in @to_*@ and @from_*@ now yields the symbolized property name. If you need the binding/definition you gotta get it yourself. * Runs with Ruby 1.8 and 1.9. h2. 0.10.0 * Wrapping can now be set through @Representable.representation_wrap=@. Possible values are: * @false@: No wrapping. In XML context, this is undefined behaviour. Default in JSON. * @String@: Wrap with provided string. * @true@: compute wrapper from class name. h2. 0.9.3 * Removed the @:as => [..]@ syntax in favor of @:array => true@. h2. 0.9.2 * Arguments and block now successfully forwarded in @#from_*@. h2. 0.9.1 * Extracted common serialization into @Representable#create_representation_with@ and deserialization into @#update_properties_from@. * Both serialization and deserialization now accept a block to make them skip elements while iterating the property definitions. h2. 0.9.0 h3. Changes * Removed the :tag option in favor of :from. The Definition#from method is now authorative for all name mappings. * Removed the active_support and i18n dependency.