representable 2.4.1 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +7 -12
  3. data/CHANGES.md +6 -27
  4. data/README.md +28 -1326
  5. data/lib/representable.rb +4 -14
  6. data/lib/representable/binding.rb +3 -7
  7. data/lib/representable/definition.rb +1 -2
  8. data/lib/representable/populator.rb +35 -0
  9. data/lib/representable/version.rb +1 -1
  10. data/test/definition_test.rb +0 -7
  11. data/test/exec_context_test.rb +2 -2
  12. data/test/instance_test.rb +0 -19
  13. data/test/realistic_benchmark.rb +0 -13
  14. data/test/representable_test.rb +0 -16
  15. data/test/skip_test.rb +1 -1
  16. data/test/test_helper.rb +0 -2
  17. metadata +3 -65
  18. data/lib/representable/deprecations.rb +0 -127
  19. data/lib/representable/parse_strategies.rb +0 -93
  20. data/test-with-deprecations/as_test.rb +0 -65
  21. data/test-with-deprecations/benchmarking.rb +0 -83
  22. data/test-with-deprecations/binding_test.rb +0 -46
  23. data/test-with-deprecations/blaaaaaaaa_test.rb +0 -69
  24. data/test-with-deprecations/cached_test.rb +0 -147
  25. data/test-with-deprecations/class_test.rb +0 -119
  26. data/test-with-deprecations/coercion_test.rb +0 -52
  27. data/test-with-deprecations/config/inherit_test.rb +0 -135
  28. data/test-with-deprecations/config_test.rb +0 -122
  29. data/test-with-deprecations/decorator_scope_test.rb +0 -28
  30. data/test-with-deprecations/decorator_test.rb +0 -96
  31. data/test-with-deprecations/default_test.rb +0 -34
  32. data/test-with-deprecations/defaults_options_test.rb +0 -93
  33. data/test-with-deprecations/definition_test.rb +0 -264
  34. data/test-with-deprecations/example.rb +0 -310
  35. data/test-with-deprecations/examples/object.rb +0 -31
  36. data/test-with-deprecations/exec_context_test.rb +0 -93
  37. data/test-with-deprecations/features_test.rb +0 -70
  38. data/test-with-deprecations/filter_test.rb +0 -57
  39. data/test-with-deprecations/for_collection_test.rb +0 -74
  40. data/test-with-deprecations/generic_test.rb +0 -116
  41. data/test-with-deprecations/getter_setter_test.rb +0 -21
  42. data/test-with-deprecations/hash_bindings_test.rb +0 -87
  43. data/test-with-deprecations/hash_test.rb +0 -160
  44. data/test-with-deprecations/heritage_test.rb +0 -62
  45. data/test-with-deprecations/if_test.rb +0 -79
  46. data/test-with-deprecations/include_exclude_test.rb +0 -88
  47. data/test-with-deprecations/inherit_test.rb +0 -159
  48. data/test-with-deprecations/inline_test.rb +0 -272
  49. data/test-with-deprecations/instance_test.rb +0 -266
  50. data/test-with-deprecations/is_representable_test.rb +0 -77
  51. data/test-with-deprecations/json_test.rb +0 -355
  52. data/test-with-deprecations/lonely_test.rb +0 -239
  53. data/test-with-deprecations/mongoid_test.rb +0 -31
  54. data/test-with-deprecations/nested_test.rb +0 -115
  55. data/test-with-deprecations/object_test.rb +0 -60
  56. data/test-with-deprecations/parse_pipeline_test.rb +0 -64
  57. data/test-with-deprecations/parse_strategy_test.rb +0 -279
  58. data/test-with-deprecations/pass_options_test.rb +0 -27
  59. data/test-with-deprecations/pipeline_test.rb +0 -277
  60. data/test-with-deprecations/populator_test.rb +0 -105
  61. data/test-with-deprecations/prepare_test.rb +0 -67
  62. data/test-with-deprecations/private_options_test.rb +0 -18
  63. data/test-with-deprecations/reader_writer_test.rb +0 -19
  64. data/test-with-deprecations/realistic_benchmark.rb +0 -115
  65. data/test-with-deprecations/render_nil_test.rb +0 -21
  66. data/test-with-deprecations/represent_test.rb +0 -88
  67. data/test-with-deprecations/representable_test.rb +0 -511
  68. data/test-with-deprecations/schema_test.rb +0 -148
  69. data/test-with-deprecations/serialize_deserialize_test.rb +0 -33
  70. data/test-with-deprecations/skip_test.rb +0 -81
  71. data/test-with-deprecations/stringify_hash_test.rb +0 -41
  72. data/test-with-deprecations/test_helper.rb +0 -135
  73. data/test-with-deprecations/test_helper_test.rb +0 -25
  74. data/test-with-deprecations/uncategorized_test.rb +0 -67
  75. data/test-with-deprecations/user_options_test.rb +0 -15
  76. data/test-with-deprecations/wrap_test.rb +0 -152
  77. data/test-with-deprecations/xml_bindings_test.rb +0 -62
  78. data/test-with-deprecations/xml_test.rb +0 -503
  79. data/test-with-deprecations/yaml_test.rb +0 -162
  80. 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