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