Skip to content

Commit 5daa2c9

Browse files
committed
Merge pull request #76 from justfalter/improve_performance
Improve performance for serialization of entities
2 parents d1f20a1 + 026c2d8 commit 5daa2c9

File tree

3 files changed

+136
-7
lines changed

3 files changed

+136
-7
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ Next Release
33

44
* Your contribution here.
55
* [#77](https://github.com/intridea/grape-entity/pull/77): Fix compatibility with Rspec 3 - [@justfalter](https://github.com/justfalter).
6+
* [#76](https://github.com/intridea/grape-entity/pull/76): Improve performance of entity serialization - [@justfalter](https://github.com/justfalter)
67

78
0.4.2 (2014-04-03)
89
==================

bench/serializing.rb

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2+
require 'grape-entity'
3+
require 'benchmark'
4+
5+
module Models
6+
class School
7+
attr_reader :classrooms
8+
def initialize
9+
@classrooms = []
10+
end
11+
end
12+
13+
class ClassRoom
14+
attr_reader :students
15+
attr_accessor :teacher
16+
def initialize(opts = {})
17+
@teacher = opts[:teacher]
18+
@students = []
19+
end
20+
end
21+
22+
class Person
23+
attr_accessor :name
24+
def initialize(opts = {})
25+
@name = opts[:name]
26+
end
27+
end
28+
29+
class Teacher < Models::Person
30+
attr_accessor :tenure
31+
def initialize(opts = {})
32+
super(opts)
33+
@tenure = opts[:tenure]
34+
end
35+
end
36+
37+
class Student < Models::Person
38+
attr_reader :grade
39+
def initialize(opts = {})
40+
super(opts)
41+
@grade = opts[:grade]
42+
end
43+
end
44+
end
45+
46+
module Entities
47+
class School < Grape::Entity
48+
expose :classrooms, using: 'Entities::ClassRoom'
49+
end
50+
51+
class ClassRoom < Grape::Entity
52+
expose :teacher, using: 'Entities::Teacher'
53+
expose :students, using: 'Entities::Student'
54+
expose :size do |model, _opts|
55+
model.students.count
56+
end
57+
end
58+
59+
class Person < Grape::Entity
60+
expose :name
61+
end
62+
63+
class Student < Entities::Person
64+
expose :grade
65+
expose :failing do |model, _opts|
66+
model.grade == 'F'
67+
end
68+
end
69+
70+
class Teacher < Entities::Person
71+
expose :tenure
72+
end
73+
end
74+
75+
teacher1 = Models::Teacher.new(name: 'John Smith', tenure: 2)
76+
classroom1 = Models::ClassRoom.new(teacher: teacher1)
77+
classroom1.students << Models::Student.new(name: 'Bobby', grade: 'A')
78+
classroom1.students << Models::Student.new(name: 'Billy', grade: 'B')
79+
80+
teacher2 = Models::Teacher.new(name: 'Lisa Barns')
81+
classroom2 = Models::ClassRoom.new(teacher: teacher2, tenure: 15)
82+
classroom2.students << Models::Student.new(name: 'Eric', grade: 'A')
83+
classroom2.students << Models::Student.new(name: 'Eddie', grade: 'C')
84+
classroom2.students << Models::Student.new(name: 'Arnie', grade: 'C')
85+
classroom2.students << Models::Student.new(name: 'Alvin', grade: 'F')
86+
school = Models::School.new
87+
school.classrooms << classroom1
88+
school.classrooms << classroom2
89+
90+
iters = 5000
91+
92+
Benchmark.bm do |bm|
93+
bm.report('serializing') do
94+
iters.times do
95+
Entities::School.represent(school, serializable: true)
96+
end
97+
end
98+
end

lib/grape_entity/entity.rb

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ def self.expose(*args, &block)
139139

140140
args.each do |attribute|
141141
unless @nested_attributes.empty?
142+
orig_attribute = attribute.to_sym
142143
attribute = "#{@nested_attributes.last}__#{attribute}"
144+
nested_attribute_names_hash[attribute.to_sym] = orig_attribute
143145
options[:nested] = true
144146
nested_exposures_hash[@nested_attributes.last.to_sym] ||= {}
145147
nested_exposures_hash[@nested_attributes.last.to_sym][attribute.to_sym] = options
@@ -175,7 +177,9 @@ def self.with_options(options)
175177
# are symbolized references to methods on the containing object, the values are
176178
# the options that were passed into expose.
177179
def self.exposures
178-
@exposures ||= {}
180+
return @exposures unless @exposures.nil?
181+
182+
@exposures = {}
179183

180184
if superclass.respond_to? :exposures
181185
@exposures = superclass.exposures.merge(@exposures)
@@ -185,20 +189,39 @@ def self.exposures
185189
end
186190

187191
class << self
192+
attr_accessor :_nested_attribute_names_hash
188193
attr_accessor :_nested_exposures_hash
189194

195+
def nested_attribute_names_hash
196+
self._nested_attribute_names_hash ||= {}
197+
end
198+
190199
def nested_exposures_hash
191200
self._nested_exposures_hash ||= {}
192201
end
193202

203+
def nested_attribute_names
204+
return @nested_attribute_names unless @nested_attribute_names.nil?
205+
206+
@nested_attribute_names = {}.merge(nested_attribute_names_hash)
207+
208+
if superclass.respond_to? :nested_attribute_names
209+
@nested_attribute_names = superclass.nested_attribute_names.deep_merge(@nested_attribute_names)
210+
end
211+
212+
@nested_attribute_names
213+
end
214+
194215
def nested_exposures
195-
value = nested_exposures_hash
216+
return @nested_exposures unless @nested_exposures.nil?
217+
218+
@nested_exposures = {}.merge(nested_exposures_hash)
196219

197220
if superclass.respond_to? :nested_exposures
198-
value = superclass.nested_exposures.deep_merge(value)
221+
@nested_exposures = superclass.nested_exposures.deep_merge(@nested_exposures)
199222
end
200223

201-
value
224+
@nested_exposures
202225
end
203226
end
204227

@@ -404,7 +427,8 @@ def to_xml(options = {})
404427
protected
405428

406429
def self.name_for(attribute)
407-
attribute.to_s.split('__').last.to_sym
430+
attribute = attribute.to_sym
431+
nested_attribute_names[attribute] || attribute
408432
end
409433

410434
def self.key_for(attribute)
@@ -475,7 +499,10 @@ def valid_exposure?(attribute, exposure_options)
475499
end
476500

477501
def conditions_met?(exposure_options, options)
478-
if_conditions = (exposure_options[:if_extras] || []).dup
502+
if_conditions = []
503+
unless exposure_options[:if_extras].nil?
504+
if_conditions.concat(exposure_options[:if_extras])
505+
end
479506
if_conditions << exposure_options[:if] unless exposure_options[:if].nil?
480507

481508
if_conditions.each do |if_condition|
@@ -486,7 +513,10 @@ def conditions_met?(exposure_options, options)
486513
end
487514
end
488515

489-
unless_conditions = (exposure_options[:unless_extras] || []).dup
516+
unless_conditions = []
517+
unless exposure_options[:unless_extras].nil?
518+
unless_conditions.concat(exposure_options[:unless_extras])
519+
end
490520
unless_conditions << exposure_options[:unless] unless exposure_options[:unless].nil?
491521

492522
unless_conditions.each do |unless_condition|

0 commit comments

Comments
 (0)