Skip to content

Commit c3d42bb

Browse files
committed
Support hashes with string keys
Before this change the only way I could find to access a hash with string keys was to either convert it to a symbol hash or define methods in the entity. This should at least allow the entire hash to be accessed with string keys yet default to existing behaviour (symbol keys).
1 parent 1443468 commit c3d42bb

File tree

8 files changed

+64
-9
lines changed

8 files changed

+64
-9
lines changed

.rubocop.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ AllCops:
99
Layout/EmptyLinesAroundArguments:
1010
Enabled: false
1111

12-
Layout/IndentHash:
12+
Layout/IndentFirstHashElement:
1313
EnforcedStyle: consistent
1414

1515
Metrics/AbcSize:

CHANGELOG.md

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
### Next
22

3+
* [#319](https://github.com/ruby-grape/grape-entity/pull/319): Support hashes with string keys - [@mhenrixon](https://github.com/mhenrixon).
4+
35
#### Features
46

57
* Your contribution here.

lib/grape_entity/delegator/fetchable_object.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
class Entity
55
module Delegator
66
class FetchableObject < Base
7-
def delegate(attribute)
7+
def delegate(attribute, **)
88
object.fetch attribute
99
end
1010
end

lib/grape_entity/delegator/hash_object.rb

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
# frozen_string_literal: true
22

3+
require 'pry'
4+
35
module Grape
46
class Entity
57
module Delegator
68
class HashObject < Base
7-
def delegate(attribute)
8-
object[attribute]
9+
def delegate(attribute, hash_access: :to_sym)
10+
object[attribute.send(hash_access)]
911
end
1012
end
1113
end

lib/grape_entity/delegator/openstruct_object.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
class Entity
55
module Delegator
66
class OpenStructObject < Base
7-
def delegate(attribute)
7+
def delegate(attribute, **)
88
object.send attribute
99
end
1010
end

lib/grape_entity/delegator/plain_object.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Grape
44
class Entity
55
module Delegator
66
class PlainObject < Base
7-
def delegate(attribute)
7+
def delegate(attribute, **)
88
object.send attribute
99
end
1010

lib/grape_entity/entity.rb

+18-2
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,22 @@ def formatters
114114
end
115115

116116
attr_writer :formatters
117+
118+
def hash_access
119+
@hash_access ||= :to_sym
120+
end
121+
122+
def hash_access=(value)
123+
@hash_access =
124+
case value
125+
when :to_s, :str, :string
126+
:to_s
127+
when :to_sym, :sym, :symbol
128+
:to_sym
129+
else
130+
:to_sym
131+
end
132+
end
117133
end
118134

119135
@formatters = {}
@@ -459,8 +475,8 @@ def inspect
459475

460476
def initialize(object, options = {})
461477
@object = object
462-
@delegator = Delegator.new(object)
463478
@options = options.is_a?(Options) ? options : Options.new(options)
479+
@delegator = Delegator.new(object)
464480
end
465481

466482
def root_exposures
@@ -514,7 +530,7 @@ def delegate_attribute(attribute)
514530
if is_defined_in_entity?(attribute)
515531
send(attribute)
516532
else
517-
delegator.delegate(attribute)
533+
delegator.delegate(attribute, hash_access: self.class.hash_access)
518534
end
519535
end
520536

spec/grape_entity/hash_spec.rb

+36-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
require 'spec_helper'
44

55
describe Grape::Entity do
6-
it 'except option for nested entity' do
6+
it 'except option for nested entity', :aggregate_failures do
77
module EntitySpec
88
class Address < Grape::Entity
99
expose :post, if: :full
@@ -12,13 +12,30 @@ class Address < Grape::Entity
1212
expose :house
1313
end
1414

15+
class AddressWithString < Grape::Entity
16+
self.hash_access = :string
17+
expose :post, if: :full
18+
expose :city
19+
expose :street
20+
expose :house
21+
end
22+
1523
class Company < Grape::Entity
1624
expose :full_name, if: :full
1725
expose :name
1826
expose :address do |c, o|
1927
Address.represent c[:address], Grape::Entity::Options.new(o.opts_hash.except(:full))
2028
end
2129
end
30+
31+
class CompanyWithString < Grape::Entity
32+
self.hash_access = :string
33+
expose :full_name, if: :full
34+
expose :name
35+
expose :address do |c, o|
36+
AddressWithString.represent c['address'], Grape::Entity::Options.new(o.opts_hash.except(:full))
37+
end
38+
end
2239
end
2340

2441
company = {
@@ -33,6 +50,24 @@ class Company < Grape::Entity
3350
}
3451
}
3552

53+
company_with_string = {
54+
'full_name' => 'full_name',
55+
'name' => 'name',
56+
'address' => {
57+
'post' => '123456',
58+
'city' => 'city',
59+
'street' => 'street',
60+
'house' => 'house',
61+
'something_else' => 'something_else'
62+
}
63+
}
64+
65+
expect(EntitySpec::CompanyWithString.represent(company_with_string).serializable_hash).to eq \
66+
company.slice(:name).merge(address: company[:address].slice(:city, :street, :house))
67+
68+
expect(EntitySpec::CompanyWithString.represent(company_with_string, full: true).serializable_hash).to eq \
69+
company.slice(:full_name, :name).merge(address: company[:address].slice(:city, :street, :house))
70+
3671
expect(EntitySpec::Company.represent(company).serializable_hash).to eq \
3772
company.slice(:name).merge(address: company[:address].slice(:city, :street, :house))
3873

0 commit comments

Comments
 (0)