halitosis 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +32 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/LICENSE.txt +21 -0
- data/README.md +360 -0
- data/Rakefile +12 -0
- data/lib/halitosis/base.rb +123 -0
- data/lib/halitosis/collection/field.rb +8 -0
- data/lib/halitosis/collection.rb +99 -0
- data/lib/halitosis/configuration.rb +13 -0
- data/lib/halitosis/errors.rb +11 -0
- data/lib/halitosis/field.rb +73 -0
- data/lib/halitosis/fields.rb +18 -0
- data/lib/halitosis/hash_util.rb +41 -0
- data/lib/halitosis/links/field.rb +76 -0
- data/lib/halitosis/links.rb +43 -0
- data/lib/halitosis/meta/field.rb +8 -0
- data/lib/halitosis/meta.rb +43 -0
- data/lib/halitosis/permissions/field.rb +8 -0
- data/lib/halitosis/permissions.rb +43 -0
- data/lib/halitosis/properties/field.rb +8 -0
- data/lib/halitosis/properties.rb +40 -0
- data/lib/halitosis/railtie.rb +23 -0
- data/lib/halitosis/relationships/field.rb +37 -0
- data/lib/halitosis/relationships.rb +80 -0
- data/lib/halitosis/resource.rb +58 -0
- data/lib/halitosis/version.rb +5 -0
- data/lib/halitosis.rb +85 -0
- data/sig/halitosis.rbs +4 -0
- metadata +77 -0
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
# Behavior for serializers with a primary collection resource.
|
5
|
+
#
|
6
|
+
# The main reason to declare a collection is that the resource with that name
|
7
|
+
# will always be included during rendering.
|
8
|
+
#
|
9
|
+
module Collection
|
10
|
+
def self.included(base)
|
11
|
+
raise InvalidCollection, "#{base.name} has already defined a resource" if base.included_modules.include?(Resource)
|
12
|
+
|
13
|
+
base.extend ClassMethods
|
14
|
+
|
15
|
+
base.send :include, InstanceMethods
|
16
|
+
|
17
|
+
base.send :attr_reader, :collection
|
18
|
+
|
19
|
+
base.class.send :attr_accessor, :collection_name
|
20
|
+
end
|
21
|
+
|
22
|
+
module ClassMethods
|
23
|
+
# @param name [Symbol, String] name of the collection
|
24
|
+
#
|
25
|
+
# @return [Module] self
|
26
|
+
#
|
27
|
+
def define_collection(name, options = {}, &procedure)
|
28
|
+
raise InvalidCollection, "#{self.name} collection is already defined" if fields.key?(Field.name)
|
29
|
+
|
30
|
+
self.collection_name = name.to_s
|
31
|
+
|
32
|
+
alias_method name, :collection
|
33
|
+
|
34
|
+
fields.add Field.new(name, options, procedure)
|
35
|
+
end
|
36
|
+
|
37
|
+
def collection?
|
38
|
+
true
|
39
|
+
end
|
40
|
+
|
41
|
+
def collection_field
|
42
|
+
fields[Field.name].last || raise(InvalidCollection, "#{name} collection is not defined")
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
module InstanceMethods
|
47
|
+
# Override standard initializer to assign primary collection
|
48
|
+
#
|
49
|
+
# @param collection [Object] the primary collection
|
50
|
+
#
|
51
|
+
def initialize(collection, **)
|
52
|
+
@collection = collection
|
53
|
+
|
54
|
+
super(**)
|
55
|
+
end
|
56
|
+
|
57
|
+
# @return [Hash] the rendered hash with collection, if any
|
58
|
+
#
|
59
|
+
def render
|
60
|
+
field = self.class.collection_field
|
61
|
+
if depth.zero?
|
62
|
+
super.merge(field.name => render_collection_field(field))
|
63
|
+
else
|
64
|
+
render_collection_field(field)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# @return [Hash] collection from fields
|
69
|
+
#
|
70
|
+
def render_collection_field(field)
|
71
|
+
value = instance_eval(&field.procedure)
|
72
|
+
value.map { |child| render_child(child, collection_opts) }
|
73
|
+
end
|
74
|
+
|
75
|
+
def collection?
|
76
|
+
true
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# @param key [String]
|
82
|
+
#
|
83
|
+
# @return [Hash]
|
84
|
+
#
|
85
|
+
def collection_opts
|
86
|
+
return include_options if depth.positive?
|
87
|
+
|
88
|
+
opts = include_options.fetch(self.class.collection_field.name.to_s, {})
|
89
|
+
|
90
|
+
# Turn { :report => 1 } into { :report => {} } for child
|
91
|
+
opts = {} unless opts.is_a?(Hash)
|
92
|
+
|
93
|
+
opts
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
require "halitosis/collection/field"
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
# Stores instructions for how to render a value for a given serializer
|
5
|
+
# instance
|
6
|
+
#
|
7
|
+
class Field
|
8
|
+
attr_reader :name, :options
|
9
|
+
|
10
|
+
attr_accessor :procedure
|
11
|
+
|
12
|
+
# Construct a new Field instance
|
13
|
+
#
|
14
|
+
# @param name [Symbol, String] Field name
|
15
|
+
# @param options [Hash] hash of options
|
16
|
+
#
|
17
|
+
# @return [Halitosis::Field] the instance
|
18
|
+
#
|
19
|
+
def initialize(name, options, procedure)
|
20
|
+
@name = name.to_sym
|
21
|
+
@options = Halitosis::HashUtil.symbolize_params(options)
|
22
|
+
@procedure = procedure
|
23
|
+
end
|
24
|
+
|
25
|
+
# @param instance [Object] the serializer instance with which to evaluate
|
26
|
+
# the stored procedure
|
27
|
+
#
|
28
|
+
def value(instance)
|
29
|
+
options.fetch(:value) do
|
30
|
+
procedure ? instance.instance_eval(&procedure) : instance.send(name)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# @return [true, false] whether this Field should be included based on
|
35
|
+
# its conditional guard, if any
|
36
|
+
#
|
37
|
+
def enabled?(instance)
|
38
|
+
if options.key?(:if)
|
39
|
+
!!eval_guard(instance, options.fetch(:if))
|
40
|
+
elsif options.key?(:unless)
|
41
|
+
!eval_guard(instance, options.fetch(:unless))
|
42
|
+
else
|
43
|
+
true
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# @return [true] if nothing is raised
|
48
|
+
#
|
49
|
+
# @raise [Halitosis::InvalidField] if the Field is invalid
|
50
|
+
#
|
51
|
+
def validate
|
52
|
+
return true unless options.key?(:value) && procedure
|
53
|
+
|
54
|
+
raise InvalidField,
|
55
|
+
"Cannot specify both value and procedure for #{name}"
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
# Evaluate guard procedure or method
|
61
|
+
#
|
62
|
+
def eval_guard(instance, guard)
|
63
|
+
case guard
|
64
|
+
when Proc
|
65
|
+
instance.instance_eval(&guard)
|
66
|
+
when Symbol, String
|
67
|
+
instance.send(guard)
|
68
|
+
else
|
69
|
+
guard
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
# Each serializer class has a Fields object that stores the fields that have been defined on it.
|
5
|
+
#
|
6
|
+
class Fields < Hash
|
7
|
+
def add(field)
|
8
|
+
type = field.class.name
|
9
|
+
|
10
|
+
field.validate
|
11
|
+
|
12
|
+
self[type] ||= []
|
13
|
+
self[type] << field
|
14
|
+
|
15
|
+
field
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module HashUtil
|
5
|
+
module_function
|
6
|
+
|
7
|
+
# Transform hash keys into strings if necessary
|
8
|
+
#
|
9
|
+
# @param hash [Hash, Array, String]
|
10
|
+
#
|
11
|
+
# @return [Hash]
|
12
|
+
#
|
13
|
+
def stringify_params(hash)
|
14
|
+
case hash
|
15
|
+
when String
|
16
|
+
hash.split(",").inject({}) do |output, key|
|
17
|
+
f, value = key.split(".", 2)
|
18
|
+
output.merge(f => value ? stringify_params(value) : true)
|
19
|
+
end
|
20
|
+
when Array
|
21
|
+
hash.map { |item| stringify_params(item) }.inject({}, &:merge)
|
22
|
+
else
|
23
|
+
hash.transform_keys(&:to_s)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Transform hash keys into symbols if necessary
|
28
|
+
#
|
29
|
+
# @param hash [Hash]
|
30
|
+
#
|
31
|
+
# @return [Hash]
|
32
|
+
#
|
33
|
+
def symbolize_params(hash)
|
34
|
+
if hash.respond_to?(:transform_keys)
|
35
|
+
hash.transform_keys(&:to_sym).transform_values(&method(:symbolize_params))
|
36
|
+
else
|
37
|
+
hash
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Links
|
5
|
+
class Field < Halitosis::Field
|
6
|
+
# Links have special keywords that other fields don't, so override
|
7
|
+
# the standard initializer to build options from keywords
|
8
|
+
#
|
9
|
+
def initialize(name, *args, procedure)
|
10
|
+
super(name, self.class.build_options(args), procedure)
|
11
|
+
end
|
12
|
+
|
13
|
+
# @return [true] if nothing is raised
|
14
|
+
#
|
15
|
+
# @raise [Halitosis::InvalidField] if the field is invalid
|
16
|
+
#
|
17
|
+
def validate
|
18
|
+
super
|
19
|
+
|
20
|
+
return true if procedure || options.key?(:value)
|
21
|
+
|
22
|
+
raise InvalidField,
|
23
|
+
"Link #{name} requires either procedure or explicit value"
|
24
|
+
end
|
25
|
+
|
26
|
+
# @return [nil, Hash]
|
27
|
+
#
|
28
|
+
def value(_instance)
|
29
|
+
hrefs = super
|
30
|
+
|
31
|
+
attrs = options.fetch(:attrs, {})
|
32
|
+
|
33
|
+
case hrefs
|
34
|
+
when Array
|
35
|
+
hrefs.map { |href| attrs.merge(href:) }
|
36
|
+
when nil
|
37
|
+
# no-op
|
38
|
+
else
|
39
|
+
attrs.merge(href: hrefs)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class << self
|
44
|
+
# Build hash of options from flexible field arguments
|
45
|
+
#
|
46
|
+
# @param args [Array] the raw field arguments
|
47
|
+
#
|
48
|
+
# @return [Hash] standardized hash of options
|
49
|
+
#
|
50
|
+
def build_options(args)
|
51
|
+
{}.tap do |options|
|
52
|
+
options.merge!(args.pop) if args.last.is_a?(Hash)
|
53
|
+
|
54
|
+
options[:attrs] ||= {}
|
55
|
+
options[:attrs].merge!(build_attrs(args))
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# @param keywords [Array] array of special keywords
|
60
|
+
#
|
61
|
+
# @raise [Halitosis::InvalidField] if a keyword is unrecognized
|
62
|
+
#
|
63
|
+
def build_attrs(keywords)
|
64
|
+
keywords.each_with_object({}) do |keyword, attrs|
|
65
|
+
case keyword
|
66
|
+
when :templated, "templated"
|
67
|
+
attrs[:templated] = true
|
68
|
+
else
|
69
|
+
raise InvalidField, "Unrecognized link keyword: #{keyword}"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Links
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# @return [Halitosis::Links::Field]
|
13
|
+
#
|
14
|
+
def link(name, *, &procedure)
|
15
|
+
fields.add(Field.new(name, *, procedure))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
# @return [Hash] the rendered hash with links, if any
|
21
|
+
#
|
22
|
+
def render
|
23
|
+
if options.fetch(:include_links, true)
|
24
|
+
decorate_render :links, super
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Hash] links from fields
|
31
|
+
#
|
32
|
+
def links
|
33
|
+
render_fields(Field.name) do |field, result|
|
34
|
+
value = field.value(self)
|
35
|
+
|
36
|
+
result[field.name] = value if value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require "halitosis/links/field"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Meta
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# @return [Halitosis::Meta::Field]
|
13
|
+
#
|
14
|
+
def meta(name, **options, &procedure)
|
15
|
+
fields.add(Field.new(name, options, procedure))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
# @return [Hash] the rendered hash with meta, if any
|
21
|
+
#
|
22
|
+
def render
|
23
|
+
if options.fetch(:include_meta, true)
|
24
|
+
decorate_render :meta, super
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Hash] meta from fields
|
31
|
+
#
|
32
|
+
def meta
|
33
|
+
render_fields(Field.name) do |field, result|
|
34
|
+
value = field.value(self)
|
35
|
+
|
36
|
+
result[field.name] = value if value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require "halitosis/meta/field"
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Permissions
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# @return [Halitosis::Permissions::Field]
|
13
|
+
#
|
14
|
+
def permission(name, **options, &procedure)
|
15
|
+
fields.add(Field.new(name, options, procedure))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module InstanceMethods
|
20
|
+
# @return [Hash] the rendered hash with permissions, if any
|
21
|
+
#
|
22
|
+
def render
|
23
|
+
if options.fetch(:include_permissions, true)
|
24
|
+
decorate_render :permissions, super
|
25
|
+
else
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Hash] permissions from fields
|
31
|
+
#
|
32
|
+
def permissions
|
33
|
+
render_fields(Field.name) do |field, result|
|
34
|
+
value = field.value(self)
|
35
|
+
|
36
|
+
result[field.name] = value if value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
require "halitosis/permissions/field"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Properties
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# @param name [Symbol, String]
|
13
|
+
# @param options [nil, Hash]
|
14
|
+
#
|
15
|
+
# @return [Halitosis::Properties::Field]
|
16
|
+
#
|
17
|
+
def property(name, options = {}, &procedure)
|
18
|
+
fields.add(Field.new(name, options, procedure))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
module InstanceMethods
|
23
|
+
# @return [Hash] the rendered hash with properties, if any
|
24
|
+
#
|
25
|
+
def render
|
26
|
+
super.merge(properties)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @return [Hash] properties from fields
|
30
|
+
#
|
31
|
+
def properties
|
32
|
+
render_fields(Field.name) do |field, result|
|
33
|
+
result[field.name] = field.value(self)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
require "halitosis/properties/field"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Halitosis
|
2
|
+
# Provide Rails-specific extensions if loaded in a Rails application
|
3
|
+
#
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
module Renderable
|
6
|
+
def render_in(view_context)
|
7
|
+
view_context.render json: self
|
8
|
+
end
|
9
|
+
|
10
|
+
def format
|
11
|
+
:json
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
initializer "halitosis.url_helpers" do |_app|
|
16
|
+
Halitosis.config.extensions << ::Rails.application.routes.url_helpers
|
17
|
+
end
|
18
|
+
|
19
|
+
initializer "halitosis.renderable" do |_app|
|
20
|
+
Halitosis.config.extensions << Renderable
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Relationships
|
5
|
+
class Field < Halitosis::Field
|
6
|
+
# @return [true] if nothing is raised
|
7
|
+
#
|
8
|
+
# @raise [Halitosis::InvalidField] if the definition is invalid
|
9
|
+
#
|
10
|
+
def validate
|
11
|
+
super
|
12
|
+
|
13
|
+
return true if procedure
|
14
|
+
|
15
|
+
raise InvalidField, "Relationship #{name} must be defined with a proc"
|
16
|
+
end
|
17
|
+
|
18
|
+
# Check whether this definition should be included for the given instance
|
19
|
+
#
|
20
|
+
# @param instance [Object]
|
21
|
+
#
|
22
|
+
# @return [true, false]
|
23
|
+
#
|
24
|
+
def enabled?(instance)
|
25
|
+
return false unless super
|
26
|
+
|
27
|
+
opts = instance.include_options
|
28
|
+
|
29
|
+
# Field name must appear in instance included option keys
|
30
|
+
return false unless opts.include?(name.to_s)
|
31
|
+
|
32
|
+
# Check value of included option for definition name
|
33
|
+
!%w[0 false].include?(opts.fetch(name.to_s).to_s)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Halitosis
|
4
|
+
module Relationships
|
5
|
+
def self.included(base)
|
6
|
+
base.extend ClassMethods
|
7
|
+
|
8
|
+
base.send :include, InstanceMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
# @param name [Symbol, String]
|
13
|
+
# @param options [nil, Hash]
|
14
|
+
#
|
15
|
+
# @return [Halitosis::Relationships::Field]
|
16
|
+
#
|
17
|
+
def relationship(name, options = {}, &procedure)
|
18
|
+
fields.add(Field.new(name, options, procedure))
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :rel, :relationship
|
22
|
+
end
|
23
|
+
|
24
|
+
module InstanceMethods
|
25
|
+
# @return [Hash] the rendered hash with relationships resources, if any
|
26
|
+
#
|
27
|
+
def render
|
28
|
+
decorate_render :relationships, super
|
29
|
+
end
|
30
|
+
|
31
|
+
# @return [Hash] hash of rendered resources to include
|
32
|
+
#
|
33
|
+
def relationships
|
34
|
+
render_fields(Field.name) do |field, result|
|
35
|
+
value = instance_eval(&field.procedure)
|
36
|
+
|
37
|
+
child = relationships_child(field.name.to_s, value)
|
38
|
+
|
39
|
+
result[field.name] = child if child
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# @return [nil, Hash, Array<Hash>] either a single rendered child
|
44
|
+
# serializer or an array of them
|
45
|
+
#
|
46
|
+
def relationships_child(key, value)
|
47
|
+
return unless value
|
48
|
+
|
49
|
+
opts = child_relationship_opts(key)
|
50
|
+
|
51
|
+
if value.is_a?(Array)
|
52
|
+
value.map { |item| render_child(item, opts) }.compact
|
53
|
+
else
|
54
|
+
render_child(value, opts)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# @param key [String]
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
61
|
+
#
|
62
|
+
def child_relationship_opts(key)
|
63
|
+
opts = include_options.fetch(key, {})
|
64
|
+
|
65
|
+
# Turn { :report => 1 } into { :report => {} } for child
|
66
|
+
opts = {} unless opts.is_a?(Hash)
|
67
|
+
|
68
|
+
opts
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Hash] hash of options with top level string keys
|
72
|
+
#
|
73
|
+
def include_options
|
74
|
+
@include_options ||= Halitosis::HashUtil.stringify_params(options.fetch(:include, {}))
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
require "halitosis/relationships/field"
|