graphiti-rb 1.0.alpha.1
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/.gitignore +11 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +20 -0
- data/.yardopts +2 -0
- data/Appraisals +11 -0
- data/CODE_OF_CONDUCT.md +49 -0
- data/Gemfile +12 -0
- data/Guardfile +32 -0
- data/LICENSE.txt +21 -0
- data/README.md +75 -0
- data/Rakefile +15 -0
- data/bin/appraisal +17 -0
- data/bin/console +14 -0
- data/bin/rspec +17 -0
- data/bin/setup +8 -0
- data/gemfiles/rails_4.gemfile +17 -0
- data/gemfiles/rails_5.gemfile +17 -0
- data/graphiti.gemspec +34 -0
- data/lib/generators/jsonapi/resource_generator.rb +169 -0
- data/lib/generators/jsonapi/templates/application_resource.rb.erb +15 -0
- data/lib/generators/jsonapi/templates/controller.rb.erb +61 -0
- data/lib/generators/jsonapi/templates/create_request_spec.rb.erb +30 -0
- data/lib/generators/jsonapi/templates/destroy_request_spec.rb.erb +20 -0
- data/lib/generators/jsonapi/templates/index_request_spec.rb.erb +22 -0
- data/lib/generators/jsonapi/templates/resource.rb.erb +11 -0
- data/lib/generators/jsonapi/templates/resource_reads_spec.rb.erb +62 -0
- data/lib/generators/jsonapi/templates/resource_writes_spec.rb.erb +63 -0
- data/lib/generators/jsonapi/templates/show_request_spec.rb.erb +21 -0
- data/lib/generators/jsonapi/templates/update_request_spec.rb.erb +34 -0
- data/lib/graphiti-rb.rb +1 -0
- data/lib/graphiti.rb +121 -0
- data/lib/graphiti/adapters/abstract.rb +516 -0
- data/lib/graphiti/adapters/active_record.rb +6 -0
- data/lib/graphiti/adapters/active_record/base.rb +249 -0
- data/lib/graphiti/adapters/active_record/belongs_to_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_many_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/has_one_sideload.rb +17 -0
- data/lib/graphiti/adapters/active_record/inferrence.rb +12 -0
- data/lib/graphiti/adapters/active_record/many_to_many_sideload.rb +30 -0
- data/lib/graphiti/adapters/null.rb +236 -0
- data/lib/graphiti/base.rb +70 -0
- data/lib/graphiti/configuration.rb +21 -0
- data/lib/graphiti/context.rb +16 -0
- data/lib/graphiti/deserializer.rb +208 -0
- data/lib/graphiti/errors.rb +309 -0
- data/lib/graphiti/extensions/boolean_attribute.rb +33 -0
- data/lib/graphiti/extensions/extra_attribute.rb +70 -0
- data/lib/graphiti/extensions/temp_id.rb +26 -0
- data/lib/graphiti/filter_operators.rb +25 -0
- data/lib/graphiti/hash_renderer.rb +57 -0
- data/lib/graphiti/jsonapi_serializable_ext.rb +50 -0
- data/lib/graphiti/query.rb +251 -0
- data/lib/graphiti/rails.rb +28 -0
- data/lib/graphiti/railtie.rb +74 -0
- data/lib/graphiti/renderer.rb +60 -0
- data/lib/graphiti/resource.rb +110 -0
- data/lib/graphiti/resource/configuration.rb +239 -0
- data/lib/graphiti/resource/dsl.rb +138 -0
- data/lib/graphiti/resource/interface.rb +32 -0
- data/lib/graphiti/resource/polymorphism.rb +68 -0
- data/lib/graphiti/resource/sideloading.rb +102 -0
- data/lib/graphiti/resource_proxy.rb +127 -0
- data/lib/graphiti/responders.rb +19 -0
- data/lib/graphiti/runner.rb +25 -0
- data/lib/graphiti/scope.rb +98 -0
- data/lib/graphiti/scoping/base.rb +99 -0
- data/lib/graphiti/scoping/default_filter.rb +58 -0
- data/lib/graphiti/scoping/extra_attributes.rb +29 -0
- data/lib/graphiti/scoping/filter.rb +93 -0
- data/lib/graphiti/scoping/filterable.rb +36 -0
- data/lib/graphiti/scoping/paginate.rb +87 -0
- data/lib/graphiti/scoping/sort.rb +64 -0
- data/lib/graphiti/sideload.rb +281 -0
- data/lib/graphiti/sideload/belongs_to.rb +34 -0
- data/lib/graphiti/sideload/has_many.rb +16 -0
- data/lib/graphiti/sideload/has_one.rb +9 -0
- data/lib/graphiti/sideload/many_to_many.rb +24 -0
- data/lib/graphiti/sideload/polymorphic_belongs_to.rb +108 -0
- data/lib/graphiti/stats/dsl.rb +89 -0
- data/lib/graphiti/stats/payload.rb +49 -0
- data/lib/graphiti/types.rb +172 -0
- data/lib/graphiti/util/attribute_check.rb +88 -0
- data/lib/graphiti/util/field_params.rb +16 -0
- data/lib/graphiti/util/hash.rb +51 -0
- data/lib/graphiti/util/hooks.rb +33 -0
- data/lib/graphiti/util/include_params.rb +39 -0
- data/lib/graphiti/util/persistence.rb +219 -0
- data/lib/graphiti/util/relationship_payload.rb +64 -0
- data/lib/graphiti/util/serializer_attributes.rb +97 -0
- data/lib/graphiti/util/sideload.rb +33 -0
- data/lib/graphiti/util/validation_response.rb +78 -0
- data/lib/graphiti/version.rb +3 -0
- metadata +317 -0
@@ -0,0 +1,138 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Resource
|
3
|
+
module DSL
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
def filter(name, *args, &blk)
|
8
|
+
opts = args.extract_options!
|
9
|
+
|
10
|
+
if att = get_attr(name, :filterable, raise_error: :only_unsupported)
|
11
|
+
aliases = [name, opts[:aliases]].flatten.compact
|
12
|
+
operators = FilterOperators.build(&blk)
|
13
|
+
config[:filters][name.to_sym] = {
|
14
|
+
aliases: aliases,
|
15
|
+
type: att[:type]
|
16
|
+
}.merge(operators.to_hash)
|
17
|
+
else
|
18
|
+
if type = args[0]
|
19
|
+
attribute name, type, only: [:filterable]
|
20
|
+
filter(name, opts, &blk)
|
21
|
+
else
|
22
|
+
raise Errors::ImplicitFilterTypeMissing.new(self, name)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def sort_all(&blk)
|
28
|
+
if block_given?
|
29
|
+
config[:_sort_all] = blk
|
30
|
+
else
|
31
|
+
config[:_sort_all]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def sort(name, *args, &blk)
|
36
|
+
opts = args.extract_options!
|
37
|
+
|
38
|
+
if get_attr(name, :sortable, raise_error: :only_unsupported)
|
39
|
+
config[:sorts][name] = blk
|
40
|
+
else
|
41
|
+
if type = args[0]
|
42
|
+
attribute name, type, only: [:sortable]
|
43
|
+
sort(name, opts, &blk)
|
44
|
+
else
|
45
|
+
raise Errors::ImplicitSortTypeMissing.new(self, name)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def paginate(&blk)
|
51
|
+
config[:pagination] = blk
|
52
|
+
end
|
53
|
+
|
54
|
+
def stat(symbol_or_hash, &blk)
|
55
|
+
dsl = Stats::DSL.new(adapter, symbol_or_hash)
|
56
|
+
dsl.instance_eval(&blk) if blk
|
57
|
+
config[:stats][dsl.name] = dsl
|
58
|
+
end
|
59
|
+
|
60
|
+
def default_filter(name = nil, &blk)
|
61
|
+
name ||= :__default
|
62
|
+
config[:default_filters][name.to_sym] = {
|
63
|
+
filter: blk
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def before_commit(only: [:create, :update, :destroy], &blk)
|
68
|
+
Array(only).each do |verb|
|
69
|
+
config[:before_commit][verb] = blk
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def attribute(name, type, options = {}, &blk)
|
74
|
+
raise Errors::TypeNotFound.new(self, name, type) unless Types[type]
|
75
|
+
attribute_option(options, :readable)
|
76
|
+
attribute_option(options, :writable)
|
77
|
+
attribute_option(options, :sortable)
|
78
|
+
attribute_option(options, :filterable)
|
79
|
+
options[:type] = type
|
80
|
+
options[:proc] = blk
|
81
|
+
config[:attributes][name] = options
|
82
|
+
apply_attributes_to_serializer
|
83
|
+
filter(name) if options[:filterable]
|
84
|
+
end
|
85
|
+
|
86
|
+
def extra_attribute(name, type, options = {}, &blk)
|
87
|
+
raise Errors::TypeNotFound.new(self, name, type) unless Types[type]
|
88
|
+
defaults = {
|
89
|
+
type: type,
|
90
|
+
proc: blk,
|
91
|
+
readable: true,
|
92
|
+
writable: false,
|
93
|
+
sortable: false,
|
94
|
+
filterable: false
|
95
|
+
}
|
96
|
+
options = defaults.merge(options)
|
97
|
+
config[:extra_attributes][name] = options
|
98
|
+
apply_extra_attributes_to_serializer
|
99
|
+
end
|
100
|
+
|
101
|
+
def all_attributes
|
102
|
+
attributes.merge(extra_attributes)
|
103
|
+
end
|
104
|
+
|
105
|
+
def apply_attributes_to_serializer
|
106
|
+
serializer.type(type)
|
107
|
+
Util::SerializerAttributes.new(self, attributes).apply
|
108
|
+
end
|
109
|
+
private :apply_attributes_to_serializer
|
110
|
+
|
111
|
+
def apply_extra_attributes_to_serializer
|
112
|
+
Util::SerializerAttributes.new(self, extra_attributes, true).apply
|
113
|
+
end
|
114
|
+
|
115
|
+
def attribute_option(options, name)
|
116
|
+
if options[name] != false
|
117
|
+
default = if only = options[:only]
|
118
|
+
Array(only).include?(name) ? true : false
|
119
|
+
elsif except = options[:except]
|
120
|
+
Array(except).include?(name) ? false : true
|
121
|
+
else
|
122
|
+
send(:"attributes_#{name}_by_default")
|
123
|
+
end
|
124
|
+
options[name] ||= default
|
125
|
+
end
|
126
|
+
end
|
127
|
+
private :attribute_option
|
128
|
+
|
129
|
+
def relationship_option(options, name)
|
130
|
+
if options[name] != false
|
131
|
+
options[name] ||= send(:"relationships_#{name}_by_default")
|
132
|
+
end
|
133
|
+
end
|
134
|
+
private :attribute_option
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Resource
|
3
|
+
module Interface
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
class_methods do
|
7
|
+
def all(params = {}, base_scope = nil)
|
8
|
+
_all(params, {}, base_scope)
|
9
|
+
end
|
10
|
+
|
11
|
+
def _all(params, opts, base_scope)
|
12
|
+
runner = Runner.new(self, params)
|
13
|
+
runner.proxy(base_scope, opts)
|
14
|
+
end
|
15
|
+
|
16
|
+
def find(params, base_scope = nil)
|
17
|
+
id = params[:data].try(:[], :id) || params.delete(:id)
|
18
|
+
params[:filter] ||= {}
|
19
|
+
params[:filter].merge!(id: id)
|
20
|
+
|
21
|
+
runner = Runner.new(self, params)
|
22
|
+
runner.proxy(base_scope, single: true, raise_on_missing: true)
|
23
|
+
end
|
24
|
+
|
25
|
+
def build(params, base_scope = nil)
|
26
|
+
runner = Runner.new(self, params)
|
27
|
+
runner.proxy(base_scope, single: true, raise_on_missing: true)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# For "Rails STI" behavior
|
2
|
+
# CreditCard.all # => [<Visa>, <Mastercard>, etc]
|
3
|
+
module Graphiti
|
4
|
+
class Resource
|
5
|
+
module Polymorphism
|
6
|
+
def self.prepended(klass)
|
7
|
+
klass.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
def serializer_for(model)
|
11
|
+
if polymorphic_child?
|
12
|
+
serializer
|
13
|
+
else
|
14
|
+
child = self.class.resource_for_model(model)
|
15
|
+
child.serializer
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def associate_all(parent, children, association_name, type)
|
20
|
+
children.each do |c|
|
21
|
+
associate(parent, c, association_name, type)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def associate(parent, child, association_name, type)
|
26
|
+
child_resource = self.class.resource_for_model(parent)
|
27
|
+
if child_resource.sideloads[association_name]
|
28
|
+
child_resource.adapter
|
29
|
+
.associate(parent, child, association_name, type)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
module ClassMethods
|
34
|
+
def inherited(klass)
|
35
|
+
klass.type = nil
|
36
|
+
klass.model = klass.infer_model
|
37
|
+
klass.polymorphic_child = true
|
38
|
+
super
|
39
|
+
end
|
40
|
+
|
41
|
+
def sideload(name)
|
42
|
+
sl = super
|
43
|
+
if !polymorphic_child? && sl.nil?
|
44
|
+
children.each do |c|
|
45
|
+
break if sl = c.sideloads[name]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
sl
|
49
|
+
end
|
50
|
+
|
51
|
+
def children
|
52
|
+
@children ||= polymorphic.map do |klass|
|
53
|
+
klass.is_a?(String) ? klass.safe_constantize : klass
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def resource_for_model(model)
|
58
|
+
resource = children.find { |c| model.is_a?(c.model) }
|
59
|
+
if resource.nil?
|
60
|
+
raise Errors::PolymorphicChildNotFound.new(self, model)
|
61
|
+
else
|
62
|
+
resource
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,102 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class Resource
|
3
|
+
module Sideloading
|
4
|
+
def self.included(klass)
|
5
|
+
klass.extend ClassMethods
|
6
|
+
end
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
def allow_sideload(name, opts = {}, &blk)
|
10
|
+
klass = Class.new(opts.delete(:class) || Sideload)
|
11
|
+
klass.class_eval(&blk) if blk
|
12
|
+
opts[:parent_resource] = self
|
13
|
+
relationship_option(opts, :readable)
|
14
|
+
relationship_option(opts, :writable)
|
15
|
+
sideload = klass.new(name, opts)
|
16
|
+
if parent = opts[:parent]
|
17
|
+
parent.children[name] = sideload
|
18
|
+
else
|
19
|
+
config[:sideloads][name] = sideload
|
20
|
+
apply_sideloads_to_serializer
|
21
|
+
end
|
22
|
+
sideload
|
23
|
+
end
|
24
|
+
|
25
|
+
def apply_sideloads_to_serializer
|
26
|
+
config[:sideloads].each_pair do |name, sideload|
|
27
|
+
if serializer.relationship_blocks[name].nil? && sideload.readable?
|
28
|
+
serializer.relationship(name)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_many(name, opts = {}, &blk)
|
34
|
+
opts[:class] = adapter.sideloading_classes[:has_many]
|
35
|
+
allow_sideload(name, opts, &blk)
|
36
|
+
end
|
37
|
+
|
38
|
+
def belongs_to(name, opts = {}, &blk)
|
39
|
+
opts[:class] = adapter.sideloading_classes[:belongs_to]
|
40
|
+
allow_sideload(name, opts, &blk)
|
41
|
+
end
|
42
|
+
|
43
|
+
def has_one(name, opts = {}, &blk)
|
44
|
+
opts[:class] = adapter.sideloading_classes[:has_one]
|
45
|
+
allow_sideload(name, opts, &blk)
|
46
|
+
end
|
47
|
+
|
48
|
+
def many_to_many(name, opts = {}, &blk)
|
49
|
+
opts[:class] = adapter.sideloading_classes[:many_to_many]
|
50
|
+
allow_sideload(name, opts, &blk)
|
51
|
+
end
|
52
|
+
|
53
|
+
def polymorphic_belongs_to(name, opts = {}, &blk)
|
54
|
+
opts[:resource] ||= Class.new(::Graphiti::Resource) do
|
55
|
+
self.polymorphic = []
|
56
|
+
self.abstract_class = true
|
57
|
+
end
|
58
|
+
# adapters *probably* don't need to override this, but it's allowed
|
59
|
+
opts[:class] ||= adapter.sideloading_classes[:polymorphic_belongs_to]
|
60
|
+
opts[:class] ||= ::Graphiti::Sideload::PolymorphicBelongsTo
|
61
|
+
allow_sideload(name, opts, &blk)
|
62
|
+
end
|
63
|
+
|
64
|
+
def sideload(name)
|
65
|
+
sideloads[name]
|
66
|
+
end
|
67
|
+
|
68
|
+
def all_sideloads(memo = {})
|
69
|
+
sideloads.each_pair do |name, sideload|
|
70
|
+
unless memo[name]
|
71
|
+
memo[name] = sideload
|
72
|
+
memo.merge!(sideload.resource.class.all_sideloads(memo))
|
73
|
+
end
|
74
|
+
end
|
75
|
+
memo
|
76
|
+
end
|
77
|
+
|
78
|
+
def association_names(memo = [])
|
79
|
+
all_sideloads.each_pair do |name, sl|
|
80
|
+
unless memo.include?(sl.name)
|
81
|
+
memo << sl.name
|
82
|
+
memo |= sl.resource.class.association_names(memo)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
memo
|
87
|
+
end
|
88
|
+
|
89
|
+
def association_types(memo = [])
|
90
|
+
all_sideloads.each_pair do |name, sl|
|
91
|
+
unless memo.include?(sl.resource.type)
|
92
|
+
memo << sl.resource.type
|
93
|
+
memo |= sl.resource.class.association_types(memo)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
memo
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Graphiti
|
2
|
+
class ResourceProxy
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :resource, :query, :scope
|
6
|
+
|
7
|
+
def initialize(resource, scope, query, payload: nil, single: false, raise_on_missing: false)
|
8
|
+
@resource = resource
|
9
|
+
@scope = scope
|
10
|
+
@query = query
|
11
|
+
@payload = payload
|
12
|
+
@single = single
|
13
|
+
@raise_on_missing = raise_on_missing
|
14
|
+
end
|
15
|
+
|
16
|
+
def single?
|
17
|
+
!!@single
|
18
|
+
end
|
19
|
+
|
20
|
+
def raise_on_missing?
|
21
|
+
!!@raise_on_missing
|
22
|
+
end
|
23
|
+
|
24
|
+
def errors
|
25
|
+
data.errors
|
26
|
+
end
|
27
|
+
|
28
|
+
def [](val)
|
29
|
+
data[val]
|
30
|
+
end
|
31
|
+
|
32
|
+
def jsonapi_render_options(opts = {})
|
33
|
+
opts[:meta] ||= {}
|
34
|
+
opts[:expose] ||= {}
|
35
|
+
opts[:expose][:context] = Graphiti.context[:object]
|
36
|
+
opts
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_jsonapi(options = {})
|
40
|
+
options = jsonapi_render_options(options)
|
41
|
+
Renderer.new(self, options).to_jsonapi
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_json(options = {})
|
45
|
+
Renderer.new(self, options).to_json
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_xml(options = {})
|
49
|
+
Renderer.new(self, options).to_xml
|
50
|
+
end
|
51
|
+
|
52
|
+
def data
|
53
|
+
@data ||= begin
|
54
|
+
records = @scope.resolve
|
55
|
+
if records.empty? && raise_on_missing?
|
56
|
+
raise Graphiti::Errors::RecordNotFound
|
57
|
+
end
|
58
|
+
records = records[0] if single?
|
59
|
+
records
|
60
|
+
end
|
61
|
+
end
|
62
|
+
alias :to_a :data
|
63
|
+
|
64
|
+
def each(&blk)
|
65
|
+
to_a.each(&blk)
|
66
|
+
end
|
67
|
+
|
68
|
+
def stats
|
69
|
+
@stats ||= @scope.resolve_stats
|
70
|
+
end
|
71
|
+
|
72
|
+
def save(action: :create)
|
73
|
+
validator = persist do
|
74
|
+
@resource.persist_with_relationships \
|
75
|
+
@payload.meta(action: action),
|
76
|
+
@payload.attributes,
|
77
|
+
@payload.relationships
|
78
|
+
end
|
79
|
+
@data, success = validator.to_a
|
80
|
+
success
|
81
|
+
end
|
82
|
+
|
83
|
+
def destroy
|
84
|
+
validator = @resource.transaction do
|
85
|
+
model = @resource.destroy(@query.filters[:id])
|
86
|
+
model.instance_variable_set(:@__serializer_klass, @resource.serializer)
|
87
|
+
validator = ::Graphiti::Util::ValidationResponse.new \
|
88
|
+
model, @payload
|
89
|
+
validator.validate!
|
90
|
+
@resource.before_commit(model, :destroy)
|
91
|
+
validator
|
92
|
+
end
|
93
|
+
@data, success = validator.to_a
|
94
|
+
success
|
95
|
+
end
|
96
|
+
|
97
|
+
def update_attributes
|
98
|
+
save(action: :update)
|
99
|
+
end
|
100
|
+
|
101
|
+
def include_hash
|
102
|
+
@payload ? @payload.include_hash : @query.include_hash
|
103
|
+
end
|
104
|
+
|
105
|
+
def fields
|
106
|
+
query.fields
|
107
|
+
end
|
108
|
+
|
109
|
+
def extra_fields
|
110
|
+
query.extra_fields
|
111
|
+
end
|
112
|
+
|
113
|
+
private
|
114
|
+
|
115
|
+
def persist
|
116
|
+
@resource.transaction do
|
117
|
+
::Graphiti::Util::Hooks.record do
|
118
|
+
model = yield
|
119
|
+
validator = ::Graphiti::Util::ValidationResponse.new \
|
120
|
+
model, @payload
|
121
|
+
validator.validate!
|
122
|
+
validator
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|