safrano 0.4.1 → 0.4.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/core_ext/Dir/iter.rb +18 -0
- data/lib/core_ext/Hash/transform.rb +21 -0
- data/lib/core_ext/Integer/edm.rb +13 -0
- data/lib/core_ext/REXML/Document/output.rb +16 -0
- data/lib/core_ext/String/convert.rb +25 -0
- data/lib/core_ext/String/edm.rb +13 -0
- data/lib/core_ext/dir.rb +3 -0
- data/lib/core_ext/hash.rb +3 -0
- data/lib/core_ext/integer.rb +3 -0
- data/lib/core_ext/rexml.rb +3 -0
- data/lib/core_ext/string.rb +5 -0
- data/lib/odata/attribute.rb +15 -10
- data/lib/odata/batch.rb +15 -13
- data/lib/odata/collection.rb +144 -535
- data/lib/odata/collection_filter.rb +47 -40
- data/lib/odata/collection_media.rb +155 -99
- data/lib/odata/collection_order.rb +50 -37
- data/lib/odata/common_logger.rb +36 -34
- data/lib/odata/complex_type.rb +152 -0
- data/lib/odata/edm/primitive_types.rb +184 -0
- data/lib/odata/entity.rb +183 -216
- data/lib/odata/error.rb +195 -31
- data/lib/odata/expand.rb +126 -0
- data/lib/odata/filter/base.rb +74 -0
- data/lib/odata/filter/error.rb +49 -6
- data/lib/odata/filter/parse.rb +44 -36
- data/lib/odata/filter/sequel.rb +136 -67
- data/lib/odata/filter/sequel_function_adapter.rb +148 -0
- data/lib/odata/filter/token.rb +26 -19
- data/lib/odata/filter/tree.rb +113 -63
- data/lib/odata/function_import.rb +168 -0
- data/lib/odata/model_ext.rb +639 -0
- data/lib/odata/navigation_attribute.rb +44 -61
- data/lib/odata/relations.rb +5 -5
- data/lib/odata/select.rb +54 -0
- data/lib/odata/transition.rb +71 -0
- data/lib/odata/url_parameters.rb +128 -37
- data/lib/odata/walker.rb +20 -10
- data/lib/safrano.rb +17 -37
- data/lib/safrano/contract.rb +143 -0
- data/lib/safrano/core.rb +29 -104
- data/lib/safrano/core_ext.rb +13 -0
- data/lib/safrano/deprecation.rb +73 -0
- data/lib/safrano/multipart.rb +39 -43
- data/lib/safrano/rack_app.rb +68 -67
- data/lib/safrano/{odata_rack_builder.rb → rack_builder.rb} +18 -2
- data/lib/safrano/request.rb +102 -51
- data/lib/safrano/response.rb +5 -3
- data/lib/safrano/sequel_join_by_paths.rb +2 -2
- data/lib/safrano/service.rb +274 -219
- data/lib/safrano/version.rb +3 -1
- data/lib/sequel/plugins/join_by_paths.rb +17 -29
- metadata +34 -11
@@ -1,33 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
|
-
require_relative '../safrano/core
|
3
|
-
require_relative './entity
|
4
|
+
require_relative '../safrano/core'
|
5
|
+
require_relative './entity'
|
4
6
|
|
5
|
-
module
|
6
|
-
|
7
|
-
# remove the relation between entity and parent by clearing
|
7
|
+
module Safrano
|
8
|
+
# remove the relation between entity and parent by clearing
|
8
9
|
# the FK field(s) (if allowed)
|
9
|
-
def
|
10
|
+
def self.remove_nav_relation(assoc, parent)
|
10
11
|
return unless assoc
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
}
|
24
|
-
|
25
|
-
end
|
13
|
+
return unless assoc[:type] == :many_to_one
|
14
|
+
|
15
|
+
# removes/clear the FK values in parent
|
16
|
+
# thus deleting the "link" between the entity and the parent
|
17
|
+
# Note: This is called if we have to delete the child--> can only be
|
18
|
+
# done after removing the FK in parent (if allowed!)
|
19
|
+
lks = [assoc[:key]].flatten
|
20
|
+
lks.each do |lk|
|
21
|
+
parent.set(lk => nil)
|
22
|
+
parent.save(transaction: false)
|
23
|
+
end
|
26
24
|
end
|
27
|
-
|
25
|
+
|
28
26
|
# link newly created entities(child) to an existing parent
|
29
27
|
# by following the association_reflection rules
|
30
|
-
def
|
28
|
+
def self.create_nav_relation(child, assoc, parent)
|
31
29
|
return unless assoc
|
32
30
|
|
33
31
|
# Note: this coding shares some bits from our sequel/plugins/join_by_paths,
|
@@ -46,16 +44,16 @@ module OData
|
|
46
44
|
lks = [leftm.primary_key].flatten
|
47
45
|
rks = [assoc[:key]].flatten
|
48
46
|
join_cond = rks.zip(lks).to_h
|
49
|
-
join_cond.each
|
47
|
+
join_cond.each do |rk, lk|
|
50
48
|
if child.values[rk] # FK in new entity from payload not nil, only check consistency
|
51
49
|
# with the parent - id(s)
|
52
|
-
if (child.values[rk] != parent.pk_hash[lk]) # error...
|
53
|
-
|
54
|
-
end
|
50
|
+
# if (child.values[rk] != parent.pk_hash[lk]) # error...
|
51
|
+
# TODO
|
52
|
+
# end
|
55
53
|
else # we can set the FK value, thus creating the "link"
|
56
54
|
child.set(rk => parent.pk_hash[lk])
|
57
55
|
end
|
58
|
-
|
56
|
+
end
|
59
57
|
when :many_to_one
|
60
58
|
# sets the FK values in parent to corresponding related child key-values
|
61
59
|
# thus creating the "link" between the new entity and the parent
|
@@ -64,29 +62,15 @@ module OData
|
|
64
62
|
lks = [assoc[:key]].flatten
|
65
63
|
rks = [child.class.primary_key].flatten
|
66
64
|
join_cond = rks.zip(lks).to_h
|
67
|
-
join_cond.each
|
65
|
+
join_cond.each do |rk, lk|
|
68
66
|
if parent.values[lk] # FK in parent not nil, only check consistency
|
69
67
|
# with the child - id(s)
|
70
|
-
if
|
71
|
-
|
72
|
-
end
|
68
|
+
# if parent.values[lk] != child.pk_hash[rk] # error...
|
69
|
+
# TODO
|
70
|
+
# end
|
73
71
|
else # we can set the FK value, thus creating the "link"
|
74
72
|
parent.set(lk => child.pk_hash[rk])
|
75
73
|
end
|
76
|
-
}
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
module EntityBase
|
81
|
-
module NavigationInfo
|
82
|
-
attr_reader :nav_parent
|
83
|
-
attr_reader :navattr_reflection
|
84
|
-
attr_reader :nav_name
|
85
|
-
def set_relation_info(parent,name)
|
86
|
-
@nav_parent = parent
|
87
|
-
@nav_name = name
|
88
|
-
@navattr_reflection = parent.class.association_reflections[name.to_sym]
|
89
|
-
@nav_klass = @navattr_reflection[:class_name].constantize
|
90
74
|
end
|
91
75
|
end
|
92
76
|
end
|
@@ -94,12 +78,12 @@ module OData
|
|
94
78
|
# Represents a named but nil-valued navigation-attribute of an Entity
|
95
79
|
# (usually resulting from a NULL FK db value)
|
96
80
|
class NilNavigationAttribute
|
97
|
-
include
|
81
|
+
include Safrano::NavigationInfo
|
98
82
|
def odata_get(req)
|
99
83
|
if req.walker.media_value
|
100
|
-
|
84
|
+
Safrano::ErrorNotFound.odata_get
|
101
85
|
elsif req.accept?(APPJSON)
|
102
|
-
[200,
|
86
|
+
[200, EMPTY_HASH, to_odata_json(service: req.service)]
|
103
87
|
else # TODO: other formats
|
104
88
|
415
|
105
89
|
end
|
@@ -108,25 +92,22 @@ module OData
|
|
108
92
|
# create the nav. entity
|
109
93
|
def odata_post(req)
|
110
94
|
# delegate to the class method
|
111
|
-
@nav_klass.odata_create_entity_and_relation(req,
|
112
|
-
@navattr_reflection,
|
95
|
+
@nav_klass.odata_create_entity_and_relation(req,
|
96
|
+
@navattr_reflection,
|
113
97
|
@nav_parent)
|
114
98
|
end
|
115
99
|
|
116
100
|
# create the nav. entity
|
117
101
|
def odata_put(req)
|
118
|
-
#
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
@nav_parent)
|
123
|
-
# else
|
124
|
-
# end
|
102
|
+
# delegate to the class method
|
103
|
+
@nav_klass.odata_create_entity_and_relation(req,
|
104
|
+
@navattr_reflection,
|
105
|
+
@nav_parent)
|
125
106
|
end
|
126
107
|
|
127
108
|
# empty output as OData json (v2)
|
128
109
|
def to_odata_json(*)
|
129
|
-
{ 'd' =>
|
110
|
+
{ 'd' => EMPTY_HASH }.to_json
|
130
111
|
end
|
131
112
|
|
132
113
|
# for testing purpose (assert_equal ...)
|
@@ -137,16 +118,18 @@ module OData
|
|
137
118
|
# methods related to transitions to next state (cf. walker)
|
138
119
|
module Transitions
|
139
120
|
def transition_end(_match_result)
|
140
|
-
|
121
|
+
Safrano::Transition::RESULT_END
|
141
122
|
end
|
142
123
|
|
143
124
|
def transition_value(_match_result)
|
144
125
|
[self, :end_with_value]
|
145
126
|
end
|
146
127
|
|
128
|
+
ALLOWED_TRANSITIONS = [Safrano::TransitionEnd,
|
129
|
+
Safrano::TransitionValue].freeze
|
130
|
+
|
147
131
|
def allowed_transitions
|
148
|
-
|
149
|
-
Safrano::TransitionValue]
|
132
|
+
ALLOWED_TRANSITIONS
|
150
133
|
end
|
151
134
|
end
|
152
135
|
include Transitions
|
data/lib/odata/relations.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'set'
|
4
4
|
|
5
5
|
# OData relation related classes/module
|
6
|
-
module
|
6
|
+
module Safrano
|
7
7
|
# we represent a relation as a Set (unordered) of two end elements
|
8
8
|
class Relation < Set
|
9
9
|
# attr_reader :rid
|
@@ -20,7 +20,7 @@ module OData
|
|
20
20
|
|
21
21
|
# we need a from/to order independant ID
|
22
22
|
def rid
|
23
|
-
|
23
|
+
Safrano::RelationManager.build_id(self)
|
24
24
|
end
|
25
25
|
|
26
26
|
# we need a from/to order independant OData like name
|
@@ -76,11 +76,11 @@ module OData
|
|
76
76
|
end
|
77
77
|
|
78
78
|
def get(arg)
|
79
|
-
rid =
|
79
|
+
rid = Safrano::RelationManager.build_id(arg)
|
80
80
|
if @list.key?(rid)
|
81
81
|
@list[rid]
|
82
82
|
else
|
83
|
-
rel =
|
83
|
+
rel = Safrano::Relation.new(arg)
|
84
84
|
@list[rid] = rel
|
85
85
|
end
|
86
86
|
end
|
data/lib/odata/select.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'odata/error'
|
4
|
+
|
5
|
+
# all dataset selecting related classes in our OData module
|
6
|
+
# ie do eager loading
|
7
|
+
module Safrano
|
8
|
+
# base class for selecting. We have to distinguish between
|
9
|
+
# fields of the current entity, and the navigation properties
|
10
|
+
# we can have one special case
|
11
|
+
# empty, ie no $select specified --> return all fields and all nav props
|
12
|
+
# ==> SelectAll
|
13
|
+
|
14
|
+
class SelectBase
|
15
|
+
ALL = new # re-useable selecting-all handler
|
16
|
+
|
17
|
+
def self.factory(selectstr, model)
|
18
|
+
case selectstr&.strip
|
19
|
+
when nil, '', '*'
|
20
|
+
ALL
|
21
|
+
else
|
22
|
+
Select.new(selectstr, model)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def all_props?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def ALL.all_props?
|
31
|
+
true
|
32
|
+
end
|
33
|
+
|
34
|
+
def parse_error?
|
35
|
+
Contract::OK
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# single select
|
40
|
+
class Select < SelectBase
|
41
|
+
COMASPLIT = /\s*,\s*/.freeze
|
42
|
+
attr_reader :props
|
43
|
+
|
44
|
+
def initialize(selstr, model)
|
45
|
+
@model = model
|
46
|
+
@selectp = selstr.strip
|
47
|
+
@props = @selectp.split(COMASPLIT)
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_error?
|
51
|
+
(invalids = @model.find_invalid_props(props.to_set)) ? BadRequestSelectInvalidProps.new(@model, invalids) : Contract::OK
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'error'
|
4
|
+
|
5
|
+
# our main namespace
|
6
|
+
module Safrano
|
7
|
+
# represents a state transition when navigating/parsing the url path
|
8
|
+
# from left to right
|
9
|
+
class Transition < Regexp
|
10
|
+
attr_accessor :trans
|
11
|
+
attr_accessor :match_result
|
12
|
+
attr_accessor :rgx
|
13
|
+
attr_reader :remain_idx
|
14
|
+
|
15
|
+
EMPTYSTR = ''.freeze
|
16
|
+
SLASH = '/'.freeze
|
17
|
+
|
18
|
+
RESULT_BAD_REQ_ERR = [nil, :error, ::Safrano::BadRequestError].freeze
|
19
|
+
RESULT_NOT_FOUND_ERR = [nil, :error, ::Safrano::ErrorNotFound].freeze
|
20
|
+
RESULT_SERVER_TR_ERR = [nil, :error, ServerTransitionError].freeze
|
21
|
+
RESULT_END = [nil, :end].freeze
|
22
|
+
|
23
|
+
def initialize(arg, trans: nil, remain_idx: 2)
|
24
|
+
@rgx = if arg.respond_to? :each_char
|
25
|
+
Regexp.new(arg)
|
26
|
+
else
|
27
|
+
arg
|
28
|
+
end
|
29
|
+
@trans = trans
|
30
|
+
@remain_idx = remain_idx
|
31
|
+
end
|
32
|
+
|
33
|
+
def do_match(str)
|
34
|
+
@match_result = @rgx.match(str)
|
35
|
+
end
|
36
|
+
|
37
|
+
# remain_idx is the index of the last match-data. ususally its 2
|
38
|
+
# but can be overidden
|
39
|
+
def path_remain
|
40
|
+
@match_result[@remain_idx] if @match_result && @match_result[@remain_idx]
|
41
|
+
end
|
42
|
+
|
43
|
+
def path_done
|
44
|
+
if @match_result
|
45
|
+
@match_result[1] || EMPTYSTR
|
46
|
+
else
|
47
|
+
EMPTYSTR
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def do_transition(ctx)
|
52
|
+
ctx.method(@trans).call(@match_result)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
TransitionEnd = Transition.new('\A(\/?)\z', trans: 'transition_end')
|
57
|
+
TransitionMetadata = Transition.new('\A(\/\$metadata)(.*)',
|
58
|
+
trans: 'transition_metadata')
|
59
|
+
TransitionBatch = Transition.new('\A(\/\$batch)(.*)',
|
60
|
+
trans: 'transition_batch')
|
61
|
+
TransitionContentId = Transition.new('\A(\/\$(\d+))(.*)',
|
62
|
+
trans: 'transition_content_id',
|
63
|
+
remain_idx: 3)
|
64
|
+
TransitionCount = Transition.new('(\A\/\$count)(.*)\z',
|
65
|
+
trans: 'transition_count')
|
66
|
+
TransitionValue = Transition.new('(\A\/\$value)(.*)\z',
|
67
|
+
trans: 'transition_value')
|
68
|
+
TransitionLinks = Transition.new('(\A\/\$links)(.*)\z',
|
69
|
+
trans: 'transition_links')
|
70
|
+
attr_accessor :allowed_transitions
|
71
|
+
end
|
data/lib/odata/url_parameters.rb
CHANGED
@@ -1,58 +1,149 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'odata/error'
|
2
4
|
|
3
5
|
# url parameters processing . Mostly delegates to specialised classes
|
4
6
|
# (filter, order...) to convert into Sequel exprs.
|
5
|
-
module
|
6
|
-
class
|
7
|
+
module Safrano
|
8
|
+
class UrlParametersBase
|
9
|
+
attr_reader :expand
|
10
|
+
attr_reader :select
|
11
|
+
|
12
|
+
# url params validation methods.
|
13
|
+
# nil is the expected return for no errors
|
14
|
+
# an error class is returned in case of errors
|
15
|
+
# this way we can combine multiple validation calls with logical ||
|
16
|
+
def check_expand
|
17
|
+
@expand.parse_error?
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_select
|
21
|
+
@select.parse_error?
|
22
|
+
end
|
23
|
+
|
24
|
+
def initialize(dataset, params = {})
|
25
|
+
@model = if dataset.respond_to? :model
|
26
|
+
dataset.model
|
27
|
+
else
|
28
|
+
dataset
|
29
|
+
end
|
30
|
+
@params = params
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# url parameters for a single entity expand/select
|
35
|
+
class UrlParameters4Single < UrlParametersBase
|
36
|
+
def initialize(dataset, params)
|
37
|
+
super
|
38
|
+
@expand = ExpandBase.factory(@params['$expand'], @model)
|
39
|
+
@select = SelectBase.factory(@params['$select'], @model)
|
40
|
+
end
|
41
|
+
|
42
|
+
def check_all
|
43
|
+
return Contract::OK unless @params
|
44
|
+
|
45
|
+
check_expand.if_valid do
|
46
|
+
check_select
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# url parameters for a collection expand/select + filter/order
|
52
|
+
class UrlParameters4Coll < UrlParametersBase
|
7
53
|
attr_reader :filt
|
8
54
|
attr_reader :ordby
|
9
|
-
|
10
|
-
|
55
|
+
|
56
|
+
def initialize(dataset, params = {})
|
57
|
+
super
|
58
|
+
# join helper is only needed for odering or filtering
|
59
|
+
@jh = @model.join_by_paths_helper if params['$orderby'] || params['$filter']
|
11
60
|
@params = params
|
61
|
+
@ordby = OrderBase.factory(@params['$orderby'], @jh)
|
62
|
+
@filt = FilterBase.factory(@params['$filter'])
|
63
|
+
@expand = ExpandBase.factory(@params['$expand'], @model)
|
64
|
+
@select = SelectBase.factory(@params['$select'], @model)
|
12
65
|
end
|
13
66
|
|
14
|
-
def
|
15
|
-
return unless @params['$
|
67
|
+
def check_top
|
68
|
+
return Contract::OK unless @params['$top']
|
16
69
|
|
17
|
-
|
18
|
-
|
70
|
+
itop = number_or_nil(@params['$top'])
|
71
|
+
(itop.nil? || itop.zero?) ? BadRequestError : Contract::OK
|
72
|
+
end
|
73
|
+
|
74
|
+
def check_skip
|
75
|
+
return Contract::OK unless @params['$skip']
|
19
76
|
|
20
|
-
|
21
|
-
nil
|
77
|
+
iskip = number_or_nil(@params['$skip'])
|
78
|
+
(iskip.nil? || iskip.negative?) ? BadRequestError : Contract::OK
|
22
79
|
end
|
23
80
|
|
24
|
-
def
|
25
|
-
return unless @params['$
|
81
|
+
def check_inlinecount
|
82
|
+
return Contract::OK unless (icp = @params['$inlinecount'])
|
26
83
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
return BadRequestError unless @jh.start_model.attrib_path_valid? qualfn
|
34
|
-
return BadRequestError unless [nil, 'asc', 'desc'].include? dir
|
35
|
-
end
|
84
|
+
((icp == 'allpages') || (icp == 'none')) ? Contract::OK : BadRequestInlineCountParamError
|
85
|
+
end
|
86
|
+
|
87
|
+
def check_filter
|
88
|
+
(err = @filt.parse_error?) ? err : Contract::OK
|
89
|
+
end
|
36
90
|
|
37
|
-
|
91
|
+
def check_orderby
|
92
|
+
return Contract::OK if @ordby.empty?
|
93
|
+
return BadRequestOrderParseError if @ordby.parse_error?
|
38
94
|
|
39
|
-
|
40
|
-
nil
|
95
|
+
Contract::OK
|
41
96
|
end
|
42
97
|
|
43
98
|
def apply_to_dataset(dtcx)
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
99
|
+
apply_expand_to_dataset(dtcx).if_valid do |dataset|
|
100
|
+
apply_filter_order_to_dataset(dataset)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def apply_expand_to_dataset(dtcx)
|
105
|
+
return Contract.valid(dtcx) if @expand.empty?
|
106
|
+
|
107
|
+
@expand.apply_to_dataset(dtcx)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Warning, the @ordby and @filt objects are coupled by way of the join helper
|
111
|
+
def apply_filter_order_to_dataset(dtcx)
|
112
|
+
return Contract.valid(dtcx) if @filt.empty? && @ordby.empty?
|
113
|
+
|
114
|
+
# filter object and join-helper need to be finalized after filter
|
115
|
+
# has been parsed and checked
|
116
|
+
@filt.finalize(@jh).if_valid do
|
117
|
+
# start with the join
|
118
|
+
dtcx = @jh.dataset(dtcx)
|
119
|
+
|
120
|
+
@filt.apply_to_dataset(dtcx).map_result! do |dataset|
|
121
|
+
dtcx = dataset
|
122
|
+
dtcx = @ordby.apply_to_dataset(dtcx)
|
123
|
+
dtcx.select_all(@jh.start_model.table_name)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
###########################################################
|
129
|
+
def check_all
|
130
|
+
return Contract::OK unless @params
|
131
|
+
|
132
|
+
# lazy nested proc evaluation.
|
133
|
+
# if one check fails, it will be passed up the chain and the ones
|
134
|
+
# below will not be evaluated
|
135
|
+
check_top.if_valid do
|
136
|
+
check_skip.if_valid do
|
137
|
+
check_orderby.if_valid do
|
138
|
+
check_filter.if_valid do
|
139
|
+
check_expand.if_valid do
|
140
|
+
check_select.if_valid do
|
141
|
+
check_inlinecount
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
56
147
|
end
|
57
148
|
end
|
58
149
|
end
|