representative 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown CHANGED
@@ -1,16 +1,16 @@
1
1
  Representative
2
2
  ==============
3
3
 
4
- "Representative" makes it easier to create XML representations of your Ruby objects.
5
- It works best when you want the XML to roughly follow the object structure,
6
- but still have complete control of the result.
4
+ "Representative" makes it easier to create XML or JSON representations of your Ruby objects.
7
5
 
8
- Example
9
- -------
6
+ It works best when you want the output to roughly follow the object structure, but still want complete control of the result.
7
+
8
+ Generating XML
9
+ --------------
10
10
 
11
11
  Given a Ruby data-structure:
12
12
 
13
- books = [
13
+ @books = [
14
14
  OpenStruct.new(
15
15
  :title => "Sailing for old dogs",
16
16
  :authors => ["Jim Watson"],
@@ -34,13 +34,13 @@ Given a Ruby data-structure:
34
34
  )
35
35
  ]
36
36
 
37
- Representative::Xml can be used to generate XML, in a declarative style:
37
+ Representative::Xml can be used to generate XML:
38
38
 
39
39
  xml = Builder::XmlMarkup.new(:indent => 2)
40
40
 
41
41
  Representative::Xml.new(xml) do |r|
42
42
 
43
- r.list_of :books, books do
43
+ r.list_of :books, @books do
44
44
  r.element :title
45
45
  r.list_of :authors
46
46
  r.element :published do
@@ -53,7 +53,7 @@ Representative::Xml can be used to generate XML, in a declarative style:
53
53
 
54
54
  puts xml.target!
55
55
 
56
- The resulting XML looks like this:
56
+ which produces:
57
57
 
58
58
  <books type="array">
59
59
  <book>
@@ -88,12 +88,65 @@ The resulting XML looks like this:
88
88
 
89
89
  Notice that:
90
90
 
91
- - The structure of the XML mirrors the structure described by the nested Ruby blocks.
91
+ - The structure of the output mirrors the structure described by the nested Ruby blocks.
92
92
  - Representative walks the object-graph for you.
93
93
  - Using `list_of` for a collection attribute generates an "array" element, which plays nicely
94
94
  with most Ruby XML-to-hash converters.
95
95
  - Where a named object-attribute is nil, you get an empty element.
96
96
 
97
+ Generating JSON
98
+ ---------------
99
+
100
+ Representative::Json can be used to generate JSON, using exactly the same DSL:
101
+
102
+ json = Representative::Json.new do |r|
103
+
104
+ r.list_of :books, @books do
105
+ r.element :title
106
+ r.list_of :authors
107
+ r.element :published do
108
+ r.element :by
109
+ r.element :year
110
+ end
111
+ end
112
+
113
+ end
114
+
115
+ puts json.to_s
116
+
117
+ producing:
118
+
119
+ [
120
+ {
121
+ "title": "Sailing for old dogs",
122
+ "authors": [
123
+ "Jim Watson"
124
+ ],
125
+ "published": {
126
+ "by": "Credulous Print",
127
+ "year": 1994
128
+ }
129
+ },
130
+ {
131
+ "title": "On the horizon",
132
+ "authors": [
133
+ "Zoe Primpton",
134
+ "Stan Ford"
135
+ ],
136
+ "published": {
137
+ "by": "McGraw-Hill",
138
+ "year": 2005
139
+ }
140
+ },
141
+ {
142
+ "title": "The Little Blue Book of VHS Programming",
143
+ "authors": [
144
+ "Henry Nelson"
145
+ ],
146
+ "published": null
147
+ }
148
+ ]
149
+
97
150
  Installation
98
151
  ------------
99
152
 
data/Rakefile CHANGED
@@ -17,6 +17,8 @@ require "spec/rake/spectask"
17
17
  Spec::Rake::SpecTask.new(:spec) do |spec|
18
18
  spec.libs << 'lib' << 'spec'
19
19
  spec.spec_files = FileList['spec/**/*_spec.rb']
20
+ spec.spec_opts << "-Du"
21
+ spec.spec_opts << "--color"
20
22
  end
21
23
 
22
24
  task :default => :spec
@@ -0,0 +1,44 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+
3
+ require "rubygems"
4
+ require "representative/json"
5
+ require "ostruct"
6
+
7
+ @books = [
8
+ OpenStruct.new(
9
+ :title => "Sailing for old dogs",
10
+ :authors => ["Jim Watson"],
11
+ :published => OpenStruct.new(
12
+ :by => "Credulous Print",
13
+ :year => 1994
14
+ )
15
+ ),
16
+ OpenStruct.new(
17
+ :title => "On the horizon",
18
+ :authors => ["Zoe Primpton", "Stan Ford"],
19
+ :published => OpenStruct.new(
20
+ :by => "McGraw-Hill",
21
+ :year => 2005
22
+ )
23
+ ),
24
+ OpenStruct.new(
25
+ :title => "The Little Blue Book of VHS Programming",
26
+ :authors => ["Henry Nelson"],
27
+ :rating => "****"
28
+ )
29
+ ]
30
+
31
+ json = Representative::Json.new do |r|
32
+
33
+ r.list_of :books, @books do
34
+ r.element :title
35
+ r.list_of :authors
36
+ r.element :published do
37
+ r.element :by
38
+ r.element :year
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ puts json.to_s
data/examples/xml_demo.rb CHANGED
@@ -4,7 +4,7 @@ require "rubygems"
4
4
  require "representative/xml"
5
5
  require "ostruct"
6
6
 
7
- books = [
7
+ @books = [
8
8
  OpenStruct.new(
9
9
  :title => "Sailing for old dogs",
10
10
  :authors => ["Jim Watson"],
@@ -29,8 +29,10 @@ books = [
29
29
  ]
30
30
 
31
31
  xml = Builder::XmlMarkup.new(:indent => 2)
32
+
32
33
  Representative::Xml.new(xml) do |r|
33
- r.list_of :books, books do
34
+
35
+ r.list_of :books, @books do
34
36
  r.element :title
35
37
  r.list_of :authors
36
38
  r.element :published do
@@ -38,6 +40,7 @@ Representative::Xml.new(xml) do |r|
38
40
  r.element :year
39
41
  end
40
42
  end
43
+
41
44
  end
42
45
 
43
46
  puts xml.target!
@@ -0,0 +1,62 @@
1
+ require "representative/object_inspector"
2
+
3
+ module Representative
4
+
5
+ class Base
6
+
7
+ def initialize(subject = nil, options = {})
8
+ @subjects = [subject]
9
+ @inspector = options[:inspector] || ObjectInspector.new
10
+ end
11
+
12
+ # Return the current "subject" of representation.
13
+ #
14
+ # This object will provide element values where they haven't been
15
+ # explicitly provided.
16
+ #
17
+ def current_subject
18
+ @subjects.last
19
+ end
20
+
21
+ alias :subject :current_subject
22
+
23
+ # Evaluate a block with a specified object as #subject.
24
+ #
25
+ def representing(new_subject, &block)
26
+ with_subject(resolve_value(new_subject), &block)
27
+ end
28
+
29
+ protected
30
+
31
+ def with_subject(subject)
32
+ @subjects.push(subject)
33
+ begin
34
+ yield subject
35
+ ensure
36
+ @subjects.pop
37
+ end
38
+ end
39
+
40
+ def resolve_value(value_generator)
41
+ if value_generator == :self
42
+ current_subject
43
+ elsif value_generator.respond_to?(:to_proc)
44
+ value_generator.to_proc.call(current_subject) unless current_subject.nil?
45
+ else
46
+ value_generator
47
+ end
48
+ end
49
+
50
+ def resolve_attributes(attributes)
51
+ if attributes
52
+ attributes.inject({}) do |resolved, (name, value_generator)|
53
+ resolved_value = resolve_value(value_generator)
54
+ resolved[name.to_s.dasherize] = resolved_value unless resolved_value.nil?
55
+ resolved
56
+ end
57
+ end
58
+ end
59
+
60
+ end
61
+
62
+ end
@@ -0,0 +1,122 @@
1
+ require "active_support/core_ext/array"
2
+ require "representative/base"
3
+ require "json"
4
+
5
+ module Representative
6
+
7
+ class Json < Base
8
+
9
+ def initialize(subject = nil, options = {})
10
+ super(subject, options)
11
+ @buffer = ""
12
+ @indent_level = 0
13
+ now_at :beginning_of_buffer
14
+ yield self if block_given?
15
+ end
16
+
17
+ def element(name, *args, &block)
18
+
19
+ metadata = @inspector.get_metadata(current_subject, name)
20
+ attributes = args.extract_options!.merge(metadata)
21
+
22
+ subject_of_element = if args.empty?
23
+ lambda do |subject|
24
+ @inspector.get_value(current_subject, name)
25
+ end
26
+ else
27
+ args.shift
28
+ end
29
+
30
+ raise ArgumentError, "too many arguments" unless args.empty?
31
+
32
+ label(name)
33
+ value(subject_of_element, &block)
34
+
35
+ end
36
+
37
+ def list_of(name, *args, &block)
38
+ list_subject = args.empty? ? name : args.shift
39
+ items = resolve_value(list_subject)
40
+ label(name)
41
+ inside "[", "]" do
42
+ items.each do |item|
43
+ new_item
44
+ value(item, &block)
45
+ end
46
+ end
47
+ end
48
+
49
+ def value(subject)
50
+ representing(subject) do
51
+ if block_given? && !current_subject.nil?
52
+ inside "{", "}" do
53
+ yield current_subject
54
+ end
55
+ else
56
+ emit(current_subject.to_json)
57
+ end
58
+ end
59
+ now_at :end_of_item
60
+ end
61
+
62
+ def comment(text)
63
+ new_item
64
+ emit("// #{text}")
65
+ now_at :end_of_comment
66
+ end
67
+
68
+ def to_json
69
+ @buffer + "\n"
70
+ end
71
+
72
+ def to_s
73
+ to_json
74
+ end
75
+
76
+ private
77
+
78
+ def emit(s)
79
+ @buffer << s
80
+ end
81
+
82
+ def indentation
83
+ (" " * @indent_level)
84
+ end
85
+
86
+ def label(name)
87
+ return false if @indent_level == 0
88
+ new_item
89
+ emit("#{name.to_s.to_json}: ")
90
+ end
91
+
92
+ def new_item
93
+ emit(",") if at? :end_of_item
94
+ emit("\n") unless at? :beginning_of_buffer
95
+ emit(indentation)
96
+ @pending_comma = ","
97
+ end
98
+
99
+ def inside(opening_char, closing_char)
100
+ emit(opening_char)
101
+ @indent_level += 1
102
+ now_at :beginning_of_block
103
+ yield
104
+ @indent_level -= 1
105
+ emit("\n#{indentation}") unless at? :beginning_of_block
106
+ emit(closing_char)
107
+ now_at :end_of_item
108
+ end
109
+
110
+ def now_at(state)
111
+ @state = state
112
+ end
113
+
114
+ def at?(state)
115
+ @state == state
116
+ end
117
+
118
+ end
119
+
120
+ JSON = Json
121
+
122
+ end
@@ -1,3 +1,3 @@
1
1
  module Representative
2
- VERSION = "0.2.3".freeze
2
+ VERSION = "0.2.4".freeze
3
3
  end
@@ -1,14 +1,14 @@
1
1
  require "active_support/core_ext/array"
2
2
  require "active_support/core_ext/string"
3
3
  require "builder"
4
+ require "representative/base"
4
5
  require "representative/empty"
5
- require "representative/object_inspector"
6
6
 
7
7
  module Representative
8
8
 
9
9
  # Easily generate XML while traversing an object-graph.
10
10
  #
11
- class Xml
11
+ class Xml < Base
12
12
 
13
13
  # Create an XML-generating Representative. The first argument should be an instance of
14
14
  # Builder::XmlMarkup (or something that implements it's interface). The second argument
@@ -16,28 +16,10 @@ module Representative
16
16
  #
17
17
  def initialize(xml_builder, subject = nil, options = {})
18
18
  @xml = xml_builder
19
- @subjects = [subject]
20
- @inspector = options[:inspector] || ObjectInspector.new
19
+ super(subject, options)
21
20
  yield self if block_given?
22
21
  end
23
22
 
24
- # Return the current "subject" of representation.
25
- #
26
- # This object will provide element values where they haven't been
27
- # explicitly provided.
28
- #
29
- def current_subject
30
- @subjects.last
31
- end
32
-
33
- alias :subject :current_subject
34
-
35
- # Evaluate a block with a specified object as #subject.
36
- #
37
- def representing(new_subject, &block)
38
- with_subject(resolve_value(new_subject), &block)
39
- end
40
-
41
23
  # Generate an element.
42
24
  #
43
25
  # With two arguments, it generates an element with the specified text content.
@@ -156,37 +138,6 @@ module Representative
156
138
  @xml.comment!(text)
157
139
  end
158
140
 
159
- private
160
-
161
- def with_subject(subject)
162
- @subjects.push(subject)
163
- begin
164
- yield subject
165
- ensure
166
- @subjects.pop
167
- end
168
- end
169
-
170
- def resolve_value(value_generator)
171
- if value_generator == :self
172
- current_subject
173
- elsif value_generator.respond_to?(:to_proc)
174
- value_generator.to_proc.call(current_subject) unless current_subject.nil?
175
- else
176
- value_generator
177
- end
178
- end
179
-
180
- def resolve_attributes(attributes)
181
- if attributes
182
- attributes.inject({}) do |resolved, (name, value_generator)|
183
- resolved_value = resolve_value(value_generator)
184
- resolved[name.to_s.dasherize] = resolved_value unless resolved_value.nil?
185
- resolved
186
- end
187
- end
188
- end
189
-
190
141
  end
191
142
 
192
143
  end
@@ -0,0 +1,224 @@
1
+ require 'spec_helper'
2
+
3
+ require "representative/json"
4
+
5
+ describe Representative::Json do
6
+
7
+ def r
8
+ @representative ||= Representative::Json.new(@subject)
9
+ end
10
+
11
+ def resulting_json
12
+ r.to_json
13
+ end
14
+
15
+ describe "at the top level" do
16
+
17
+ describe "#element" do
18
+
19
+ describe "with an explicit String value" do
20
+
21
+ it "outputs the value as JSON" do
22
+ r.element :name, "Fred"
23
+ resulting_json.should == %{"Fred"\n}
24
+ end
25
+
26
+ end
27
+
28
+ describe "with an explicit integer value" do
29
+
30
+ it "outputs the value as JSON" do
31
+ r.element :age, 36
32
+ resulting_json.should == "36\n"
33
+ end
34
+
35
+ end
36
+
37
+ describe "with a nil value" do
38
+
39
+ it "generates null" do
40
+ r.element :flavour, nil
41
+ resulting_json.should == "null\n"
42
+ end
43
+
44
+ describe "and a block" do
45
+
46
+ it "generates null" do
47
+ r.element :book, nil do
48
+ r.element :author
49
+ end
50
+ resulting_json.should == "null\n"
51
+ end
52
+
53
+ end
54
+
55
+ end
56
+
57
+ describe "without an explicit value" do
58
+
59
+ it "extracts the value from the current subject" do
60
+ @author = OpenStruct.new(:name => "Fred", :age => 36)
61
+ r.representing(@author) do
62
+ r.element :name
63
+ end
64
+ resulting_json.should == %{"Fred"\n}
65
+ end
66
+
67
+ end
68
+
69
+ describe "with a block" do
70
+
71
+ it "outputs an object" do
72
+ r.element :something, Object.new do
73
+ end
74
+ resulting_json.should == "{}\n"
75
+ end
76
+
77
+ end
78
+
79
+ end
80
+
81
+ describe "#list_of" do
82
+
83
+ describe "with an explicit array value" do
84
+
85
+ it "outputs the array as JSON" do
86
+ r.list_of :names, %w(Hewey Dewey Louie)
87
+ resulting_json.should == undent(<<-JSON)
88
+ [
89
+ "Hewey",
90
+ "Dewey",
91
+ "Louie"
92
+ ]
93
+ JSON
94
+ end
95
+
96
+ end
97
+
98
+ describe "without an explicit value" do
99
+
100
+ it "extracts the value from the current subject" do
101
+ @donald = OpenStruct.new(:nephews => %w(Hewey Dewey Louie))
102
+ r.element(:duck, @donald) do
103
+ r.list_of :nephews
104
+ end
105
+ resulting_json.should == undent(<<-JSON)
106
+ {
107
+ "nephews": [
108
+ "Hewey",
109
+ "Dewey",
110
+ "Louie"
111
+ ]
112
+ }
113
+ JSON
114
+ end
115
+
116
+ end
117
+
118
+ describe "with a block" do
119
+
120
+ it "generates an object for each array element" do
121
+ @authors = [
122
+ OpenStruct.new(:name => "Hewey", :age => 3),
123
+ OpenStruct.new(:name => "Dewey", :age => 4),
124
+ OpenStruct.new(:name => "Louie", :age => 5)
125
+ ]
126
+ r.list_of :authors, @authors do
127
+ r.element :name
128
+ r.element :age
129
+ end
130
+ resulting_json.should == undent(<<-JSON)
131
+ [
132
+ {
133
+ "name": "Hewey",
134
+ "age": 3
135
+ },
136
+ {
137
+ "name": "Dewey",
138
+ "age": 4
139
+ },
140
+ {
141
+ "name": "Louie",
142
+ "age": 5
143
+ }
144
+ ]
145
+ JSON
146
+ end
147
+
148
+ end
149
+
150
+ end
151
+
152
+ describe "#comment" do
153
+
154
+ it "inserts a comment" do
155
+ r.comment "now pay attention"
156
+ resulting_json.should == undent(<<-JSON)
157
+ // now pay attention
158
+ JSON
159
+ end
160
+
161
+ end
162
+
163
+ end
164
+
165
+ describe "within an element block" do
166
+
167
+ describe "#element" do
168
+
169
+ it "generates labelled values" do
170
+ r.element :author, Object.new do
171
+ r.element :name, "Fred"
172
+ r.element :age, 36
173
+ end
174
+ resulting_json.should == undent(<<-JSON)
175
+ {
176
+ "name": "Fred",
177
+ "age": 36
178
+ }
179
+ JSON
180
+ end
181
+
182
+ describe "without an explicit value" do
183
+
184
+ it "extracts the value from the current subject" do
185
+ @author = OpenStruct.new(:name => "Fred", :age => 36)
186
+ r.element :author, @author do
187
+ r.element :name
188
+ r.element :age
189
+ end
190
+ resulting_json.should == undent(<<-JSON)
191
+ {
192
+ "name": "Fred",
193
+ "age": 36
194
+ }
195
+ JSON
196
+ end
197
+
198
+ end
199
+
200
+ end
201
+
202
+ describe "#comment" do
203
+
204
+ it "inserts a comment" do
205
+ @author = OpenStruct.new(:name => "Fred", :age => 36)
206
+ r.element :author, @author do
207
+ r.element :name
208
+ r.comment "age is irrelevant"
209
+ r.element :age
210
+ end
211
+ resulting_json.should == undent(<<-JSON)
212
+ {
213
+ "name": "Fred",
214
+ // age is irrelevant
215
+ "age": 36
216
+ }
217
+ JSON
218
+ end
219
+
220
+ end
221
+
222
+ end
223
+
224
+ end
@@ -1,4 +1,4 @@
1
- require File.dirname(__FILE__) + '/../spec_helper'
1
+ require 'spec_helper'
2
2
 
3
3
  require "representative/xml"
4
4
 
data/spec/spec_helper.rb CHANGED
@@ -8,3 +8,12 @@ require "rubygems"
8
8
  Spec::Runner.configure do |config|
9
9
 
10
10
  end
11
+
12
+ def undent(raw)
13
+ if raw =~ /\A( +)/
14
+ indent = $1
15
+ raw.gsub(/^#{indent}/, '').gsub(/ +$/, '')
16
+ else
17
+ raw
18
+ end
19
+ end
metadata CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
5
5
  segments:
6
6
  - 0
7
7
  - 2
8
- - 3
9
- version: 0.2.3
8
+ - 4
9
+ version: 0.2.4
10
10
  platform: ruby
11
11
  authors:
12
12
  - Mike Williams
@@ -40,11 +40,14 @@ extensions: []
40
40
  extra_rdoc_files: []
41
41
 
42
42
  files:
43
+ - lib/representative/base.rb
43
44
  - lib/representative/empty.rb
45
+ - lib/representative/json.rb
44
46
  - lib/representative/object_inspector.rb
45
47
  - lib/representative/version.rb
46
48
  - lib/representative/xml.rb
47
49
  - lib/representative.rb
50
+ - examples/json_demo.rb
48
51
  - examples/xml_demo.rb
49
52
  - README.markdown
50
53
  - LICENSE
@@ -79,6 +82,7 @@ signing_key:
79
82
  specification_version: 3
80
83
  summary: Builds XML representations of your Ruby objects
81
84
  test_files:
85
+ - spec/representative/json_spec.rb
82
86
  - spec/representative/xml_spec.rb
83
87
  - spec/spec_helper.rb
84
88
  - Rakefile