representable 2.4.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +7 -12
- data/CHANGES.md +6 -27
- data/README.md +28 -1326
- data/lib/representable.rb +4 -14
- data/lib/representable/binding.rb +3 -7
- data/lib/representable/definition.rb +1 -2
- data/lib/representable/populator.rb +35 -0
- data/lib/representable/version.rb +1 -1
- data/test/definition_test.rb +0 -7
- data/test/exec_context_test.rb +2 -2
- data/test/instance_test.rb +0 -19
- data/test/realistic_benchmark.rb +0 -13
- data/test/representable_test.rb +0 -16
- data/test/skip_test.rb +1 -1
- data/test/test_helper.rb +0 -2
- metadata +3 -65
- data/lib/representable/deprecations.rb +0 -127
- data/lib/representable/parse_strategies.rb +0 -93
- data/test-with-deprecations/as_test.rb +0 -65
- data/test-with-deprecations/benchmarking.rb +0 -83
- data/test-with-deprecations/binding_test.rb +0 -46
- data/test-with-deprecations/blaaaaaaaa_test.rb +0 -69
- data/test-with-deprecations/cached_test.rb +0 -147
- data/test-with-deprecations/class_test.rb +0 -119
- data/test-with-deprecations/coercion_test.rb +0 -52
- data/test-with-deprecations/config/inherit_test.rb +0 -135
- data/test-with-deprecations/config_test.rb +0 -122
- data/test-with-deprecations/decorator_scope_test.rb +0 -28
- data/test-with-deprecations/decorator_test.rb +0 -96
- data/test-with-deprecations/default_test.rb +0 -34
- data/test-with-deprecations/defaults_options_test.rb +0 -93
- data/test-with-deprecations/definition_test.rb +0 -264
- data/test-with-deprecations/example.rb +0 -310
- data/test-with-deprecations/examples/object.rb +0 -31
- data/test-with-deprecations/exec_context_test.rb +0 -93
- data/test-with-deprecations/features_test.rb +0 -70
- data/test-with-deprecations/filter_test.rb +0 -57
- data/test-with-deprecations/for_collection_test.rb +0 -74
- data/test-with-deprecations/generic_test.rb +0 -116
- data/test-with-deprecations/getter_setter_test.rb +0 -21
- data/test-with-deprecations/hash_bindings_test.rb +0 -87
- data/test-with-deprecations/hash_test.rb +0 -160
- data/test-with-deprecations/heritage_test.rb +0 -62
- data/test-with-deprecations/if_test.rb +0 -79
- data/test-with-deprecations/include_exclude_test.rb +0 -88
- data/test-with-deprecations/inherit_test.rb +0 -159
- data/test-with-deprecations/inline_test.rb +0 -272
- data/test-with-deprecations/instance_test.rb +0 -266
- data/test-with-deprecations/is_representable_test.rb +0 -77
- data/test-with-deprecations/json_test.rb +0 -355
- data/test-with-deprecations/lonely_test.rb +0 -239
- data/test-with-deprecations/mongoid_test.rb +0 -31
- data/test-with-deprecations/nested_test.rb +0 -115
- data/test-with-deprecations/object_test.rb +0 -60
- data/test-with-deprecations/parse_pipeline_test.rb +0 -64
- data/test-with-deprecations/parse_strategy_test.rb +0 -279
- data/test-with-deprecations/pass_options_test.rb +0 -27
- data/test-with-deprecations/pipeline_test.rb +0 -277
- data/test-with-deprecations/populator_test.rb +0 -105
- data/test-with-deprecations/prepare_test.rb +0 -67
- data/test-with-deprecations/private_options_test.rb +0 -18
- data/test-with-deprecations/reader_writer_test.rb +0 -19
- data/test-with-deprecations/realistic_benchmark.rb +0 -115
- data/test-with-deprecations/render_nil_test.rb +0 -21
- data/test-with-deprecations/represent_test.rb +0 -88
- data/test-with-deprecations/representable_test.rb +0 -511
- data/test-with-deprecations/schema_test.rb +0 -148
- data/test-with-deprecations/serialize_deserialize_test.rb +0 -33
- data/test-with-deprecations/skip_test.rb +0 -81
- data/test-with-deprecations/stringify_hash_test.rb +0 -41
- data/test-with-deprecations/test_helper.rb +0 -135
- data/test-with-deprecations/test_helper_test.rb +0 -25
- data/test-with-deprecations/uncategorized_test.rb +0 -67
- data/test-with-deprecations/user_options_test.rb +0 -15
- data/test-with-deprecations/wrap_test.rb +0 -152
- data/test-with-deprecations/xml_bindings_test.rb +0 -62
- data/test-with-deprecations/xml_test.rb +0 -503
- data/test-with-deprecations/yaml_test.rb +0 -162
- data/test/parse_strategy_test.rb +0 -279
@@ -1,105 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class PopulatorTest < Minitest::Spec
|
4
|
-
Song = Struct.new(:id)
|
5
|
-
Artist = Struct.new(:name)
|
6
|
-
Album = Struct.new(:songs, :artist)
|
7
|
-
|
8
|
-
describe "populator: ->{}" do
|
9
|
-
representer! do
|
10
|
-
collection :songs, populator: ->(input, options) { options[:represented].songs << song = Song.new; song } do
|
11
|
-
property :id
|
12
|
-
end
|
13
|
-
|
14
|
-
property :artist, populator: ->(input, options) { options[:represented].artist = Artist.new } do
|
15
|
-
property :name
|
16
|
-
end
|
17
|
-
end
|
18
|
-
|
19
|
-
let (:album) { Album.new([]) }
|
20
|
-
|
21
|
-
it do
|
22
|
-
album.extend(representer).from_hash("songs"=>[{"id"=>1}, {"id"=>2}], "artist"=>{"name"=>"Waste"})
|
23
|
-
album.inspect.must_equal "#<struct PopulatorTest::Album songs=[#<struct PopulatorTest::Song id=1>, #<struct PopulatorTest::Song id=2>], artist=#<struct PopulatorTest::Artist name=\"Waste\">>"
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
describe "populator: ->{}, " do
|
28
|
-
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
class PopulatorFindOrInstantiateTest < Minitest::Spec
|
33
|
-
Song = Struct.new(:id, :title, :uid) do
|
34
|
-
def self.find_by(attributes={})
|
35
|
-
return new(1, "Resist Stan", "abcd") if attributes[:id]==1# we should return the same object here
|
36
|
-
new
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
Composer = Struct.new(:song)
|
41
|
-
Composer.class_eval do
|
42
|
-
def song=(v)
|
43
|
-
@song = v
|
44
|
-
"Absolute nonsense" # this tests that the populator always returns the correct object.
|
45
|
-
end
|
46
|
-
|
47
|
-
attr_reader :song
|
48
|
-
end
|
49
|
-
|
50
|
-
describe "FindOrInstantiate with property" do
|
51
|
-
representer! do
|
52
|
-
property :song, populator: Representable::FindOrInstantiate, class: Song do
|
53
|
-
property :id
|
54
|
-
property :title
|
55
|
-
end
|
56
|
-
end
|
57
|
-
|
58
|
-
let (:album) { Composer.new.extend(representer).extend(Representable::Debug) }
|
59
|
-
|
60
|
-
it "finds by :id and creates new without :id" do
|
61
|
-
album.from_hash({"song"=>{"id" => 1, "title"=>"Resist Stance"}})
|
62
|
-
|
63
|
-
album.song.title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
|
64
|
-
album.song.id.must_equal 1
|
65
|
-
album.song.uid.must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object.
|
66
|
-
end
|
67
|
-
|
68
|
-
it "creates new without :id" do
|
69
|
-
album.from_hash({"song"=>{"title"=>"Lower"}})
|
70
|
-
|
71
|
-
album.song.title.must_equal "Lower"
|
72
|
-
album.song.id.must_equal nil
|
73
|
-
album.song.uid.must_equal nil
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
describe "FindOrInstantiate with collection" do
|
78
|
-
representer! do
|
79
|
-
collection :songs, populator: Representable::FindOrInstantiate, class: Song do
|
80
|
-
property :id
|
81
|
-
property :title
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
let (:album) { Struct.new(:songs).new([]).extend(representer) }
|
86
|
-
|
87
|
-
it "finds by :id and creates new without :id" do
|
88
|
-
album.from_hash({"songs"=>[
|
89
|
-
{"id" => 1, "title"=>"Resist Stance"},
|
90
|
-
{"title"=>"Suffer"}
|
91
|
-
]})
|
92
|
-
|
93
|
-
album.songs[0].title.must_equal "Resist Stance" # note how title is updated from "Resist Stan"
|
94
|
-
album.songs[0].id.must_equal 1
|
95
|
-
album.songs[0].uid.must_equal "abcd" # not changed via populator, indicating this is a formerly "persisted" object.
|
96
|
-
|
97
|
-
album.songs[1].title.must_equal "Suffer"
|
98
|
-
album.songs[1].id.must_equal nil
|
99
|
-
album.songs[1].uid.must_equal nil
|
100
|
-
end
|
101
|
-
|
102
|
-
# TODO: test with existing collection
|
103
|
-
end
|
104
|
-
|
105
|
-
end
|
@@ -1,67 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class PrepareTest < BaseTest
|
4
|
-
class PreparerClass
|
5
|
-
def initialize(object)
|
6
|
-
@object = object
|
7
|
-
end
|
8
|
-
|
9
|
-
def ==(b)
|
10
|
-
return unless b.instance_of?(PreparerClass)
|
11
|
-
|
12
|
-
object == b.object
|
13
|
-
end
|
14
|
-
|
15
|
-
attr_reader :object
|
16
|
-
end
|
17
|
-
|
18
|
-
describe "#to_hash" do # TODO: introduce :representable option?
|
19
|
-
representer! do
|
20
|
-
property :song,
|
21
|
-
:prepare => lambda { |obj, args| args.binding[:arbitrary].new(obj) },
|
22
|
-
:arbitrary => PreparerClass,
|
23
|
-
:extend => true,
|
24
|
-
:pass_options => true,
|
25
|
-
:representable => false # don't call #to_hash.
|
26
|
-
end
|
27
|
-
|
28
|
-
let (:hit) { Struct.new(:song).new(song).extend(representer) }
|
29
|
-
|
30
|
-
it "calls prepare:, nothing else" do
|
31
|
-
# render(hit).must_equal_document(output)
|
32
|
-
hit.to_hash.must_equal({"song" => PreparerClass.new(song)})
|
33
|
-
end
|
34
|
-
|
35
|
-
|
36
|
-
# it "calls #from_hash on the existing song instance, nothing else" do
|
37
|
-
# song_id = hit.song.object_id
|
38
|
-
|
39
|
-
# parse(hit, input)
|
40
|
-
|
41
|
-
# hit.song.title.must_equal "Suffer"
|
42
|
-
# hit.song.object_id.must_equal song_id
|
43
|
-
# end
|
44
|
-
end
|
45
|
-
|
46
|
-
|
47
|
-
describe "#from_hash" do
|
48
|
-
representer! do
|
49
|
-
property :song,
|
50
|
-
:prepare => lambda { |obj, args| args.binding[:arbitrary].new(obj) },
|
51
|
-
:arbitrary => PreparerClass,
|
52
|
-
#:extend => true, # TODO: typed: true would be better.
|
53
|
-
:instance => String.new, # pass_fragment
|
54
|
-
:pass_options => true,
|
55
|
-
:representable => false # don't call #to_hash.
|
56
|
-
end
|
57
|
-
|
58
|
-
let (:hit) { Struct.new(:song).new.extend(representer) }
|
59
|
-
|
60
|
-
it "calls prepare:, nothing else" do
|
61
|
-
# render(hit).must_equal_document(output)
|
62
|
-
hit.from_hash("song" => {})
|
63
|
-
|
64
|
-
hit.song.must_equal(PreparerClass.new(String.new))
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
@@ -1,18 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class PrivateOptionsTest < MiniTest::Spec # TODO: move me to separate file.
|
4
|
-
representer!(decorator: true) do
|
5
|
-
end
|
6
|
-
|
7
|
-
options = {exclude: "name"}
|
8
|
-
|
9
|
-
it "render: doesn't modify options" do
|
10
|
-
representer.new(nil).to_hash(options)
|
11
|
-
options.must_equal({exclude: "name"})
|
12
|
-
end
|
13
|
-
|
14
|
-
it "parse: doesn't modify options" do
|
15
|
-
representer.new(nil).from_hash(options)
|
16
|
-
options.must_equal({exclude: "name"})
|
17
|
-
end
|
18
|
-
end
|
@@ -1,19 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class ReaderWriterTest < BaseTest
|
4
|
-
representer! do
|
5
|
-
property :name,
|
6
|
-
:writer => lambda { |doc, args| doc["title"] = "#{args[:nr]}) #{name}" },
|
7
|
-
:reader => lambda { |doc, args| self.name = doc["title"].split(") ").last }
|
8
|
-
end
|
9
|
-
|
10
|
-
subject { OpenStruct.new(:name => "Disorder And Disarray").extend(representer) }
|
11
|
-
|
12
|
-
it "uses :writer when rendering" do
|
13
|
-
subject.to_hash(:nr => 14).must_equal({"title" => "14) Disorder And Disarray"})
|
14
|
-
end
|
15
|
-
|
16
|
-
it "uses :reader when parsing" do
|
17
|
-
subject.from_hash({"title" => "15) The Wars End"}).name.must_equal "The Wars End"
|
18
|
-
end
|
19
|
-
end
|
@@ -1,115 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
require 'benchmark'
|
3
|
-
|
4
|
-
Kernel.class_eval do
|
5
|
-
# def respond_to_missing?
|
6
|
-
# raise
|
7
|
-
# end
|
8
|
-
# alias_method :orig_hash, :hash
|
9
|
-
# def hash
|
10
|
-
# puts "hash in #{self.class}"
|
11
|
-
# raise self.inspect if self.class == Symbol
|
12
|
-
# orig_hash
|
13
|
-
# end
|
14
|
-
end
|
15
|
-
Representable.deprecations = false
|
16
|
-
|
17
|
-
SONG_PROPERTIES = 50.times.collect do |i|
|
18
|
-
"song_property_#{i}"
|
19
|
-
end
|
20
|
-
|
21
|
-
|
22
|
-
module SongRepresenter
|
23
|
-
include Representable::JSON
|
24
|
-
|
25
|
-
SONG_PROPERTIES.each { |p| property p }
|
26
|
-
end
|
27
|
-
|
28
|
-
class NestedProperty < Representable::Decorator
|
29
|
-
include Representable::JSON
|
30
|
-
|
31
|
-
SONG_PROPERTIES.each { |p| property p }
|
32
|
-
end
|
33
|
-
|
34
|
-
|
35
|
-
class SongDecorator < Representable::Decorator
|
36
|
-
include Representable::JSON
|
37
|
-
|
38
|
-
SONG_PROPERTIES.each { |p| property p, extend: NestedProperty }
|
39
|
-
end
|
40
|
-
|
41
|
-
class AlbumRepresenter < Representable::Decorator
|
42
|
-
include Representable::JSON
|
43
|
-
|
44
|
-
# collection :songs, extend: SongRepresenter
|
45
|
-
collection :songs, extend: SongDecorator
|
46
|
-
end
|
47
|
-
|
48
|
-
Song = Struct.new(*SONG_PROPERTIES.map(&:to_sym))
|
49
|
-
Album = Struct.new(:songs)
|
50
|
-
|
51
|
-
def random_song
|
52
|
-
Song.new(*SONG_PROPERTIES.collect { |p| Song.new(*SONG_PROPERTIES) })
|
53
|
-
end
|
54
|
-
|
55
|
-
times = []
|
56
|
-
|
57
|
-
3.times.each do
|
58
|
-
album = Album.new(100.times.collect { random_song })
|
59
|
-
|
60
|
-
times << Benchmark.measure do
|
61
|
-
puts "================ next!"
|
62
|
-
AlbumRepresenter.new(album).to_json
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
puts times.join("")
|
67
|
-
|
68
|
-
album = Album.new(100.times.collect { random_song })
|
69
|
-
require 'ruby-prof'
|
70
|
-
RubyProf.start
|
71
|
-
AlbumRepresenter.new(album).to_hash
|
72
|
-
res = RubyProf.stop
|
73
|
-
printer = RubyProf::FlatPrinter.new(res)
|
74
|
-
printer.print(array = [])
|
75
|
-
|
76
|
-
array[0..60].each { |a| puts a }
|
77
|
-
|
78
|
-
# 100 songs, 100 attrs
|
79
|
-
# 0.050000 0.000000 0.050000 ( 0.093157)
|
80
|
-
|
81
|
-
## 100 songs, 1000 attrs
|
82
|
-
# 0.470000 0.010000 0.480000 ( 0.483708)
|
83
|
-
|
84
|
-
|
85
|
-
### without binding cache:
|
86
|
-
# 2.790000 0.030000 2.820000 ( 2.820190)
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
### with extend: on Song, with binding cache>
|
91
|
-
# 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0
|
92
|
-
### without skip?
|
93
|
-
# 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3
|
94
|
-
|
95
|
-
### without :writer
|
96
|
-
# 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2
|
97
|
-
### without :render_filter
|
98
|
-
# 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0
|
99
|
-
###without default_for and skipable?
|
100
|
-
# 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7
|
101
|
-
### without :serialize
|
102
|
-
# 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7
|
103
|
-
### using decorator
|
104
|
-
# 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6
|
105
|
-
### with prepare AFTER representable?
|
106
|
-
# 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3
|
107
|
-
|
108
|
-
|
109
|
-
# representable 2.0
|
110
|
-
# 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0
|
111
|
-
|
112
|
-
# no method missing
|
113
|
-
# 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5
|
114
|
-
# no def_delegator in Definition
|
115
|
-
# 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1
|
@@ -1,21 +0,0 @@
|
|
1
|
-
require "test_helper"
|
2
|
-
|
3
|
-
class RenderNilTest < MiniTest::Spec
|
4
|
-
Song = Struct.new(:title)
|
5
|
-
|
6
|
-
describe "render_nil: true" do
|
7
|
-
representer! do
|
8
|
-
property :title, render_nil: true
|
9
|
-
end
|
10
|
-
|
11
|
-
it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) }
|
12
|
-
end
|
13
|
-
|
14
|
-
describe "with :extend it shouldn't extend nil" do
|
15
|
-
representer! do
|
16
|
-
property :title, render_nil: true, extend: Class
|
17
|
-
end
|
18
|
-
|
19
|
-
it { Song.new.extend(representer).to_hash.must_equal({"title"=>nil}) }
|
20
|
-
end
|
21
|
-
end
|
@@ -1,88 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class RepresentTest < MiniTest::Spec
|
4
|
-
let (:songs) { [song, Song.new("Can't Take Them All")] }
|
5
|
-
let (:song) { Song.new("Days Go By") }
|
6
|
-
|
7
|
-
for_formats(
|
8
|
-
:hash => [Representable::Hash, out=[{"name" => "Days Go By"}, {"name"=>"Can't Take Them All"}], out],
|
9
|
-
# :json => [Representable::JSON, out="[{\"name\":\"Days Go By\"},{\"name\":\"Can't Take Them All\"}]", out],
|
10
|
-
# :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
|
11
|
-
) do |format, mod, output, input|
|
12
|
-
|
13
|
-
# Representer.represents detects collection.
|
14
|
-
describe "Module#to_/from_#{format}" do
|
15
|
-
let (:format) { format }
|
16
|
-
|
17
|
-
let (:representer) {
|
18
|
-
Module.new do
|
19
|
-
include mod
|
20
|
-
property :name
|
21
|
-
|
22
|
-
collection_representer :class => Song # TODOOOOOOOOOOOO: test without Song and fix THIS FUCKINGNoMethodError: undefined method `name=' for {"name"=>"Days Go By"}:Hash ERROR!!!!!!!!!!!!!!!
|
23
|
-
end
|
24
|
-
}
|
25
|
-
|
26
|
-
it { render(representer.represent(songs)).must_equal_document output }
|
27
|
-
it { parse(representer.represent([]), input).must_equal songs }
|
28
|
-
end
|
29
|
-
|
30
|
-
# Decorator.represents detects collection.
|
31
|
-
describe "Decorator#to_/from_#{format}" do
|
32
|
-
let (:format) { format }
|
33
|
-
let (:representer) {
|
34
|
-
Class.new(Representable::Decorator) do
|
35
|
-
include mod
|
36
|
-
property :name
|
37
|
-
|
38
|
-
collection_representer :class => Song
|
39
|
-
end
|
40
|
-
}
|
41
|
-
|
42
|
-
it { render(representer.represent(songs)).must_equal_document output }
|
43
|
-
it("ficken") { parse(representer.represent([]), input).must_equal songs }
|
44
|
-
end
|
45
|
-
end
|
46
|
-
|
47
|
-
|
48
|
-
for_formats(
|
49
|
-
:hash => [Representable::Hash, out={"name" => "Days Go By"}, out],
|
50
|
-
:json => [Representable::JSON, out="{\"name\":\"Days Go By\"}", out],
|
51
|
-
# :xml => [Representable::XML, out="<a><song></song><song></song></a>", out]
|
52
|
-
) do |format, mod, output, input|
|
53
|
-
|
54
|
-
# Representer.represents detects singular.
|
55
|
-
describe "Module#to_/from_#{format}" do
|
56
|
-
let (:format) { format }
|
57
|
-
|
58
|
-
let (:representer) {
|
59
|
-
Module.new do
|
60
|
-
include mod
|
61
|
-
property :name
|
62
|
-
|
63
|
-
collection_representer :class => Song
|
64
|
-
end
|
65
|
-
}
|
66
|
-
|
67
|
-
it { render(representer.represent(song)).must_equal_document output }
|
68
|
-
it { parse(representer.represent(Song.new), input).must_equal song }
|
69
|
-
end
|
70
|
-
|
71
|
-
|
72
|
-
# Decorator.represents detects singular.
|
73
|
-
describe "Decorator#to_/from_#{format}" do
|
74
|
-
let (:format) { format }
|
75
|
-
let (:representer) {
|
76
|
-
Class.new(Representable::Decorator) do
|
77
|
-
include mod
|
78
|
-
property :name
|
79
|
-
|
80
|
-
collection_representer :class => Song
|
81
|
-
end
|
82
|
-
}
|
83
|
-
|
84
|
-
it { render(representer.represent(song)).must_equal_document output }
|
85
|
-
it { parse(representer.represent(Song.new), input).must_equal song }
|
86
|
-
end
|
87
|
-
end
|
88
|
-
end
|
@@ -1,511 +0,0 @@
|
|
1
|
-
require 'test_helper'
|
2
|
-
|
3
|
-
class RepresentableTest < MiniTest::Spec
|
4
|
-
class Band
|
5
|
-
include Representable::Hash
|
6
|
-
property :name
|
7
|
-
attr_accessor :name
|
8
|
-
end
|
9
|
-
|
10
|
-
class PunkBand < Band
|
11
|
-
property :street_cred
|
12
|
-
attr_accessor :street_cred
|
13
|
-
end
|
14
|
-
|
15
|
-
module BandRepresentation
|
16
|
-
include Representable
|
17
|
-
|
18
|
-
property :name
|
19
|
-
end
|
20
|
-
|
21
|
-
module PunkBandRepresentation
|
22
|
-
include Representable
|
23
|
-
include BandRepresentation
|
24
|
-
|
25
|
-
property :street_cred
|
26
|
-
end
|
27
|
-
|
28
|
-
|
29
|
-
describe "#representable_attrs" do
|
30
|
-
describe "in module" do
|
31
|
-
it "allows including the concrete representer module later" do
|
32
|
-
vd = class VD
|
33
|
-
attr_accessor :name, :street_cred
|
34
|
-
include Representable::JSON
|
35
|
-
include PunkBandRepresentation
|
36
|
-
end.new
|
37
|
-
vd.name = "Vention Dention"
|
38
|
-
vd.street_cred = 1
|
39
|
-
assert_json "{\"name\":\"Vention Dention\",\"street_cred\":1}", vd.to_json
|
40
|
-
end
|
41
|
-
|
42
|
-
#it "allows including the concrete representer module only" do
|
43
|
-
# require 'representable/json'
|
44
|
-
# module RockBandRepresentation
|
45
|
-
# include Representable::JSON
|
46
|
-
# property :name
|
47
|
-
# end
|
48
|
-
# vd = class VH
|
49
|
-
# include RockBandRepresentation
|
50
|
-
# end.new
|
51
|
-
# vd.name = "Van Halen"
|
52
|
-
# assert_equal "{\"name\":\"Van Halen\"}", vd.to_json
|
53
|
-
#end
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
|
58
|
-
describe "inheritance" do
|
59
|
-
class CoverSong < OpenStruct
|
60
|
-
end
|
61
|
-
|
62
|
-
module SongRepresenter
|
63
|
-
include Representable::Hash
|
64
|
-
property :name
|
65
|
-
end
|
66
|
-
|
67
|
-
module CoverSongRepresenter
|
68
|
-
include Representable::Hash
|
69
|
-
include SongRepresenter
|
70
|
-
property :by
|
71
|
-
end
|
72
|
-
|
73
|
-
it "merges properties from all ancestors" do
|
74
|
-
props = {"name"=>"The Brews", "by"=>"Nofx"}
|
75
|
-
assert_equal(props, CoverSong.new(props).extend(CoverSongRepresenter).to_hash)
|
76
|
-
end
|
77
|
-
|
78
|
-
it "allows mixing in multiple representers" do
|
79
|
-
require 'representable/json'
|
80
|
-
require 'representable/xml'
|
81
|
-
class Bodyjar
|
82
|
-
include Representable::XML
|
83
|
-
include Representable::JSON
|
84
|
-
include PunkBandRepresentation
|
85
|
-
|
86
|
-
self.representation_wrap = "band"
|
87
|
-
attr_accessor :name, :street_cred
|
88
|
-
end
|
89
|
-
|
90
|
-
band = Bodyjar.new
|
91
|
-
band.name = "Bodyjar"
|
92
|
-
|
93
|
-
assert_json "{\"band\":{\"name\":\"Bodyjar\"}}", band.to_json
|
94
|
-
assert_xml_equal "<band><name>Bodyjar</name></band>", band.to_xml
|
95
|
-
end
|
96
|
-
|
97
|
-
it "allows extending with different representers subsequentially" do
|
98
|
-
module SongXmlRepresenter
|
99
|
-
include Representable::XML
|
100
|
-
property :name, :as => "name", :attribute => true
|
101
|
-
end
|
102
|
-
|
103
|
-
module SongJsonRepresenter
|
104
|
-
include Representable::JSON
|
105
|
-
property :name
|
106
|
-
end
|
107
|
-
|
108
|
-
@song = Song.new("Days Go By")
|
109
|
-
assert_xml_equal "<song name=\"Days Go By\"/>", @song.extend(SongXmlRepresenter).to_xml
|
110
|
-
assert_json "{\"name\":\"Days Go By\"}", @song.extend(SongJsonRepresenter).to_json
|
111
|
-
end
|
112
|
-
|
113
|
-
|
114
|
-
# test if we call super in
|
115
|
-
# ::inherited
|
116
|
-
# ::included
|
117
|
-
# ::extended
|
118
|
-
module Representer
|
119
|
-
include Representable # overrides ::inherited.
|
120
|
-
end
|
121
|
-
|
122
|
-
class BaseClass
|
123
|
-
def self.inherited(subclass)
|
124
|
-
super
|
125
|
-
subclass.instance_eval { def other; end }
|
126
|
-
end
|
127
|
-
|
128
|
-
include Representable # overrides ::inherited.
|
129
|
-
include Representer
|
130
|
-
end
|
131
|
-
|
132
|
-
class SubClass < BaseClass # triggers Representable::inherited, then OtherModule::inherited.
|
133
|
-
end
|
134
|
-
|
135
|
-
# test ::inherited.
|
136
|
-
it do
|
137
|
-
BaseClass.respond_to?(:other).must_equal false
|
138
|
-
SubClass.respond_to?(:other).must_equal true
|
139
|
-
end
|
140
|
-
|
141
|
-
module DifferentIncluded
|
142
|
-
def included(includer)
|
143
|
-
includer.instance_eval { def different; end }
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
module CombinedIncluded
|
148
|
-
extend DifferentIncluded # defines ::included.
|
149
|
-
include Representable # overrides ::included.
|
150
|
-
end
|
151
|
-
|
152
|
-
class IncludingClass
|
153
|
-
include Representable
|
154
|
-
include CombinedIncluded
|
155
|
-
end
|
156
|
-
|
157
|
-
# test ::included.
|
158
|
-
it do
|
159
|
-
IncludingClass.respond_to?(:representable_attrs) # from Representable
|
160
|
-
IncludingClass.respond_to?(:different)
|
161
|
-
end
|
162
|
-
end
|
163
|
-
|
164
|
-
|
165
|
-
describe "#property" do
|
166
|
-
it "doesn't modify options hash" do
|
167
|
-
options = {}
|
168
|
-
representer.property(:title, options)
|
169
|
-
options.must_equal({})
|
170
|
-
end
|
171
|
-
|
172
|
-
representer! {}
|
173
|
-
|
174
|
-
it "returns the Definition instance" do
|
175
|
-
representer.property(:name).must_be_kind_of Representable::Definition
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
describe "#collection" do
|
180
|
-
class RockBand < Band
|
181
|
-
collection :albums
|
182
|
-
end
|
183
|
-
|
184
|
-
it "creates correct Definition" do
|
185
|
-
assert_equal "albums", RockBand.representable_attrs.get(:albums).name
|
186
|
-
assert RockBand.representable_attrs.get(:albums).array?
|
187
|
-
end
|
188
|
-
end
|
189
|
-
|
190
|
-
describe "#hash" do
|
191
|
-
it "also responds to the original method" do
|
192
|
-
assert_kind_of Integer, BandRepresentation.hash
|
193
|
-
end
|
194
|
-
end
|
195
|
-
|
196
|
-
class Hometown
|
197
|
-
attr_accessor :name
|
198
|
-
end
|
199
|
-
|
200
|
-
module HometownRepresentable
|
201
|
-
include Representable::JSON
|
202
|
-
property :name
|
203
|
-
end
|
204
|
-
|
205
|
-
# DISCUSS: i don't like the JSON requirement here, what about some generic test module?
|
206
|
-
class PopBand
|
207
|
-
include Representable::JSON
|
208
|
-
property :name
|
209
|
-
property :groupies
|
210
|
-
property :hometown, class: Hometown, extend: HometownRepresentable
|
211
|
-
attr_accessor :name, :groupies, :hometown
|
212
|
-
end
|
213
|
-
|
214
|
-
describe "#update_properties_from" do
|
215
|
-
before do
|
216
|
-
@band = PopBand.new
|
217
|
-
end
|
218
|
-
|
219
|
-
it "copies values from document to object" do
|
220
|
-
@band.from_hash({"name"=>"No One's Choice", "groupies"=>2})
|
221
|
-
assert_equal "No One's Choice", @band.name
|
222
|
-
assert_equal 2, @band.groupies
|
223
|
-
end
|
224
|
-
|
225
|
-
it "ignores non-writeable properties" do
|
226
|
-
@band = Class.new(Band) { property :name; collection :founders, :writeable => false; attr_accessor :founders }.new
|
227
|
-
@band.from_hash("name" => "Iron Maiden", "groupies" => 2, "founders" => ["Steve Harris"])
|
228
|
-
assert_equal "Iron Maiden", @band.name
|
229
|
-
assert_equal nil, @band.founders
|
230
|
-
end
|
231
|
-
|
232
|
-
it "always returns the represented" do
|
233
|
-
assert_equal @band, @band.from_hash({"name"=>"Nofx"})
|
234
|
-
end
|
235
|
-
|
236
|
-
it "includes false attributes" do
|
237
|
-
@band.from_hash({"groupies"=>false})
|
238
|
-
assert_equal false, @band.groupies
|
239
|
-
end
|
240
|
-
|
241
|
-
it "ignores properties not present in the incoming document" do
|
242
|
-
@band.instance_eval do
|
243
|
-
def name=(*); raise "I should never be called!"; end
|
244
|
-
end
|
245
|
-
@band.from_hash({})
|
246
|
-
end
|
247
|
-
|
248
|
-
|
249
|
-
# FIXME: do we need this test with XML _and_ JSON?
|
250
|
-
it "ignores (no-default) properties not present in the incoming document" do
|
251
|
-
{ Representable::Hash => [:from_hash, {}],
|
252
|
-
Representable::XML => [:from_xml, xml(%{<band/>}).to_s]
|
253
|
-
}.each do |format, config|
|
254
|
-
nested_repr = Module.new do # this module is never applied. # FIXME: can we make that a simpler test?
|
255
|
-
include format
|
256
|
-
property :created_at
|
257
|
-
end
|
258
|
-
|
259
|
-
repr = Module.new do
|
260
|
-
include format
|
261
|
-
property :name, :class => Object, :extend => nested_repr
|
262
|
-
end
|
263
|
-
|
264
|
-
@band = Band.new.extend(repr)
|
265
|
-
@band.send(config.first, config.last)
|
266
|
-
assert_equal nil, @band.name, "Failed in #{format}"
|
267
|
-
end
|
268
|
-
end
|
269
|
-
|
270
|
-
describe "passing options" do
|
271
|
-
module TrackRepresenter
|
272
|
-
include Representable::Hash
|
273
|
-
|
274
|
-
end
|
275
|
-
|
276
|
-
representer! do
|
277
|
-
property :track, class: OpenStruct do
|
278
|
-
property :nr
|
279
|
-
|
280
|
-
property :length, class: OpenStruct do
|
281
|
-
def to_hash(options)
|
282
|
-
{seconds: options[:user_options][:nr]}
|
283
|
-
end
|
284
|
-
|
285
|
-
def from_hash(hash, options)
|
286
|
-
super.tap do
|
287
|
-
self.seconds = options[:user_options][:nr]
|
288
|
-
end
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def to_hash(options)
|
293
|
-
super.merge({"nr" => options[:user_options][:nr]})
|
294
|
-
end
|
295
|
-
|
296
|
-
def from_hash(data, options)
|
297
|
-
super.tap do
|
298
|
-
self.nr = options[:user_options][:nr]
|
299
|
-
end
|
300
|
-
end
|
301
|
-
end
|
302
|
-
end
|
303
|
-
|
304
|
-
it "#to_hash propagates to nested objects" do
|
305
|
-
OpenStruct.new(track: OpenStruct.new(nr: 1, length: OpenStruct.new(seconds: nil))).extend(representer).extend(Representable::Debug).
|
306
|
-
to_hash(nr: 9).must_equal({"track"=>{"nr"=>9, "length"=>{seconds: 9}}})
|
307
|
-
end
|
308
|
-
|
309
|
-
it "#from_hash propagates to nested objects" do
|
310
|
-
song = OpenStruct.new.extend(representer).from_hash({"track"=>{"nr" => "replace me", "length"=>{"seconds"=>"replacing"}}}, :nr => 9)
|
311
|
-
song.track.nr.must_equal 9
|
312
|
-
song.track.length.seconds.must_equal 9
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
316
|
-
|
317
|
-
describe "#create_representation_with" do
|
318
|
-
before do
|
319
|
-
@band = PopBand.new
|
320
|
-
@band.name = "No One's Choice"
|
321
|
-
@band.groupies = 2
|
322
|
-
end
|
323
|
-
|
324
|
-
it "compiles document from properties in object" do
|
325
|
-
assert_equal({"name"=>"No One's Choice", "groupies"=>2}, @band.to_hash)
|
326
|
-
end
|
327
|
-
|
328
|
-
it "ignores non-readable properties" do
|
329
|
-
@band = Class.new(Band) { property :name; collection :founder_ids, :readable => false; attr_accessor :founder_ids }.new
|
330
|
-
@band.name = "Iron Maiden"
|
331
|
-
@band.founder_ids = [1,2,3]
|
332
|
-
|
333
|
-
hash = @band.to_hash
|
334
|
-
assert_equal({"name" => "Iron Maiden"}, hash)
|
335
|
-
end
|
336
|
-
|
337
|
-
it "does not write nil attributes" do
|
338
|
-
@band.groupies = nil
|
339
|
-
assert_equal({"name"=>"No One's Choice"}, @band.to_hash)
|
340
|
-
end
|
341
|
-
|
342
|
-
it "writes false attributes" do
|
343
|
-
@band.groupies = false
|
344
|
-
assert_equal({"name"=>"No One's Choice","groupies"=>false}, @band.to_hash)
|
345
|
-
end
|
346
|
-
end
|
347
|
-
|
348
|
-
|
349
|
-
describe ":extend and :class" do
|
350
|
-
module UpcaseRepresenter
|
351
|
-
include Representable
|
352
|
-
def to_hash(*); upcase; end
|
353
|
-
def from_hash(hsh, *args); replace hsh.upcase; end # DISCUSS: from_hash must return self.
|
354
|
-
end
|
355
|
-
module DowncaseRepresenter
|
356
|
-
include Representable
|
357
|
-
def to_hash(*); downcase; end
|
358
|
-
def from_hash(hsh, *args); replace hsh.downcase; end
|
359
|
-
end
|
360
|
-
class UpcaseString < String; end
|
361
|
-
|
362
|
-
|
363
|
-
describe "lambda blocks" do
|
364
|
-
representer! do
|
365
|
-
property :name, :extend => lambda { |name, *| compute_representer(name) }
|
366
|
-
end
|
367
|
-
|
368
|
-
it "executes lambda in represented instance context" do
|
369
|
-
Song.new("Carnage").instance_eval do
|
370
|
-
def compute_representer(name)
|
371
|
-
UpcaseRepresenter
|
372
|
-
end
|
373
|
-
self
|
374
|
-
end.extend(representer).to_hash.must_equal({"name" => "CARNAGE"})
|
375
|
-
end
|
376
|
-
end
|
377
|
-
|
378
|
-
describe ":instance" do
|
379
|
-
obj = String.new("Fate")
|
380
|
-
mod = Module.new { include Representable; def from_hash(*); self; end }
|
381
|
-
representer! do
|
382
|
-
property :name, :extend => mod, :instance => lambda { |*| obj }
|
383
|
-
end
|
384
|
-
|
385
|
-
it "uses object from :instance but still extends it" do
|
386
|
-
song = Song.new.extend(representer).from_hash("name" => "Eric's Had A Bad Day")
|
387
|
-
song.name.must_equal obj
|
388
|
-
song.name.must_be_kind_of mod
|
389
|
-
end
|
390
|
-
end
|
391
|
-
|
392
|
-
describe "property with :extend" do
|
393
|
-
representer! do
|
394
|
-
property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
|
395
|
-
end
|
396
|
-
|
397
|
-
it "uses lambda when rendering" do
|
398
|
-
assert_equal({"name" => "you make me thick"}, Song.new("You Make Me Thick").extend(representer).to_hash )
|
399
|
-
assert_equal({"name" => "STEPSTRANGER"}, Song.new(UpcaseString.new "Stepstranger").extend(representer).to_hash )
|
400
|
-
end
|
401
|
-
|
402
|
-
it "uses lambda when parsing" do
|
403
|
-
Song.new.extend(representer).from_hash({"name" => "You Make Me Thick"}).name.must_equal "you make me thick"
|
404
|
-
Song.new.extend(representer).from_hash({"name" => "Stepstranger"}).name.must_equal "stepstranger" # DISCUSS: we compare "".is_a?(UpcaseString)
|
405
|
-
end
|
406
|
-
|
407
|
-
describe "with :class lambda" do
|
408
|
-
representer! do
|
409
|
-
property :name, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
|
410
|
-
:class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
|
411
|
-
end
|
412
|
-
|
413
|
-
it "creates instance from :class lambda when parsing" do
|
414
|
-
song = OpenStruct.new.extend(representer).from_hash({"name" => "Quitters Never Win"})
|
415
|
-
song.name.must_be_kind_of UpcaseString
|
416
|
-
song.name.must_equal "QUITTERS NEVER WIN"
|
417
|
-
|
418
|
-
song = OpenStruct.new.extend(representer).from_hash({"name" => "Still Failing?"})
|
419
|
-
song.name.must_be_kind_of String
|
420
|
-
song.name.must_equal "still failing?"
|
421
|
-
end
|
422
|
-
end
|
423
|
-
end
|
424
|
-
|
425
|
-
|
426
|
-
describe "collection with :extend" do
|
427
|
-
representer! do
|
428
|
-
collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter }, :class => String
|
429
|
-
end
|
430
|
-
|
431
|
-
it "uses lambda for each item when rendering" do
|
432
|
-
Album.new([UpcaseString.new("Dean Martin"), "Charlie Still Smirks"]).extend(representer).to_hash.must_equal("songs"=>["DEAN MARTIN", "charlie still smirks"])
|
433
|
-
end
|
434
|
-
|
435
|
-
it "uses lambda for each item when parsing" do
|
436
|
-
album = Album.new.extend(representer).from_hash("songs"=>["DEAN MARTIN", "charlie still smirks"])
|
437
|
-
album.songs.must_equal ["dean martin", "charlie still smirks"] # DISCUSS: we compare "".is_a?(UpcaseString)
|
438
|
-
end
|
439
|
-
|
440
|
-
describe "with :class lambda" do
|
441
|
-
representer! do
|
442
|
-
collection :songs, :extend => lambda { |name, *| name.is_a?(UpcaseString) ? UpcaseRepresenter : DowncaseRepresenter },
|
443
|
-
:class => lambda { |fragment, *| fragment == "Still Failing?" ? String : UpcaseString }
|
444
|
-
end
|
445
|
-
|
446
|
-
it "creates instance from :class lambda for each item when parsing" do
|
447
|
-
album = Album.new.extend(representer).from_hash("songs"=>["Still Failing?", "charlie still smirks"])
|
448
|
-
album.songs.must_equal ["still failing?", "CHARLIE STILL SMIRKS"]
|
449
|
-
end
|
450
|
-
end
|
451
|
-
end
|
452
|
-
|
453
|
-
describe ":decorator" do
|
454
|
-
let (:extend_rpr) { Module.new { include Representable::Hash; collection :songs, :extend => SongRepresenter } }
|
455
|
-
let (:decorator_rpr) { Module.new { include Representable::Hash; collection :songs, :decorator => SongRepresenter } }
|
456
|
-
let (:songs) { [Song.new("Bloody Mary")] }
|
457
|
-
|
458
|
-
it "is aliased to :extend" do
|
459
|
-
Album.new(songs).extend(extend_rpr).to_hash.must_equal Album.new(songs).extend(decorator_rpr).to_hash
|
460
|
-
end
|
461
|
-
end
|
462
|
-
|
463
|
-
describe ":binding" do
|
464
|
-
representer! do
|
465
|
-
class MyBinding < Representable::Binding
|
466
|
-
def write(doc, *args)
|
467
|
-
doc[:title] = represented.title
|
468
|
-
end
|
469
|
-
end
|
470
|
-
property :title, :binding => lambda { |*args| MyBinding.new(*args) }
|
471
|
-
end
|
472
|
-
|
473
|
-
it "uses the specified binding instance" do
|
474
|
-
OpenStruct.new(:title => "Affliction").extend(representer).to_hash.must_equal({:title => "Affliction"})
|
475
|
-
end
|
476
|
-
end
|
477
|
-
|
478
|
-
|
479
|
-
# TODO: Move to global place since it's used twice.
|
480
|
-
class SongRepresentation < Representable::Decorator
|
481
|
-
include Representable::JSON
|
482
|
-
property :name
|
483
|
-
end
|
484
|
-
class AlbumRepresentation < Representable::Decorator
|
485
|
-
include Representable::JSON
|
486
|
-
|
487
|
-
collection :songs, :class => Song, :extend => SongRepresentation
|
488
|
-
end
|
489
|
-
|
490
|
-
describe "::prepare" do
|
491
|
-
let (:song) { Song.new("Still Friends In The End") }
|
492
|
-
let (:album) { Album.new([song]) }
|
493
|
-
|
494
|
-
describe "module including Representable" do
|
495
|
-
it "uses :extend strategy" do
|
496
|
-
album_rpr = Module.new { include Representable::Hash; collection :songs, :class => Song, :extend => SongRepresenter}
|
497
|
-
|
498
|
-
album_rpr.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
|
499
|
-
album.must_respond_to :to_hash
|
500
|
-
end
|
501
|
-
end
|
502
|
-
|
503
|
-
describe "Decorator subclass" do
|
504
|
-
it "uses :decorate strategy" do
|
505
|
-
AlbumRepresentation.prepare(album).to_hash.must_equal({"songs"=>[{"name"=>"Still Friends In The End"}]})
|
506
|
-
album.wont_respond_to :to_hash
|
507
|
-
end
|
508
|
-
end
|
509
|
-
end
|
510
|
-
end
|
511
|
-
end
|