halitosis 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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"
|