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.
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