praxis 2.0.pre.5 → 2.0.pre.6
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 +5 -5
- data/.rspec +0 -1
- data/.ruby-version +1 -0
- data/CHANGELOG.md +22 -0
- data/Gemfile +1 -1
- data/Guardfile +2 -1
- data/Rakefile +1 -7
- data/TODO.md +28 -0
- data/lib/api_browser/package-lock.json +7110 -0
- data/lib/praxis.rb +6 -4
- data/lib/praxis/action_definition.rb +9 -16
- data/lib/praxis/application.rb +1 -2
- data/lib/praxis/bootloader_stages/routing.rb +2 -4
- data/lib/praxis/extensions/attribute_filtering.rb +2 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +148 -157
- data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +15 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +90 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +68 -0
- data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +58 -0
- data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +35 -0
- data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +9 -12
- data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +3 -2
- data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +7 -9
- data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +6 -9
- data/lib/praxis/extensions/pagination.rb +130 -0
- data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +42 -0
- data/lib/praxis/extensions/pagination/header_generator.rb +70 -0
- data/lib/praxis/extensions/pagination/ordering_params.rb +234 -0
- data/lib/praxis/extensions/pagination/pagination_handler.rb +68 -0
- data/lib/praxis/extensions/pagination/pagination_params.rb +374 -0
- data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +45 -0
- data/lib/praxis/handlers/json.rb +2 -0
- data/lib/praxis/handlers/www_form.rb +5 -0
- data/lib/praxis/handlers/{xml.rb → xml-sample.rb} +6 -0
- data/lib/praxis/mapper/active_model_compat.rb +23 -5
- data/lib/praxis/mapper/resource.rb +16 -9
- data/lib/praxis/mapper/sequel_compat.rb +1 -0
- data/lib/praxis/media_type.rb +1 -56
- data/lib/praxis/plugins/mapper_plugin.rb +1 -1
- data/lib/praxis/plugins/pagination_plugin.rb +71 -0
- data/lib/praxis/resource_definition.rb +4 -12
- data/lib/praxis/route.rb +2 -4
- data/lib/praxis/routing_config.rb +4 -8
- data/lib/praxis/tasks/routes.rb +9 -14
- data/lib/praxis/validation_handler.rb +1 -2
- data/lib/praxis/version.rb +1 -1
- data/praxis.gemspec +2 -3
- data/spec/functional_spec.rb +9 -6
- data/spec/praxis/action_definition_spec.rb +4 -16
- data/spec/praxis/api_general_info_spec.rb +6 -6
- data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +304 -0
- data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +39 -0
- data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +34 -0
- data/spec/praxis/extensions/field_expansion_spec.rb +6 -24
- data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +15 -11
- data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +4 -3
- data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +130 -0
- data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_active_model.rb +45 -2
- data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_sequel.rb +0 -0
- data/spec/praxis/media_type_spec.rb +5 -129
- data/spec/praxis/request_spec.rb +3 -22
- data/spec/praxis/resource_definition_spec.rb +1 -1
- data/spec/praxis/response_definition_spec.rb +1 -5
- data/spec/praxis/route_spec.rb +2 -9
- data/spec/praxis/routing_config_spec.rb +4 -13
- data/spec/praxis/types/multipart_array_spec.rb +4 -21
- data/spec/spec_app/config/environment.rb +0 -2
- data/spec/spec_app/design/api.rb +1 -1
- data/spec/spec_app/design/media_types/instance.rb +0 -8
- data/spec/spec_app/design/media_types/volume.rb +0 -12
- data/spec/spec_app/design/resources/instances.rb +1 -2
- data/spec/spec_helper.rb +6 -0
- data/spec/support/spec_media_types.rb +0 -73
- metadata +35 -45
- data/spec/praxis/handlers/xml_spec.rb +0 -177
- data/spec/praxis/links_spec.rb +0 -68
data/lib/praxis.rb
CHANGED
@@ -50,7 +50,6 @@ module Praxis
|
|
50
50
|
autoload :RestfulDocGenerator, 'praxis/restful_doc_generator'
|
51
51
|
module Docs
|
52
52
|
autoload :Generator, 'praxis/docs/generator'
|
53
|
-
autoload :LinkBuilder, 'praxis/docs/link_builder'
|
54
53
|
autoload :OpenApiGenerator, 'praxis/docs/open_api_generator'
|
55
54
|
end
|
56
55
|
|
@@ -61,7 +60,6 @@ module Praxis
|
|
61
60
|
autoload :MultipartArray, 'praxis/types/multipart_array'
|
62
61
|
end
|
63
62
|
|
64
|
-
autoload :Links, 'praxis/links'
|
65
63
|
autoload :MediaType, 'praxis/media_type'
|
66
64
|
autoload :MediaTypeIdentifier, 'praxis/media_type_identifier'
|
67
65
|
autoload :Multipart, 'praxis/types/multipart'
|
@@ -89,15 +87,19 @@ module Praxis
|
|
89
87
|
autoload :MapperSelectors, 'praxis/extensions/mapper_selectors'
|
90
88
|
autoload :Rendering, 'praxis/extensions/rendering'
|
91
89
|
autoload :FieldExpansion, 'praxis/extensions/field_expansion'
|
90
|
+
autoload :AttributeFiltering, 'praxis/extensions/attribute_filtering'
|
92
91
|
autoload :ActiveRecordFilterQueryBuilder, 'praxis/extensions/attribute_filtering/active_record_filter_query_builder'
|
93
92
|
autoload :SequelFilterQueryBuilder, 'praxis/extensions/attribute_filtering/sequel_filter_query_builder'
|
93
|
+
autoload :Pagination, 'praxis/extensions/pagination'
|
94
|
+
module Pagination
|
95
|
+
autoload :ActiveRecordPaginationHandler, 'praxis/extensions/pagination/active_record_pagination_handler'
|
96
|
+
autoload :SequelPaginationHandler, 'praxis/extensions/pagination/sequel_pagination_handler'
|
97
|
+
end
|
94
98
|
end
|
95
99
|
|
96
100
|
module Handlers
|
97
101
|
autoload :Plain, 'praxis/handlers/plain'
|
98
102
|
autoload :JSON, 'praxis/handlers/json'
|
99
|
-
autoload :WWWForm, 'praxis/handlers/www_form'
|
100
|
-
autoload :XML, 'praxis/handlers/xml'
|
101
103
|
end
|
102
104
|
|
103
105
|
module BootloaderStages
|
@@ -12,9 +12,8 @@ module Praxis
|
|
12
12
|
|
13
13
|
attr_reader :name
|
14
14
|
attr_reader :resource_definition
|
15
|
-
attr_reader :
|
16
|
-
attr_reader :
|
17
|
-
attr_reader :named_routes
|
15
|
+
attr_reader :api_definition
|
16
|
+
attr_reader :route
|
18
17
|
attr_reader :responses
|
19
18
|
attr_reader :traits
|
20
19
|
|
@@ -37,7 +36,7 @@ module Praxis
|
|
37
36
|
@resource_definition = resource_definition
|
38
37
|
@responses = Hash.new
|
39
38
|
@metadata = Hash.new
|
40
|
-
@
|
39
|
+
@route = nil
|
41
40
|
@traits = []
|
42
41
|
|
43
42
|
if (media_type = resource_definition.media_type)
|
@@ -179,12 +178,7 @@ module Praxis
|
|
179
178
|
def routing(&block)
|
180
179
|
@routing_config.instance_eval &block
|
181
180
|
|
182
|
-
@
|
183
|
-
@primary_route = @routing_config.routes.first
|
184
|
-
@named_routes = @routing_config.routes.each_with_object({}) do |route, hash|
|
185
|
-
next if route.name.nil?
|
186
|
-
hash[route.name] = route
|
187
|
-
end
|
181
|
+
@route = @routing_config.route
|
188
182
|
end
|
189
183
|
|
190
184
|
|
@@ -232,9 +226,8 @@ module Praxis
|
|
232
226
|
end
|
233
227
|
hash[:traits] = traits if traits.any?
|
234
228
|
# FIXME: change to :routes along with api browser
|
235
|
-
|
236
|
-
|
237
|
-
end.compact
|
229
|
+
# FIXME: change urls to url ... (along with the browser)
|
230
|
+
hash[:urls] = [ ActionDefinition.url_description(route: route, params: self.params, params_example: params_example) ]
|
238
231
|
self.class.doc_decorations.each do |callback|
|
239
232
|
callback.call(self, hash)
|
240
233
|
end
|
@@ -252,10 +245,10 @@ module Praxis
|
|
252
245
|
|
253
246
|
def params_description(example:)
|
254
247
|
route_params = []
|
255
|
-
if
|
256
|
-
warn "Warning: No
|
248
|
+
if route.nil?
|
249
|
+
warn "Warning: No route defined for #{resource_definition.name}##{name}."
|
257
250
|
else
|
258
|
-
route_params =
|
251
|
+
route_params = route.path.
|
259
252
|
named_captures.
|
260
253
|
keys.
|
261
254
|
collect(&:to_sym)
|
data/lib/praxis/application.rb
CHANGED
@@ -61,8 +61,7 @@ module Praxis
|
|
61
61
|
|
62
62
|
builtin_handlers = {
|
63
63
|
'plain' => Praxis::Handlers::Plain,
|
64
|
-
'json' => Praxis::Handlers::JSON
|
65
|
-
'x-www-form-urlencoded' => Praxis::Handlers::WWWForm
|
64
|
+
'json' => Praxis::Handlers::JSON
|
66
65
|
}
|
67
66
|
# Register built-in handlers unless the app already provided its own
|
68
67
|
builtin_handlers.each_pair do |name, handler|
|
@@ -23,10 +23,8 @@ module Praxis
|
|
23
23
|
def execute
|
24
24
|
application.controllers.each do |controller|
|
25
25
|
controller.definition.actions.each do |action_name, action|
|
26
|
-
|
27
|
-
|
28
|
-
application.router.add_route target, route
|
29
|
-
end
|
26
|
+
target = target_factory(controller, action_name)
|
27
|
+
application.router.add_route target, action.route
|
30
28
|
end
|
31
29
|
end
|
32
30
|
end
|
@@ -1,178 +1,169 @@
|
|
1
|
+
|
2
|
+
|
1
3
|
module Praxis
|
2
4
|
module Extensions
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
21
|
-
end
|
5
|
+
module AttributeFiltering
|
6
|
+
ALIAS_TABLE_PREFIX = ''
|
7
|
+
require_relative 'active_record_patches'
|
8
|
+
|
9
|
+
class ActiveRecordFilterQueryBuilder
|
10
|
+
attr_reader :query, :model, :attr_to_column
|
11
|
+
|
12
|
+
# Base query to build upon
|
13
|
+
def initialize(query: , model:, filters_map:, debug: false)
|
14
|
+
@query = query
|
15
|
+
@model = model
|
16
|
+
@attr_to_column = filters_map
|
17
|
+
@logger = debug ? Logger.new(STDOUT) : nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def debug(msg)
|
21
|
+
@logger && @logger.puts(msg)
|
22
22
|
end
|
23
|
-
end
|
24
23
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
24
|
+
def generate(filters)
|
25
|
+
# Resolve the names and values first, based on filters_map
|
26
|
+
root_node = _convert_to_treenode(filters)
|
27
|
+
craft_filter_query(root_node, for_model: @model)
|
28
|
+
debug("SQL due to filters: #{@query.all.to_sql}")
|
29
|
+
@query
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
end
|
32
|
+
def craft_filter_query(nodetree, for_model:)
|
33
|
+
result = _compute_joins_and_conditions_data(nodetree, model: for_model)
|
34
|
+
@query = query.joins(result[:associations_hash]) unless result[:associations_hash].empty?
|
37
35
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
bindings = column_name.call(spec)
|
46
|
-
# A hash of bindings, consisting of a key with column name and a value to the query value
|
47
|
-
bindings.each do|col,val|
|
48
|
-
assoc_or_field, *rest = col.to_s.split('.')
|
49
|
-
expand_binding(column_name: assoc_or_field, rest: rest, op: spec[:op], value: val, use_this_name_for_clause: @last_join_alias)
|
50
|
-
end
|
51
|
-
else
|
52
|
-
assoc_or_field, *rest = column_name.to_s.split('.')
|
53
|
-
expand_binding(column_name: assoc_or_field, rest: rest, **spec, use_this_name_for_clause: @last_join_alias)
|
36
|
+
result[:conditions].each do |condition|
|
37
|
+
filter_name = condition[:name]
|
38
|
+
filter_value = condition[:value]
|
39
|
+
column_prefix = condition[:column_prefix]
|
40
|
+
|
41
|
+
colo = condition[:model].columns_hash[filter_name.to_s]
|
42
|
+
add_clause(column_prefix: column_prefix, column_object: colo, op: condition[:op], value: filter_value)
|
54
43
|
end
|
55
44
|
end
|
56
|
-
query
|
57
|
-
end
|
58
45
|
|
59
|
-
|
60
|
-
def do_join(query, assoc , source_alias, table_alias)
|
61
|
-
reflection = query.reflections[assoc.to_s]
|
62
|
-
do_join_reflection( query, reflection, source_alias, table_alias )
|
63
|
-
end
|
46
|
+
private
|
64
47
|
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
[c.quote_table_name(reflection.klass.table_name),
|
83
|
-
c.quote_table_name(table_alias),
|
84
|
-
c.quote_table_name(source_alias),
|
85
|
-
c.quote_column_name(reflection.active_record.primary_key),
|
86
|
-
c.quote_table_name(table_alias),
|
87
|
-
c.quote_column_name(reflection.foreign_key)
|
88
|
-
]
|
89
|
-
|
90
|
-
if reflection.type # && reflection.options[:as]....
|
91
|
-
# addition = " AND \"#{table_alias}\".\"#{reflection.type}\" = \'#{reflection.active_record.class_name}\'"
|
92
|
-
addition = " AND %s.%s = %s" % \
|
93
|
-
[ c.quote_table_name(table_alias),
|
94
|
-
c.quote_table_name(reflection.type),
|
95
|
-
c.quote(reflection.active_record.class_name)]
|
96
|
-
|
97
|
-
join_clause += addition
|
48
|
+
# Resolve and convert from filters, to a more manageable and param-type-independent structure
|
49
|
+
def _convert_to_treenode(filters)
|
50
|
+
# Resolve the names and values first, based on filters_map
|
51
|
+
resolved_array = []
|
52
|
+
filters.parsed_array.each do |filter|
|
53
|
+
mapped_value = attr_to_column[filter[:name]]
|
54
|
+
raise "Filtering by #{filter[:name]} not allowed (no mapping found)" unless mapped_value
|
55
|
+
bindings_array = \
|
56
|
+
if mapped_value.is_a?(Proc)
|
57
|
+
result = mapped_value.call(filter)
|
58
|
+
# Result could be an array of hashes (each hash has name/op/value to identify a condition)
|
59
|
+
result.is_a?(Array) ? result : [result]
|
60
|
+
else
|
61
|
+
# For non-procs there's only 1 filter and 1 value (we're just overriding the mapped value)
|
62
|
+
[filter.merge( name: mapped_value)]
|
63
|
+
end
|
64
|
+
resolved_array = resolved_array + bindings_array
|
98
65
|
end
|
99
|
-
|
100
|
-
when ActiveRecord::Reflection::ThroughReflection
|
101
|
-
#puts "TODO: choose different alias (based on matching table type...)"
|
102
|
-
talias = pick_alias(reflection.through_reflection.table_name)
|
103
|
-
salias = source_alias
|
104
|
-
|
105
|
-
query = do_join_reflection(query, reflection.through_reflection, salias, talias)
|
106
|
-
#puts "TODO: choose different alias ?????????"
|
107
|
-
salias = talias
|
108
|
-
|
109
|
-
through_model = reflection.through_reflection.klass
|
110
|
-
through_assoc = reflection.name
|
111
|
-
final_reflection = reflection.source_reflection
|
112
|
-
|
113
|
-
do_join_reflection(query, final_reflection, salias, table_alias)
|
114
|
-
else
|
115
|
-
raise "Joins for this association type are currently UNSUPPORTED: #{reflection.inspect}"
|
66
|
+
FilterTreeNode.new(resolved_array, path: [ALIAS_TABLE_PREFIX])
|
116
67
|
end
|
117
|
-
end
|
118
68
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
69
|
+
# Calculate join tree and conditions array for the nodetree object and its children
|
70
|
+
def _compute_joins_and_conditions_data(nodetree, model:)
|
71
|
+
h = {}
|
72
|
+
conditions = []
|
73
|
+
nodetree.children.each do |name, child|
|
74
|
+
child_model = model.reflections[name.to_s].klass
|
75
|
+
result = _compute_joins_and_conditions_data(child, model: child_model)
|
76
|
+
h[name] = result[:associations_hash]
|
77
|
+
conditions += result[:conditions]
|
78
|
+
end
|
79
|
+
column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? model.table_name : nodetree.path.join('/')
|
80
|
+
#column_prefix = nodetree.path == [ALIAS_TABLE_PREFIX] ? nil : nodetree.path.join('/')
|
81
|
+
nodetree.conditions.each do |condition|
|
82
|
+
conditions += [condition.merge(column_prefix: column_prefix, model: model)]
|
83
|
+
end
|
84
|
+
{associations_hash: h, conditions: conditions}
|
131
85
|
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def attr_to_column
|
135
|
-
# Class method defined by the subclassing Class (using .for)
|
136
|
-
self.class.attr_to_column
|
137
|
-
end
|
138
86
|
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
87
|
+
def add_clause(column_prefix:, column_object:, op:, value:)
|
88
|
+
@query = @query.references(column_prefix) #Mark where clause referencing the appropriate alias
|
89
|
+
likeval = get_like_value(value)
|
90
|
+
case op
|
91
|
+
when '!' # name! means => name IS NOT NULL (and the incoming value is nil)
|
92
|
+
op = '!='
|
93
|
+
value = nil # Enforce it is indeed nil (should be)
|
94
|
+
when '!!'
|
95
|
+
op = '='
|
96
|
+
value = nil # Enforce it is indeed nil (should be)
|
97
|
+
end
|
98
|
+
@query = case op
|
99
|
+
when '='
|
100
|
+
if likeval
|
101
|
+
add_safe_where(tab: column_prefix, col: column_object, op: 'LIKE', value: likeval)
|
102
|
+
else
|
103
|
+
quoted_right = quote_right_part(value: value, column_object: column_object, negative: false)
|
104
|
+
query.where("#{quote_column_path(column_prefix, column_object)} #{quoted_right}")
|
105
|
+
end
|
106
|
+
when '!='
|
107
|
+
if likeval
|
108
|
+
add_safe_where(tab: column_prefix, col: column_object, op: 'NOT LIKE', value: likeval)
|
109
|
+
else
|
110
|
+
quoted_right = quote_right_part(value: value, column_object: column_object, negative: true)
|
111
|
+
query.where("#{quote_column_path(column_prefix, column_object)} #{quoted_right}")
|
112
|
+
end
|
113
|
+
when '>'
|
114
|
+
add_safe_where(tab: column_prefix, col: column_object, op: '>', value: value)
|
115
|
+
when '<'
|
116
|
+
add_safe_where(tab: column_prefix, col: column_object, op: '<', value: value)
|
117
|
+
when '>='
|
118
|
+
add_safe_where(tab: column_prefix, col: column_object, op: '>=', value: value)
|
119
|
+
when '<='
|
120
|
+
add_safe_where(tab: column_prefix, col: column_object, op: '<=', value: value)
|
153
121
|
else
|
154
|
-
|
122
|
+
raise "Unsupported Operator!!! #{op}"
|
155
123
|
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
when '<='
|
163
|
-
query.where("#{column_name} <= ?", value)
|
164
|
-
else
|
165
|
-
raise "Unsupported Operator!!! #{op}"
|
166
|
-
end
|
167
|
-
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def add_safe_where(tab:, col:, op:, value:)
|
127
|
+
quoted_value = query.connection.quote_default_expression(value,col)
|
128
|
+
query.where("#{quote_column_path(tab, col)} #{op} #{quoted_value}")
|
129
|
+
end
|
168
130
|
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
131
|
+
def quote_column_path(prefix, column_object)
|
132
|
+
c = query.connection
|
133
|
+
quoted_column = c.quote_column_name(column_object.name)
|
134
|
+
if prefix
|
135
|
+
quoted_table = c.quote_table_name(prefix)
|
136
|
+
"#{quoted_table}.#{quoted_column}"
|
137
|
+
else
|
138
|
+
quoted_column
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def quote_right_part(value:, column_object:, negative:)
|
143
|
+
conn = query.connection
|
144
|
+
if value.nil?
|
145
|
+
no = negative ? ' NOT' : ''
|
146
|
+
"IS#{no} #{conn.quote_default_expression(value,column_object)}"
|
147
|
+
elsif value.is_a?(Array)
|
148
|
+
no = negative ? 'NOT ' : ''
|
149
|
+
list = value.map{|v| conn.quote_default_expression(v,column_object)}
|
150
|
+
"#{no}IN (#{list.join(',')})"
|
151
|
+
elsif value && value.is_a?(Range)
|
152
|
+
raise "TODO!"
|
153
|
+
else
|
154
|
+
op = negative ? '<>' : '='
|
155
|
+
"#{op} #{conn.quote_default_expression(value,column_object)}"
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Returns nil if the value was not a fuzzzy pattern
|
160
|
+
def get_like_value(value)
|
161
|
+
if value.is_a?(String) && (value[-1] == '*' || value[0] == '*')
|
162
|
+
likeval = value.dup
|
163
|
+
likeval[-1] = '%' if value[-1] == '*'
|
164
|
+
likeval[0] = '%' if value[0] == '*'
|
165
|
+
likeval
|
166
|
+
end
|
176
167
|
end
|
177
168
|
end
|
178
169
|
end
|