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,93 +0,0 @@
1
- module Representable
2
- class Populator
3
- FindOrInstantiate = ->(input, options) {
4
- binding = options[:binding]
5
-
6
- object_class = binding[:class].(input, options)
7
- object = object_class.find_by(id: input["id"]) || object_class.new
8
- if options[:binding].array?
9
- # represented.songs[i] = model
10
- options[:represented].send(binding.getter)[options[:index]] = object
11
- else
12
- # represented.song = model
13
- options[:represented].send(binding.setter, object)
14
- end
15
-
16
- object
17
- }
18
-
19
- # pipeline: [StopOnExcluded, AssignName, ReadFragment, StopOnNotFound, OverwriteOnNil, AssignFragment, #<Representable::Function::CreateObject:0x9805a44>, #<Representable::Function::Decorate:0x9805a1c>, Deserialize, Set]
20
-
21
- def self.apply!(options)
22
- return unless populator = options[:populator]
23
-
24
- options[:parse_pipeline] = ->(input, options) do
25
- pipeline = Pipeline[*parse_functions] # TODO: AssignFragment
26
- pipeline = Pipeline::Insert.(pipeline, SetValue, delete: true) # remove the setter function.
27
- pipeline = Pipeline::Insert.(pipeline, populator, replace: CreateObject::Populator) # let the actual populator do the job.
28
- # puts pipeline.extend(Representable::Pipeline::Debug).inspect
29
- pipeline
30
- end
31
- end
32
- end
33
-
34
- FindOrInstantiate = Populator::FindOrInstantiate
35
-
36
- # Parse strategies are just a combination of representable's options. They save you from memoizing the
37
- # necessary parameters.
38
- #
39
- # Feel free to contribute your strategy if you think it's worth sharing!
40
- class ParseStrategy
41
- def self.apply!(options)
42
- return unless strategy = options[:parse_strategy]
43
-
44
- warn "[Representable] :parse_strategy is deprecated. Please use a populator."
45
-
46
- strategy = :proc if strategy.is_a?(::Proc)
47
-
48
- parse_strategies[strategy].apply!(name, options)
49
- end
50
-
51
- def self.parse_strategies
52
- {
53
- :sync => Sync,
54
- :find_or_instantiate => FindOrInstantiate,
55
- :proc => Proc
56
- }
57
- end
58
-
59
-
60
- # Using a lambda as parse_strategy does not set the parsed property for you.
61
- class Proc
62
- def self.apply!(name, options)
63
- options[:setter] = lambda { |*| }
64
- options[:pass_options] = true
65
- options[:instance] = options[:parse_strategy]
66
- end
67
- end
68
-
69
-
70
- class Sync
71
- def self.apply!(name, options)
72
- options[:setter] = lambda { |*args| }
73
- options[:pass_options] = true
74
- options[:instance] = options[:collection] ?
75
- lambda { |options| options[:binding].get(options)[options[:index]] } :
76
- lambda { |options| options[:binding].get(options) }
77
- end
78
- end
79
-
80
-
81
- # replaces current collection.
82
- class FindOrInstantiate
83
- def self.apply!(name, options)
84
- options[:pass_options] = true
85
- options[:instance] = lambda { |options|
86
- object_class = options[:binding][:class].evaluate(self, options)
87
-
88
- object_class.find_by({id: options[:fragment]["id"]}) or object_class.new
89
- }
90
- end
91
- end
92
- end
93
- end
@@ -1,65 +0,0 @@
1
- require 'test_helper'
2
-
3
- class AsTest < MiniTest::Spec
4
- for_formats(
5
- :hash => [Representable::Hash, {"title" => "Wie Es Geht"}, {"title" => "Revolution"}],
6
- # :xml => [Representable::XML, "<open_struct>\n <song>\n <name>Alive</name>\n </song>\n</open_struct>", "<open_struct><song><name>You've Taken Everything</name></song>/open_struct>"],
7
- # :yaml => [Representable::YAML, "---\nsong:\n name: Alive\n", "---\nsong:\n name: You've Taken Everything\n"],
8
- ) do |format, mod, input, output|
9
-
10
- let (:song) { representer.prepare(Song.new("Revolution")) }
11
- let (:format) { format }
12
-
13
-
14
- describe "as: with :symbol" do
15
- representer!(:module => mod) do
16
- property :name, :as => :title
17
- end
18
-
19
- it { render(song).must_equal_document output }
20
- it { parse(song, input).name.must_equal "Wie Es Geht" }
21
- end
22
-
23
-
24
- describe "as: with lambda" do
25
- representer!(:module => mod) do
26
- property :name, :as => lambda { |*| "#{self.class}" }
27
- end
28
-
29
- it { render(song).must_equal_document({"Song" => "Revolution"}) }
30
- it { parse(song, {"Song" => "Wie Es Geht"}).name.must_equal "Wie Es Geht" }
31
- end
32
-
33
-
34
- describe "lambda arguments" do
35
- representer! do
36
- property :name, :as => lambda { |*args| args.inspect }
37
- end
38
-
39
- it { render(song, :volume => 1).must_equal_document({"[{:volume=>1}]" => "Revolution"}) }
40
- it { parse(song, {"[{:volume=>1}]" => "Wie Es Geht"}, :volume => 1).name.must_equal "Wie Es Geht" }
41
- end
42
- end
43
- end
44
-
45
-
46
- # hash: to_hash(wrap: ) is representation_wrap
47
-
48
- class AsXmlTest < MiniTest::Spec
49
- Band = Struct.new(:name, :label)
50
- Album = Struct.new(:band)
51
- Label = Struct.new(:name)
52
-
53
- representer!(module: Representable::XML, decorator: true) do
54
- self.representation_wrap = :album
55
- property :band, as: :combo do
56
- self.representation_wrap = :band
57
- property :name
58
- end
59
- end
60
-
61
- it do
62
- skip
63
- representer.new(Album.new(Band.new("Offspring"))).to_xml.must_equal ""
64
- end
65
- end
@@ -1,83 +0,0 @@
1
- require 'test_helper'
2
- require 'benchmark'
3
-
4
- SONG_PROPERTIES = 1000.times.collect do |i|
5
- "property_#{i}"
6
- end
7
-
8
-
9
- module SongRepresenter
10
- include Representable::JSON
11
-
12
- SONG_PROPERTIES.each { |p| property p }
13
- end
14
-
15
- class SongDecorator < Representable::Decorator
16
- include Representable::JSON
17
-
18
- SONG_PROPERTIES.each { |p| property p }
19
- end
20
-
21
- module AlbumRepresenter
22
- include Representable::JSON
23
-
24
- # collection :songs, extend: SongRepresenter
25
- collection :songs, extend: SongDecorator
26
- end
27
-
28
- def random_song
29
- attrs = Hash[SONG_PROPERTIES.collect { |n| [n,n] }]
30
- OpenStruct.new(attrs)
31
- end
32
-
33
- times = []
34
-
35
- 3.times.each do
36
- album = OpenStruct.new(songs: 100.times.collect { random_song })
37
-
38
- times << Benchmark.measure do
39
- puts "================ next!"
40
- album.extend(AlbumRepresenter).to_json
41
- end
42
- end
43
-
44
- puts times.join("")
45
-
46
- # 100 songs, 100 attrs
47
- # 0.050000 0.000000 0.050000 ( 0.093157)
48
-
49
- ## 100 songs, 1000 attrs
50
- # 0.470000 0.010000 0.480000 ( 0.483708)
51
-
52
-
53
- ### without binding cache:
54
- # 2.790000 0.030000 2.820000 ( 2.820190)
55
-
56
-
57
-
58
- ### with extend: on Song, with binding cache>
59
- # 2.490000 0.030000 2.520000 ( 2.517433) 2.4-3.0
60
- ### without skip?
61
- # 2.030000 0.020000 2.050000 ( 2.050796) 2.1-2.3
62
-
63
- ### without :writer
64
- # 2.270000 0.010000 2.280000 ( 2.284530 1.9-2.2
65
- ### without :render_filter
66
- # 2.020000 0.000000 2.020000 ( 2.030234) 1.5-2.0
67
- ###without default_for and skipable?
68
- # 1.730000 0.010000 1.740000 ( 1.735597 1.4-1.7
69
- ### without :serialize
70
- # 1.780000 0.010000 1.790000 ( 1.786791) 1.4-1.7
71
- ### using decorator
72
- # 1.400000 0.030000 1.430000 ( 1.434206) 1.4-1.6
73
- ### with prepare AFTER representable?
74
- # 1.330000 0.010000 1.340000 ( 1.335900) 1.1-1.3
75
-
76
-
77
- # representable 2.0
78
- # 3.000000 0.020000 3.020000 ( 3.013031) 2.7-3.0
79
-
80
- # no method missing
81
- # 2.280000 0.030000 2.310000 ( 2.313522) 2.2-2.5
82
- # no def_delegator in Definition
83
- # 2.130000 0.010000 2.140000 ( 2.136115) 1.7-2.1
@@ -1,46 +0,0 @@
1
- require 'test_helper'
2
-
3
- class BindingTest < MiniTest::Spec
4
- Binding = Representable::Binding
5
- let (:render_nil_definition) { Representable::Definition.new(:song, :render_nil => true) }
6
-
7
- describe "#skipable_empty_value?" do
8
- let (:binding) { Binding.new(render_nil_definition) }
9
-
10
- # don't skip when present.
11
- it { binding.skipable_empty_value?("Disconnect, Disconnect").must_equal false }
12
-
13
- # don't skip when it's nil and render_nil: true
14
- it { binding.skipable_empty_value?(nil).must_equal false }
15
-
16
- # skip when nil and :render_nil undefined.
17
- it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?(nil).must_equal true }
18
-
19
- # don't skip when nil and :render_nil undefined.
20
- it { Binding.new(Representable::Definition.new(:song)).skipable_empty_value?("Fatal Flu").must_equal false }
21
- end
22
-
23
-
24
- describe "#default_for" do
25
- let (:definition) { Representable::Definition.new(:song, :default => "Insider") }
26
- let (:binding) { Binding.new(definition) }
27
-
28
- # return value when value present.
29
- it { binding.default_for("Black And Blue").must_equal "Black And Blue" }
30
-
31
- # return false when value false.
32
- it { binding.default_for(false).must_equal false }
33
-
34
- # return default when value nil.
35
- it { binding.default_for(nil).must_equal "Insider" }
36
-
37
- # return nil when value nil and render_nil: true.
38
- it { Binding.new(render_nil_definition).default_for(nil).must_equal nil }
39
-
40
- # return nil when value nil and render_nil: true, even when :default is set" do
41
- it { Binding.new(Representable::Definition.new(:song, :render_nil => true, :default => "The Quest")).default_for(nil).must_equal nil }
42
-
43
- # return nil if no :default
44
- it { Binding.new(Representable::Definition.new(:song)).default_for(nil).must_equal nil }
45
- end
46
- end
@@ -1,69 +0,0 @@
1
- require 'test_helper'
2
-
3
-
4
-
5
-
6
- # Include Inherit Module And Decorator Test
7
- class SchemaTest < MiniTest::Spec
8
- module Genre
9
- include Representable
10
- property :genre
11
- end
12
-
13
- module LinkFeature
14
- def self.included(base)
15
- base.extend(Link)
16
- end
17
-
18
- module Link
19
- def link
20
- end
21
- end
22
- end
23
-
24
-
25
- module Module
26
- include Representable::Hash
27
- feature LinkFeature
28
-
29
- property :title
30
- property :label do # extend: LabelModule
31
- property :name
32
- link # feature
33
-
34
- property :location do
35
- property :city
36
- link # feature.
37
- end
38
- end
39
-
40
- property :album, :extend => lambda { raise "don't manifest me!" } # this is not an inline decorator, don't manifest it.
41
-
42
-
43
- include Genre # Schema::Included::included is called!
44
- end
45
-
46
-
47
- class InheritDecorator < Representable::Decorator
48
- include Representable::Hash
49
-
50
- include Module
51
-
52
- property :label, inherit: true do # decorator.rb:27:in `initialize': superclass must be a Class (Module given)
53
- property :city
54
-
55
- property :location, :inherit => true do
56
- property :city
57
- end
58
- end
59
- end
60
-
61
-
62
- class InheritFromDecorator < InheritDecorator
63
- property :label, inherit: true do
64
- collection :employees do
65
- property :name
66
- end
67
- end
68
- end
69
- end
@@ -1,147 +0,0 @@
1
- require "test_helper"
2
- require 'ruby-prof'
3
-
4
- class CachedTest < MiniTest::Spec
5
- # TODO: also test with feature(Cached)
6
-
7
- module Model
8
- Song = Struct.new(:title, :composer)
9
- Album = Struct.new(:name, :songs, :artist)
10
- Artist = Struct.new(:name, :hidden_taste)
11
- end
12
-
13
- class SongRepresenter < Representable::Decorator
14
- include Representable::Hash
15
- feature Representable::Cached
16
-
17
- property :title, render_filter: lambda { |input, options| "#{input}:#{options[:options][:user_options]}" }
18
- property :composer, class: Model::Artist do
19
- property :name
20
- end
21
- end
22
-
23
- class AlbumRepresenter < Representable::Decorator
24
- include Representable::Hash
25
- include Representable::Cached
26
-
27
- property :name
28
- collection :songs, decorator: SongRepresenter, class: Model::Song
29
- end
30
-
31
-
32
- describe "serialization" do
33
- let (:album_hash) { {"name"=>"Louder And Even More Dangerous", "songs"=>[{"title"=>"Southbound:{:volume=>10}"}, {"title"=>"Jailbreak:{:volume=>10}"}]} }
34
-
35
- let (:song) { Model::Song.new("Jailbreak") }
36
- let (:song2) { Model::Song.new("Southbound") }
37
- let (:album) { Model::Album.new("Live And Dangerous", [song, song2, Model::Song.new("Emerald")]) }
38
- let (:representer) { AlbumRepresenter.new(album) }
39
-
40
- it do
41
- album2 = Model::Album.new("Louder And Even More Dangerous", [song2, song])
42
-
43
- # makes sure options are passed correctly.
44
- representer.to_hash(volume: 9).must_equal({"name"=>"Live And Dangerous",
45
- "songs"=>[{"title"=>"Jailbreak:{:volume=>9}"}, {"title"=>"Southbound:{:volume=>9}"}, {"title"=>"Emerald:{:volume=>9}"}]}) # called in Deserializer/Serializer
46
-
47
- # representer becomes reusable as it is stateless.
48
- # representer.update!(album2)
49
-
50
- # makes sure options are passed correctly.
51
- # representer.to_hash(volume:10).must_equal(album_hash)
52
- end
53
-
54
- # profiling
55
- it do
56
- representer.to_hash
57
-
58
- RubyProf.start
59
- representer.to_hash
60
- res = RubyProf.stop
61
-
62
- printer = RubyProf::FlatPrinter.new(res)
63
-
64
- data = StringIO.new
65
- printer.print(data)
66
- data = data.string
67
-
68
- printer.print(STDOUT)
69
-
70
- # 3 songs get decorated.
71
- data.must_match "3 Representable::Function::Decorate#call"
72
- # 3 nested decorator is instantiated for 3 Songs, though.
73
- data.must_match "3 <Class::Representable::Decorator>#prepare"
74
- # no Binding is instantiated at runtime.
75
- data.wont_match "Representable::Binding#initialize"
76
- # 2 mappers for Album, Song
77
- # data.must_match "2 Representable::Mapper::Methods#initialize"
78
- # title, songs, 3x title, composer
79
- data.must_match "8 Representable::Binding#render_pipeline"
80
- data.wont_match "render_functions"
81
- data.wont_match "Representable::Binding::Factories#render_functions"
82
- end
83
- end
84
-
85
-
86
- describe "deserialization" do
87
- let (:album_hash) {
88
- {
89
- "name"=>"Louder And Even More Dangerous",
90
- "songs"=>[
91
- {"title"=>"Southbound", "composer"=>{"name"=>"Lynott"}},
92
- {"title"=>"Jailbreak", "composer"=>{"name"=>"Phil Lynott"}},
93
- {"title"=>"Emerald"}
94
- ]
95
- }
96
- }
97
-
98
- it do
99
- album = Model::Album.new
100
-
101
- AlbumRepresenter.new(album).from_hash(album_hash)
102
-
103
- album.songs.size.must_equal 3
104
- album.name.must_equal "Louder And Even More Dangerous"
105
- album.songs[0].title.must_equal "Southbound"
106
- album.songs[0].composer.name.must_equal "Lynott"
107
- album.songs[1].title.must_equal "Jailbreak"
108
- album.songs[1].composer.name.must_equal "Phil Lynott"
109
- album.songs[2].title.must_equal "Emerald"
110
- album.songs[2].composer.must_equal nil
111
-
112
- # TODO: test options.
113
- end
114
-
115
- it "xxx" do
116
- representer = AlbumRepresenter.new(Model::Album.new)
117
- representer.from_hash(album_hash)
118
-
119
- RubyProf.start
120
- # puts "#{representer.class.representable_attrs.get(:songs).representer_module.representable_attrs.inspect}"
121
- representer.from_hash(album_hash)
122
- res = RubyProf.stop
123
-
124
- printer = RubyProf::FlatPrinter.new(res)
125
-
126
- data = StringIO.new
127
- printer.print(data)
128
- data = data.string
129
-
130
- # only 2 nested decorators are instantiated, Song, and Artist.
131
- data.must_match "5 <Class::Representable::Decorator>#prepare"
132
- # a total of 5 properties in the object graph.
133
- data.wont_match "Representable::Binding#initialize"
134
-
135
-
136
- data.wont_match "parse_functions" # no pipeline creation.
137
- data.must_match "10 Representable::Binding#parse_pipeline"
138
- # three mappers for Album, Song, composer
139
- # data.must_match "3 Representable::Mapper::Methods#initialize"
140
- # # 6 deserializers as the songs collection uses 2.
141
- # data.must_match "6 Representable::Deserializer#initialize"
142
- # # one populater for every property.
143
- # data.must_match "5 Representable::Populator#initialize"
144
- # printer.print(STDOUT)
145
- end
146
- end
147
- end