ruby-serializer 0.0.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 76d0570d6c6888182f54fad8d93447115da9b15d
4
- data.tar.gz: fadc5ff494af8792ba60f1c76b90cc822ed90f4c
3
+ metadata.gz: 64873bd0b35de8defd662d6bd92bb9b52100937e
4
+ data.tar.gz: a735bbb0a3e92c063bbef5be1c1b58dac515cfd2
5
5
  SHA512:
6
- metadata.gz: 0d35417352d902d4e46cd7de29e7151fe6024b784f6b46a0029bf38cd4db8545c8e559f6f71a82e9d75dbcefadf3411eab517a7b60ab4dc39baa23aeaae2313a
7
- data.tar.gz: 5a0ea330adc5f8ad4cdac778524677342547f30888fadadae3f527b2a685070e6f32557a8854d6a759647747638a6f60593a9224967241cda736521663ed6c35
6
+ metadata.gz: da5bf86ac16016adec3d8951000c0846f488408e67826afc85395bf273456854319e35289bb083377404c4863c276fa9e650ba716b34cae6e5090206ec7d6fce
7
+ data.tar.gz: d26a542f36175f9638a29091fe03250ae7272cacc1cff5e5a52f8ba7bf91a052ccf875921fd47f0971443f57f290ad07475e8a482563e86fb31a8c8df29538f7
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- ruby-serializer (0.0.1)
4
+ ruby-serializer (1.0.0)
5
5
 
6
6
  GEM
7
7
  remote: http://rubygems.org/
@@ -0,0 +1,24 @@
1
+ module RubySerializer
2
+ class Association < Field
3
+
4
+ #----------------------------------------------------------------------------------------------
5
+
6
+ def serialize(resource, serializer)
7
+ includes = serializer.send(:includes)[field]
8
+ association = resource.send(field)
9
+ if association
10
+ RubySerializer.as_json(association, include: includes)
11
+ end
12
+ end
13
+
14
+ #----------------------------------------------------------------------------------------------
15
+
16
+ def present?(resource, serializer)
17
+ super &&
18
+ serializer.send(:include?, field)
19
+ end
20
+
21
+ #----------------------------------------------------------------------------------------------
22
+
23
+ end
24
+ end
@@ -3,8 +3,7 @@ module RubySerializer
3
3
 
4
4
  extend RubySerializer::Dsl
5
5
 
6
- attr_reader :options, :include # only populated during #serialize and (temporarily) stores the #serialize options to make
7
- # them available to any potential :value, :only and :unless lambdas in the derived serializer
6
+ attr_reader :options
8
7
 
9
8
  def initialize(resource)
10
9
  @resource = resource
@@ -23,6 +22,11 @@ module RubySerializer
23
22
  private
24
23
 
25
24
  attr_reader :resource
25
+ attr_reader :includes
26
+
27
+ def include?(association)
28
+ includes.key?(association.to_sym)
29
+ end
26
30
 
27
31
  def serialize(options = {})
28
32
  if resource.respond_to? :to_ary
@@ -40,7 +44,8 @@ module RubySerializer
40
44
  end
41
45
 
42
46
  def serialize_object(options)
43
- @options = options
47
+ @options = options
48
+ @includes = expand_includes(options[:include])
44
49
  json = {}
45
50
  json[:errors] = resource.errors if resource.respond_to?(:valid?) && !resource.valid?
46
51
  self.class.fields.each do |field|
@@ -58,5 +63,28 @@ module RubySerializer
58
63
 
59
64
  #----------------------------------------------------------------------------------------------
60
65
 
66
+ def expand_includes(includes, options = {})
67
+
68
+ return includes if includes.is_a?(Hash) # already expanded
69
+
70
+ includes = case includes
71
+ when nil then []
72
+ when Array then includes
73
+ when Symbol then [ includes ]
74
+ when String then includes.split(',')
75
+ else
76
+ raise ArgumentError, "unexpected includes #{includes.class}"
77
+ end
78
+
79
+ includes.each_with_object({}) do |i, hash|
80
+ i.to_s.downcase.strip.split('.').reduce(hash) do |h, part|
81
+ h[part.to_sym] ||= {}
82
+ end
83
+ end
84
+
85
+ end
86
+
87
+ #----------------------------------------------------------------------------------------------
88
+
61
89
  end
62
90
  end
@@ -1,6 +1,8 @@
1
1
  module RubySerializer
2
2
  module Dsl
3
3
 
4
+ #----------------------------------------------------------------------------------------------
5
+
4
6
  def serializes(name)
5
7
  define_method(name) do # provide a friendly-name accessor to the underlying resource
6
8
  resource
@@ -11,8 +13,17 @@ module RubySerializer
11
13
  fields << Field.new(field, namespace, options)
12
14
  end
13
15
 
14
- def fields
15
- @fields ||= []
16
+ def belongs_to(field, options = {})
17
+ fields << Field.new("#{field}_id", namespace, options)
18
+ fields << Association.new(field, namespace, options)
19
+ end
20
+
21
+ def has_many(field, options = {})
22
+ fields << Association.new(field, namespace, options)
23
+ end
24
+
25
+ def has_one(field, options = {})
26
+ fields << Association.new(field, namespace, options)
16
27
  end
17
28
 
18
29
  def namespace(ns = nil, &block)
@@ -23,5 +34,11 @@ module RubySerializer
23
34
  @namespace.pop
24
35
  end
25
36
 
37
+ def fields
38
+ @fields ||= []
39
+ end
40
+
41
+ #----------------------------------------------------------------------------------------------
42
+
26
43
  end
27
44
  end
@@ -5,8 +5,8 @@ module RubySerializer
5
5
 
6
6
  def initialize(field, namespace, options)
7
7
  @field = field.to_sym
8
- @as = options[:as] || field
9
- @from = options[:from] || field
8
+ @as = options[:as] || @field
9
+ @from = options[:from] || @field
10
10
  @value = options[:value]
11
11
  @only = options[:only]
12
12
  @unless = options[:unless]
@@ -2,16 +2,17 @@ module RubySerializer
2
2
 
3
3
  #------------------------------------------------------------------------------------------------
4
4
 
5
- VERSION = '0.0.1'
5
+ VERSION = '1.0.0'
6
6
  SUMMARY = 'Serialize POROs to JSON'
7
7
  DESCRIPTION = 'A general purpose library for serializing plain old ruby objects (POROs) into JSON using a declarative DSL'
8
8
  LIB = File.dirname(__FILE__)
9
9
 
10
10
  #------------------------------------------------------------------------------------------------
11
11
 
12
- autoload :Dsl, File.join(LIB, 'ruby_serializer/dsl')
13
- autoload :Base, File.join(LIB, 'ruby_serializer/base')
14
- autoload :Field, File.join(LIB, 'ruby_serializer/field')
12
+ autoload :Dsl, File.join(LIB, 'ruby_serializer/dsl')
13
+ autoload :Base, File.join(LIB, 'ruby_serializer/base')
14
+ autoload :Field, File.join(LIB, 'ruby_serializer/field')
15
+ autoload :Association, File.join(LIB ,'ruby_serializer/association')
15
16
 
16
17
  #------------------------------------------------------------------------------------------------
17
18
 
data/readme.md CHANGED
@@ -6,7 +6,7 @@ into JSON using a declarative DSL.
6
6
  ## Installation
7
7
 
8
8
  ```bash
9
- $ gem install ruby-serializer # ... or add to your Gemfile as appropriate
9
+ $ gem install ruby-serializer # ... or add to your Gemfile as appropriate
10
10
  ```
11
11
 
12
12
  ## Usage
@@ -38,6 +38,21 @@ And serialize using...
38
38
  # { size: 'large', color: 'red', shape: 'square' }
39
39
  ```
40
40
 
41
+ ### Serializer naming convention
42
+
43
+ By default, `ruby-serializer` will look for a serializer with the following naming convention
44
+
45
+ ```ruby
46
+ "#{resource.class.name}Serializer"
47
+ ```
48
+
49
+ But you can always specify the serializer class explicitly:
50
+
51
+ ```ruby
52
+ shape = Shape.new(...)
53
+ json = RubySerializer.as_json shape, with: MyCustomSerializer
54
+ ```
55
+
41
56
  ## Beyond the basics
42
57
 
43
58
  Ok, that was very basic, but with RubySerializer you can also:
@@ -50,54 +65,153 @@ Ok, that was very basic, but with RubySerializer you can also:
50
65
  * `:include` nested `:belongs_to` associated models
51
66
  * `:include` nested `:has_one` associated models
52
67
  * `:include` nested `:has_many` associated models
53
- * serialize an array of models
54
- * add `ActionController` integration for easy API serialization
55
68
 
56
69
  ### Exposing attributes with another name
57
70
 
58
- >> _TODO_
71
+ You can expose an attribute with a different name using `:as`
72
+
73
+ ```ruby
74
+ class UserSerializer < RubySerializer::Base
75
+ expose :id
76
+ expose :name, as: :user_name
77
+ expose :email, as: :user_email
78
+ end
79
+
80
+ user = User.new(id: 42, name: 'Jake', email: 'jake@codeincomplete.com')
81
+ json = RubySerializer.as_json user
82
+
83
+ # { id: 42, user_name: 'Jake', user_email: 'jake@codeincomplete.com' }
84
+ ```
59
85
 
60
86
  ### Exposing attributes with custom values
61
87
 
62
- >> _TODO_
88
+ You can expose an attribute with a different value using `:value`
89
+
90
+ ```ruby
91
+ class UserSerializer < RubySerializer::Base
92
+ expose :id
93
+ expose :static, value: 'static' # expose a static attribute
94
+ expose :dynamic, value: -> { resource.name.reverse } # expose a dynamic attribute using a lambda
95
+ expose :method, value: :name # expose a dynamic attribute using a symbol (calls a method on the underlying resource automatically)
96
+ end
97
+
98
+ user = User.new(id: 42, name: 'Jake')
99
+ json = RubySerializer.as_json user
100
+
101
+ # { id: 42, static: 'static', dynamic: 'ekaJ', method: 'Jake' }
102
+ ```
63
103
 
64
104
  ### Exposing attributes conditionally
65
105
 
66
- >> _TODO_
106
+ You can expose attributes conditionally using `:only` and `:unless`
107
+
108
+ ```ruby
109
+ class UserSerializer < RubySerializer::Base
110
+ expose :id
111
+ expose :admin, only: :admin? # expose this field only when resource.admin?
112
+ expose :name, only: -> { resource.name.present? } # expose this field only when resource.name.present?
113
+ expose :email, unless: -> { resource.email.blank? } # expose this field unless resource.email.blank?
114
+ end
115
+
116
+ user = User.new(id: 1)
117
+ json = RubySerializer.as_json user
118
+
119
+ # { id: 1 }
120
+
121
+ user = User.new(id: 2, name: 'Joe')
122
+ json = RubySerializer.as_json user
123
+
124
+ # { id: 2, name: 'Joe' }
125
+
126
+ user = User.new(id: 3, name: 'Bob', admin: true)
127
+ json = RubySerializer.as_json user
128
+
129
+ # { id: 3, name: 'Bob', admin: true }
130
+
131
+ ```
67
132
 
68
133
  ### Exposing attributes within a namespace
69
134
 
70
- >> _TODO_
135
+ You can nest serialized attributes using a `:namespace`:
71
136
 
72
- ### Exposing model validation errors
137
+ ```ruby
138
+ class UserSerializer < RubySerializer::Base
139
+ expose :id
140
+ namespace :i18n do
141
+ expose :locale
142
+ expose :time_zone
143
+ end
144
+ end
145
+
146
+ user = User.new(id: 42, locale: 'en-us', time_zone: 'UTC')
147
+ json = RubySerializer.as_json user
73
148
 
74
- >> _TODO_
149
+ # { id: 42, i18n: { locale: 'en-us', time_zone: 'UTC' } }
150
+ ```
75
151
 
76
152
  ### Exposing associations
77
153
 
78
- >> _TODO_
154
+ If your models have ActiveRecord like associations you can declare them in your serializers and
155
+ then use the optional `:include` mechanism to choose when to include them as nested resources
156
+ during serialization:
157
+
158
+ ```ruby
159
+ class Publisher
160
+ attr :id, :name
161
+ has_many :books
162
+ end
163
+
164
+ class Book
165
+ attr :isbn, :name
166
+ belongs_to :publisher
167
+ end
168
+
169
+ class PublisherSerializer
170
+ expose :id
171
+ expose :name
172
+ has_many :books
173
+ end
174
+
175
+ class BookSerializer
176
+ expose :isbn
177
+ expose :name
178
+ belongs_to :publisher
179
+ end
180
+
181
+ publisher = Publisher.new(id: 42, name: 'Addison Wesley')
182
+
183
+ RubySerializer.as_json publisher # WITHOUT :include
184
+
185
+ # { id: 42, name: 'Addison Wesley' }
186
+
187
+ RubySerializer.as_json publisher, include: :books # WITH :include
79
188
 
80
- ### Serializing arrays
189
+ # { id: 42, name: 'Addison Wesley', books: [
190
+ # { isbn: '020161622X', name: 'Pragmatic Programmer' },
191
+ # { isbn: '0201633612', name: 'Design Patterns '},
192
+ # ...
193
+ # ] }
194
+ ```
81
195
 
82
- >> _TODO_
196
+ `ruby-serializer` supports `belongs_to`, `has_one`, and `has_many` associations.
83
197
 
84
- ### ActionController integration
198
+ You can include multiple associations using an array of includes:
85
199
 
86
- >> _TODO_
200
+ ```ruby
201
+ RubySerializer.as_json publisher, include: [ :books, :address, :websites ]
202
+ ```
87
203
 
88
- # TODO
204
+ You can also include nested associations:
89
205
 
90
- * :belongs_to
91
- * :has_one
92
- * :has_many
93
- * :includes
94
- * ActionController integration
95
- * documentation
206
+ ```ruby
207
+ RubySerializer.as_json publisher, include: { books: [ :authors ] }
208
+ ```
209
+
210
+ ### Exposing model validation errors
96
211
 
97
- # Roadmap
212
+ By default, if your underlying model responds to `:valid?` and returns `false` then the
213
+ serialized response will automatically include a serialized `:errors` attribute.
98
214
 
99
- * Extensibility with custom Field types
100
-
101
215
  # License
102
216
 
103
217
  See [LICENSE](https://github.com/jakesgordon/ruby-serializer/blob/master/LICENSE) file.
@@ -0,0 +1,288 @@
1
+ require_relative 'test_case'
2
+
3
+ module RubySerializer
4
+ class AssociationTest < TestCase
5
+
6
+ #----------------------------------------------------------------------------------------------
7
+
8
+ PORTLAND = { id: :portland, city: 'Portland', state: 'OR' }
9
+ SEATTLE = { id: :seattle, city: 'Seattle', state: 'WA' }
10
+ BOSTON = { id: :boston, city: 'Boston', state: 'MA' }
11
+ NYC = { id: :nyc, city: 'New York', state: 'NY' }
12
+ SF = { id: :sf, city: 'San Francisco', state: 'CA' }
13
+ LA = { id: :la, city: 'Los Angeles', state: 'LA' }
14
+ CHICAGO = { id: :chicago, city: 'Chicago', state: 'IL' }
15
+ DALLAS = { id: :dallas, city: 'Dallas', state: 'TX' }
16
+ REDMOND = { id: :redmond, city: 'Redmond', state: 'WA' }
17
+
18
+ MCCONNEL = { id: :mcconnel, name: 'Steve McConnel', address: SEATTLE }
19
+ HUNT = { id: :hunt, name: 'Andrew Hunt', address: PORTLAND }
20
+ THOMAS = { id: :thomas, name: 'David Thomas', address: BOSTON }
21
+ GAMMA = { id: :gamma, name: 'Erich Gamma', address: NYC }
22
+ HELM = { id: :helm, name: 'Richard Helm', address: SF }
23
+ JOHNSON = { id: :johnson, name: 'Ralph Johnson', address: CHICAGO }
24
+ VLISSIDES = { id: :vlissides, name: 'John Vlissides', address: DALLAS }
25
+
26
+ PP = { id: :pp, isbn: '020161622X', name: 'Pragmatic Programmer', publisher_id: :aw, authors: [ HUNT, THOMAS ] }
27
+ DP = { id: :dp, isbn: '0201633612', name: 'Design Patterns', publisher_id: :aw, authors: [ GAMMA, HELM, JOHNSON, VLISSIDES ] }
28
+ CC = { id: :cc, isbn: '0735619670', name: 'Code Complete', publisher_id: :ms, authors: [ MCCONNEL ] }
29
+
30
+ AW = { id: :aw, name: 'Addison-Wesley', address: LA, books: [ PP, DP ] }
31
+ MS = { id: :ms, name: 'Microsoft Press', address: REDMOND, books: [ CC ] }
32
+
33
+ PROGRAMMING = { id: :programming, name: 'Programming', books: [ PP, DP, CC ] }
34
+
35
+ #----------------------------------------------------------------------------------------------
36
+
37
+ class Publisher < Model
38
+ attr :id, :name
39
+ has_one :address
40
+ has_many :books
41
+ end
42
+
43
+ class Book < Model
44
+ attr :isbn, :name
45
+ belongs_to :publisher
46
+ has_many :authors
47
+ end
48
+
49
+ class Author < Model
50
+ attr :id, :name
51
+ has_one :address
52
+ end
53
+
54
+ class Address < Model
55
+ attr :id, :city, :state
56
+ end
57
+
58
+ class Category < Model
59
+ attr :id, :name
60
+ has_many :books
61
+ end
62
+
63
+ #----------------------------------------------------------------------------------------------
64
+
65
+ class PublisherSerializer < RubySerializer::Base
66
+ expose :id
67
+ expose :name
68
+ has_one :address
69
+ has_many :books
70
+ end
71
+
72
+ class BookSerializer < RubySerializer::Base
73
+ expose :isbn
74
+ expose :name
75
+ belongs_to :publisher
76
+ has_many :authors
77
+ end
78
+
79
+ class AuthorSerializer < RubySerializer::Base
80
+ expose :id
81
+ expose :name
82
+ has_one :address
83
+ end
84
+
85
+ class AddressSerializer < RubySerializer::Base
86
+ expose :id
87
+ expose :city
88
+ expose :state
89
+ end
90
+
91
+ class CategorySerializer < RubySerializer::Base
92
+ expose :id
93
+ expose :name
94
+ has_many :books
95
+ end
96
+
97
+ #----------------------------------------------------------------------------------------------
98
+
99
+ def test_belongs_to
100
+
101
+ book = books(PP)
102
+ with = serialize book, include: :publisher
103
+ without = serialize book
104
+
105
+ assert_book PP, with, with: :publisher
106
+ assert_publisher AW, with[:publisher]
107
+ assert_book PP, without
108
+
109
+ end
110
+
111
+ #----------------------------------------------------------------------------------------------
112
+
113
+ def test_has_one
114
+
115
+ author = authors(HUNT)
116
+ with = serialize author, include: :address
117
+ without = serialize author
118
+
119
+ assert_author HUNT, with, with: :address
120
+ assert_address PORTLAND, with[:address]
121
+ assert_author HUNT, without
122
+
123
+ end
124
+
125
+ #----------------------------------------------------------------------------------------------
126
+
127
+ def test_has_many
128
+
129
+ publisher = publishers(AW)
130
+ with = serialize publisher, include: :books
131
+ without = serialize publisher
132
+
133
+ assert_publisher AW, with, with: :books
134
+ assert_equal 2, with[:books].length
135
+ assert_book PP, with[:books][0]
136
+ assert_book DP, with[:books][1]
137
+ assert_publisher AW, without
138
+
139
+ end
140
+
141
+ #----------------------------------------------------------------------------------------------
142
+
143
+ def test_multiple_includes
144
+ variations = [
145
+ [ :books, :address ], # verify using array of symbols
146
+ [ 'books', 'address' ], # verify using array of strings
147
+ "books,address", # verify using a comma separated string
148
+ " books , address " # verify using a comma separated string (with whitespace)
149
+ ]
150
+ variations.each do |includes|
151
+ publisher = publishers(AW)
152
+ json = serialize publisher, include: includes
153
+ assert_publisher AW, json, with: [ :address, :books ]
154
+ assert_address LA, json[:address]
155
+ assert_equal 2, json[:books].length
156
+ assert_book PP, json[:books][0]
157
+ assert_book DP, json[:books][1]
158
+ end
159
+ end
160
+
161
+ #----------------------------------------------------------------------------------------------
162
+
163
+ def test_nested_includes
164
+ publisher = publishers(AW)
165
+ json = serialize publisher, include: "books.authors.address"
166
+ assert_publisher AW, json, with: :books
167
+ assert_equal 2, json[:books].length
168
+ assert_book PP, json[:books][0], with: :authors
169
+ assert_equal 2, json[:books][0][:authors].length
170
+ assert_author HUNT, json[:books][0][:authors][0], with: :address
171
+ assert_address PORTLAND, json[:books][0][:authors][0][:address]
172
+ assert_author THOMAS, json[:books][0][:authors][1], with: :address
173
+ assert_address BOSTON, json[:books][0][:authors][1][:address]
174
+ assert_book DP, json[:books][1], with: :authors
175
+ assert_equal 4, json[:books][1][:authors].length
176
+ assert_author GAMMA, json[:books][1][:authors][0], with: :address
177
+ assert_address NYC, json[:books][1][:authors][0][:address]
178
+ assert_author HELM, json[:books][1][:authors][1], with: :address
179
+ assert_address SF, json[:books][1][:authors][1][:address]
180
+ assert_author JOHNSON, json[:books][1][:authors][2], with: :address
181
+ assert_address CHICAGO, json[:books][1][:authors][2][:address]
182
+ assert_author VLISSIDES, json[:books][1][:authors][3], with: :address
183
+ assert_address DALLAS, json[:books][1][:authors][3][:address]
184
+ end
185
+
186
+ #----------------------------------------------------------------------------------------------
187
+
188
+ def test_multiple_nested_includes
189
+ category = categories(PROGRAMMING)
190
+ json = serialize category, include: "books.publisher, books.authors"
191
+ assert_category PROGRAMMING, json, with: :books
192
+ assert_equal 3, json[:books].length
193
+ assert_book PP, json[:books][0], with: [ :publisher, :authors ]
194
+ assert_publisher AW, json[:books][0][:publisher]
195
+ assert_equal 2, json[:books][0][:authors].length
196
+ assert_author HUNT, json[:books][0][:authors][0]
197
+ assert_author THOMAS, json[:books][0][:authors][1]
198
+ assert_book DP, json[:books][1], with: [ :publisher, :authors ]
199
+ assert_publisher AW, json[:books][1][:publisher]
200
+ assert_equal 4, json[:books][1][:authors].length
201
+ assert_author GAMMA, json[:books][1][:authors][0]
202
+ assert_author HELM, json[:books][1][:authors][1]
203
+ assert_author JOHNSON, json[:books][1][:authors][2]
204
+ assert_author VLISSIDES, json[:books][1][:authors][3]
205
+ assert_book CC, json[:books][2], with: [ :publisher, :authors ]
206
+ assert_publisher MS, json[:books][2][:publisher]
207
+ assert_equal 1, json[:books][2][:authors].length
208
+ assert_author MCCONNEL, json[:books][2][:authors][0]
209
+ end
210
+
211
+ #----------------------------------------------------------------------------------------------
212
+
213
+ private
214
+
215
+ def addresses(fixture)
216
+ Address.new(fixture)
217
+ end
218
+
219
+ def authors(fixture)
220
+ author = Author.new(fixture)
221
+ author.address = addresses(fixture[:address]) if fixture.key?(:address)
222
+ author
223
+ end
224
+
225
+ def books(fixture)
226
+ book = Book.new(fixture)
227
+ book.publisher = publishers(fixture[:publisher_id], false)
228
+ book.authors = Array(fixture[:authors]).map { |a| authors(a) } if fixture.key?(:authors)
229
+ book
230
+ end
231
+
232
+ def publishers(fixture, include_books = true)
233
+ fixture = AW if fixture == :aw
234
+ fixture = MS if fixture == :ms
235
+ publisher = Publisher.new(fixture)
236
+ publisher.address = addresses(fixture[:address]) if fixture.key?(:address)
237
+ publisher.books = Array(fixture[:books]).map { |b| books(b) } if include_books && fixture[:books]
238
+ publisher
239
+ end
240
+
241
+ def categories(fixture)
242
+ category = Category.new(fixture)
243
+ category.books = Array(fixture[:books]).map { |b| books(b) } if fixture.key?(:books)
244
+ category
245
+ end
246
+
247
+ #----------------------------------------------------------------------------------------------
248
+
249
+ def assert_publisher(expected, actual, options = {})
250
+ expected_keys = [ :id, :name ] + Array(options[:with])
251
+ assert_set expected_keys, actual.keys
252
+ assert_equal expected[:id], actual[:id]
253
+ assert_equal expected[:name], actual[:name]
254
+ end
255
+
256
+ def assert_book(expected, actual, options = {})
257
+ expected_keys = [ :isbn, :name, :publisher_id ] + Array(options[:with])
258
+ assert_set expected_keys, actual.keys
259
+ assert_equal expected[:isbn], actual[:isbn]
260
+ assert_equal expected[:name], actual[:name]
261
+ assert_equal expected[:publisher_id], actual[:publisher_id]
262
+ end
263
+
264
+ def assert_author(expected, actual, options = {})
265
+ expected_keys = [ :id, :name ] + Array(options[:with])
266
+ assert_set expected_keys, actual.keys
267
+ assert_equal expected[:id], actual[:id]
268
+ assert_equal expected[:name], actual[:name]
269
+ end
270
+
271
+ def assert_address(expected, actual)
272
+ assert_set [ :id, :city, :state ], actual.keys
273
+ assert_equal expected[:id], actual[:id]
274
+ assert_equal expected[:city], actual[:city]
275
+ assert_equal expected[:state], actual[:state]
276
+ end
277
+
278
+ def assert_category(expected, actual, options = {})
279
+ expected_keys = [ :id, :name ] + Array(options[:with])
280
+ assert_equal expected_keys, actual.keys
281
+ assert_equal expected[:id], actual[:id]
282
+ assert_equal expected[:name], actual[:name]
283
+ end
284
+
285
+ #----------------------------------------------------------------------------------------------
286
+
287
+ end
288
+ end
data/test/basic_test.rb CHANGED
@@ -6,15 +6,20 @@ module RubySerializer
6
6
  #----------------------------------------------------------------------------------------------
7
7
 
8
8
  def test_gem_information
9
- assert_equal '0.0.1', VERSION
9
+ assert_equal '1.0.0', VERSION
10
10
  assert_equal 'Serialize POROs to JSON', SUMMARY
11
11
  assert_match 'A general purpose library for', DESCRIPTION
12
12
  end
13
13
 
14
14
  #----------------------------------------------------------------------------------------------
15
15
 
16
- Shape = Struct.new(:color, :shape)
17
- Car = Struct.new(:make, :model)
16
+ class Shape < Model
17
+ attr :color, :shape
18
+ end
19
+
20
+ class Car < Model
21
+ attr :make, :model
22
+ end
18
23
 
19
24
  class ShapeSerializer < RubySerializer::Base
20
25
  expose :color
@@ -30,12 +35,12 @@ module RubySerializer
30
35
 
31
36
  def test_basic_serialization
32
37
 
33
- json = RubySerializer.as_json Shape.new(:red, :square)
38
+ json = serialize Shape.new(color: :red, shape: :square)
34
39
  assert_set [ :color, :shape ], json.keys
35
40
  assert_equal :red, json[:color]
36
41
  assert_equal :square, json[:shape]
37
42
 
38
- json = RubySerializer.as_json Car.new(:honda, :civic)
43
+ json = serialize Car.new(make: :honda, model: :civic)
39
44
  assert_set [ :make, :model ], json.keys
40
45
  assert_equal :honda, json[:make]
41
46
  assert_equal :civic, json[:model]
@@ -46,12 +51,12 @@ module RubySerializer
46
51
 
47
52
  def test_array_serialization
48
53
  resources = [
49
- Shape.new(:red, :square),
50
- Shape.new(:blue, :circle),
51
- Car.new(:honda, :civic),
52
- Car.new(:jeep, :wrangler)
54
+ Shape.new(color: :red, shape: :square),
55
+ Shape.new(color: :blue, shape: :circle),
56
+ Car.new(make: :honda, model: :civic),
57
+ Car.new(make: :jeep, model: :wrangler)
53
58
  ]
54
- json = RubySerializer.as_json resources
59
+ json = serialize resources
55
60
  assert_equal resources.length, json.length
56
61
  assert_set [ :color, :shape ], json[0].keys
57
62
  assert_equal :red, json[0][:color]
@@ -70,7 +75,7 @@ module RubySerializer
70
75
  #----------------------------------------------------------------------------------------------
71
76
 
72
77
  def test_serialize_with_root
73
- json = RubySerializer.as_json Car.new(:honda, :civic), root: :car
78
+ json = serialize Car.new(make: :honda, model: :civic), root: :car
74
79
  assert_set [ :car ], json.keys
75
80
  assert_equal :honda, json[:car][:make]
76
81
  assert_equal :civic, json[:car][:model]
@@ -79,8 +84,8 @@ module RubySerializer
79
84
  #----------------------------------------------------------------------------------------------
80
85
 
81
86
  def test_serialize_with_errors
82
- resource = OpenStruct.new(color: :red, shape: :sausages, valid?: false, errors: { shape: 'is invalid' })
83
- json = RubySerializer.as_json resource, with: ShapeSerializer
87
+ resource = Shape.new(color: :red, shape: :sausages, valid: false, errors: { shape: 'is invalid' })
88
+ json = serialize resource, with: ShapeSerializer
84
89
  expected = [ :color, :shape, :errors ]
85
90
  assert_set expected, json.keys
86
91
  assert_equal :red, json[:color]
data/test/expose_test.rb CHANGED
@@ -10,7 +10,14 @@ module RubySerializer
10
10
  EMAIL = 'Email'
11
11
  SECRET = 'Secret'
12
12
 
13
- Resource = OpenStruct
13
+ class Resource < Model
14
+ attr :id
15
+ attr :name
16
+ attr :email
17
+ attr :secret # this attribute will not be exposed in any serializer
18
+ attr :value # this attribute might be used as a custom :value in some serializers
19
+ attr :show # this attribute might be used to conditionally show :only or :unless
20
+ end
14
21
 
15
22
  #==============================================================================================
16
23
  # Sample Serializers
@@ -45,9 +52,8 @@ module RubySerializer
45
52
  class CustomValueSerializer < RubySerializer::Base
46
53
  expose :id
47
54
  expose :static, value: 'static value'
48
- expose :method, value: :method_value
49
55
  expose :dynamic, value: -> { "dynamic value (#{resource.name})" }
50
- expose :empty, value: nil
56
+ expose :method, value: :value
51
57
  end
52
58
 
53
59
  #----------------------------------------------------------------------------------------------
@@ -89,7 +95,7 @@ module RubySerializer
89
95
 
90
96
  def test_expose_attributes_unchanged
91
97
  resource = Resource.new(id: ID, name: NAME, email: EMAIL, secret: SECRET)
92
- json = RubySerializer.as_json resource, with: BasicSerializer
98
+ json = serialize resource, with: BasicSerializer
93
99
  expected = [ :id, :name, :email ]
94
100
  assert_set expected, json.keys
95
101
  assert_equal ID, json[:id]
@@ -101,7 +107,7 @@ module RubySerializer
101
107
 
102
108
  def test_expose_renamed_attributes
103
109
  resource = Resource.new(id: ID, name: NAME, email: EMAIL, secret: SECRET)
104
- json = RubySerializer.as_json resource, with: RenamingSerializer
110
+ json = serialize resource, with: RenamingSerializer
105
111
  expected = [ :id, :user_name, :user_email ]
106
112
  assert_set expected, json.keys
107
113
  assert_equal ID, json[:id]
@@ -113,7 +119,7 @@ module RubySerializer
113
119
 
114
120
  def test_expose_namespaced_attributes
115
121
  resource = Resource.new(id: ID, name: NAME, email: EMAIL, secret: SECRET)
116
- json = RubySerializer.as_json resource, with: NamespaceSerializer
122
+ json = serialize resource, with: NamespaceSerializer
117
123
  expected = [ :id, :user ]
118
124
  assert_set expected, json.keys
119
125
  assert_equal ID, json[:id]
@@ -125,23 +131,22 @@ module RubySerializer
125
131
  #----------------------------------------------------------------------------------------------
126
132
 
127
133
  def test_expose_attributes_with_custom_values
128
- resource = Resource.new(id: ID, name: NAME, method_value: 'method value')
129
- json = RubySerializer.as_json resource, with: CustomValueSerializer
130
- expected = [ :id, :static, :dynamic, :method, :empty ]
134
+ resource = Resource.new(id: ID, name: NAME, value: 'method value')
135
+ json = serialize resource, with: CustomValueSerializer
136
+ expected = [ :id, :static, :dynamic, :method ]
131
137
  assert_set expected, json.keys
132
138
  assert_equal ID, json[:id]
133
139
  assert_equal 'static value', json[:static]
134
140
  assert_equal 'dynamic value (Name)', json[:dynamic]
135
141
  assert_equal 'method value', json[:method]
136
- assert_equal nil, json[:empty]
137
142
  end
138
143
 
139
144
  #----------------------------------------------------------------------------------------------
140
145
 
141
146
  def test_expose_attributes_conditionally
142
- show = RubySerializer.as_json Resource.new(id: ID, show: true), with: ConditionalSerializer
143
- noshow = RubySerializer.as_json Resource.new(id: ID, show: false), with: ConditionalSerializer
144
- unspecified = RubySerializer.as_json Resource.new(id: ID), with: ConditionalSerializer
147
+ show = serialize Resource.new(id: ID, show: true), with: ConditionalSerializer
148
+ noshow = serialize Resource.new(id: ID, show: false), with: ConditionalSerializer
149
+ unspecified = serialize Resource.new(id: ID), with: ConditionalSerializer
145
150
  assert_set [ :id, :only_true, :unless_false, :only_method, :only_dynamic ], show.keys
146
151
  assert_set [ :id, :only_true, :unless_false, :unless_method, :unless_dynamic ], noshow.keys
147
152
  assert_set [ :id, :only_true, :unless_false, :unless_method, :unless_dynamic ], unspecified.keys
@@ -152,7 +157,7 @@ module RubySerializer
152
157
  def test_expose_attributes_using_custom_resource_name
153
158
 
154
159
  resource = Resource.new(id: ID, value: 42, show: false)
155
- json = RubySerializer.as_json resource, with: CustomResourceNameSerializer
160
+ json = serialize resource, with: CustomResourceNameSerializer
156
161
  expected = [ :id, :value, :show_unless ]
157
162
  assert_set expected, json.keys
158
163
  assert_equal ID, json[:id]
@@ -160,7 +165,7 @@ module RubySerializer
160
165
  assert_equal "show unless", json[:show_unless]
161
166
 
162
167
  resource = Resource.new(id: ID, value: 99, show: true)
163
- json = RubySerializer.as_json resource, with: CustomResourceNameSerializer
168
+ json = serialize resource, with: CustomResourceNameSerializer
164
169
  expected = [ :id, :value, :show_only ]
165
170
  assert_set expected, json.keys
166
171
  assert_equal ID, json[:id]
@@ -175,14 +180,14 @@ module RubySerializer
175
180
 
176
181
  resource = Resource.new(id: ID)
177
182
 
178
- json = RubySerializer.as_json resource, value: 42, show: false, with: CustomOptionsSerializer
183
+ json = serialize resource, value: 42, show: false, with: CustomOptionsSerializer
179
184
  expected = [ :id, :value, :show_unless ]
180
185
  assert_set expected, json.keys
181
186
  assert_equal ID, json[:id]
182
187
  assert_equal "value is 42", json[:value]
183
188
  assert_equal "show unless", json[:show_unless]
184
189
 
185
- json = RubySerializer.as_json resource, value: 99, show: true, with: CustomOptionsSerializer
190
+ json = serialize resource, value: 99, show: true, with: CustomOptionsSerializer
186
191
  expected = [ :id, :value, :show_only ]
187
192
  assert_set expected, json.keys
188
193
  assert_equal ID, json[:id]
data/test/model.rb ADDED
@@ -0,0 +1,66 @@
1
+ module RubySerializer
2
+ class Model # simple model with ActiveRecord like associations
3
+
4
+ def initialize(attributes = {})
5
+ @valid = true
6
+ @errors = []
7
+ attributes.each do |key, value|
8
+ instance_variable_set "@#{key}", value
9
+ end
10
+ end
11
+
12
+ def valid?
13
+ @valid
14
+ end
15
+
16
+ def errors
17
+ @errors
18
+ end
19
+
20
+ #----------------------------------------------------------------------------------------------
21
+
22
+ def self.belongs_to(key)
23
+ idkey = "#{key}_id"
24
+ instvar = "@#{key}"
25
+ idinstvar = "@#{idkey}"
26
+ define_method key do
27
+ instance_variable_get instvar
28
+ end
29
+ define_method idkey do
30
+ instance_variable_get idinstvar
31
+ end
32
+ define_method "#{key}=" do |value|
33
+ instance_variable_set instvar, value
34
+ instance_variable_set idinstvar, value ? value.id : nil
35
+ end
36
+ end
37
+
38
+ #----------------------------------------------------------------------------------------------
39
+
40
+ def self.has_one(key)
41
+ instvar = "@#{key}"
42
+ define_method key do
43
+ instance_variable_get instvar
44
+ end
45
+ define_method "#{key}=" do |value|
46
+ instance_variable_set instvar, value
47
+ end
48
+ end
49
+
50
+ #----------------------------------------------------------------------------------------------
51
+
52
+ def self.has_many(key)
53
+ instvar = "@#{key}"
54
+ define_method key do
55
+ instance_variable_set instvar, [] unless instance_variable_defined?(instvar)
56
+ instance_variable_get instvar
57
+ end
58
+ define_method "#{key}=" do |value|
59
+ instance_variable_set instvar, Array(value)
60
+ end
61
+ end
62
+
63
+ #----------------------------------------------------------------------------------------------
64
+
65
+ end
66
+ end
data/test/test_case.rb CHANGED
@@ -2,13 +2,22 @@ require_relative '../lib/ruby_serializer'
2
2
 
3
3
  require 'minitest/autorun'
4
4
  require 'minitest/pride'
5
+ require 'ostruct'
5
6
  require 'byebug'
6
7
 
8
+ require_relative 'model'
9
+
7
10
  module RubySerializer
8
11
  class TestCase < Minitest::Test
9
12
 
10
13
  #----------------------------------------------------------------------------------------------
11
14
 
15
+ def serialize(resource, options = {})
16
+ RubySerializer.as_json resource, options
17
+ end
18
+
19
+ #----------------------------------------------------------------------------------------------
20
+
12
21
  def assert_set(expected, actual, message = nil)
13
22
  assert_equal Set.new(expected), Set.new(actual), message
14
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-serializer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jake Gordon
@@ -81,13 +81,16 @@ files:
81
81
  - LICENSE
82
82
  - Rakefile
83
83
  - lib/ruby_serializer.rb
84
+ - lib/ruby_serializer/association.rb
84
85
  - lib/ruby_serializer/base.rb
85
86
  - lib/ruby_serializer/dsl.rb
86
87
  - lib/ruby_serializer/field.rb
87
88
  - readme.md
88
89
  - ruby-serializer.gemspec
90
+ - test/association_test.rb
89
91
  - test/basic_test.rb
90
92
  - test/expose_test.rb
93
+ - test/model.rb
91
94
  - test/test_case.rb
92
95
  homepage: https://github.com/jakesgordon/ruby-serializer
93
96
  licenses: