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.
Files changed (76) hide show
  1. checksums.yaml +5 -5
  2. data/.rspec +0 -1
  3. data/.ruby-version +1 -0
  4. data/CHANGELOG.md +22 -0
  5. data/Gemfile +1 -1
  6. data/Guardfile +2 -1
  7. data/Rakefile +1 -7
  8. data/TODO.md +28 -0
  9. data/lib/api_browser/package-lock.json +7110 -0
  10. data/lib/praxis.rb +6 -4
  11. data/lib/praxis/action_definition.rb +9 -16
  12. data/lib/praxis/application.rb +1 -2
  13. data/lib/praxis/bootloader_stages/routing.rb +2 -4
  14. data/lib/praxis/extensions/attribute_filtering.rb +2 -0
  15. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +148 -157
  16. data/lib/praxis/extensions/attribute_filtering/active_record_patches.rb +15 -0
  17. data/lib/praxis/extensions/attribute_filtering/active_record_patches/5x.rb +90 -0
  18. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_0.rb +68 -0
  19. data/lib/praxis/extensions/attribute_filtering/active_record_patches/6_1_plus.rb +58 -0
  20. data/lib/praxis/extensions/attribute_filtering/filter_tree_node.rb +35 -0
  21. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +9 -12
  22. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +3 -2
  23. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +7 -9
  24. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +6 -9
  25. data/lib/praxis/extensions/pagination.rb +130 -0
  26. data/lib/praxis/extensions/pagination/active_record_pagination_handler.rb +42 -0
  27. data/lib/praxis/extensions/pagination/header_generator.rb +70 -0
  28. data/lib/praxis/extensions/pagination/ordering_params.rb +234 -0
  29. data/lib/praxis/extensions/pagination/pagination_handler.rb +68 -0
  30. data/lib/praxis/extensions/pagination/pagination_params.rb +374 -0
  31. data/lib/praxis/extensions/pagination/sequel_pagination_handler.rb +45 -0
  32. data/lib/praxis/handlers/json.rb +2 -0
  33. data/lib/praxis/handlers/www_form.rb +5 -0
  34. data/lib/praxis/handlers/{xml.rb → xml-sample.rb} +6 -0
  35. data/lib/praxis/mapper/active_model_compat.rb +23 -5
  36. data/lib/praxis/mapper/resource.rb +16 -9
  37. data/lib/praxis/mapper/sequel_compat.rb +1 -0
  38. data/lib/praxis/media_type.rb +1 -56
  39. data/lib/praxis/plugins/mapper_plugin.rb +1 -1
  40. data/lib/praxis/plugins/pagination_plugin.rb +71 -0
  41. data/lib/praxis/resource_definition.rb +4 -12
  42. data/lib/praxis/route.rb +2 -4
  43. data/lib/praxis/routing_config.rb +4 -8
  44. data/lib/praxis/tasks/routes.rb +9 -14
  45. data/lib/praxis/validation_handler.rb +1 -2
  46. data/lib/praxis/version.rb +1 -1
  47. data/praxis.gemspec +2 -3
  48. data/spec/functional_spec.rb +9 -6
  49. data/spec/praxis/action_definition_spec.rb +4 -16
  50. data/spec/praxis/api_general_info_spec.rb +6 -6
  51. data/spec/praxis/extensions/attribute_filtering/active_record_filter_query_builder_spec.rb +304 -0
  52. data/spec/praxis/extensions/attribute_filtering/filter_tree_node_spec.rb +39 -0
  53. data/spec/praxis/extensions/attribute_filtering/filtering_params_spec.rb +34 -0
  54. data/spec/praxis/extensions/field_expansion_spec.rb +6 -24
  55. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +15 -11
  56. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +4 -3
  57. data/spec/praxis/extensions/pagination/active_record_pagination_handler_spec.rb +130 -0
  58. data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_active_model.rb +45 -2
  59. data/spec/praxis/extensions/{field_selection/support → support}/spec_resources_sequel.rb +0 -0
  60. data/spec/praxis/media_type_spec.rb +5 -129
  61. data/spec/praxis/request_spec.rb +3 -22
  62. data/spec/praxis/resource_definition_spec.rb +1 -1
  63. data/spec/praxis/response_definition_spec.rb +1 -5
  64. data/spec/praxis/route_spec.rb +2 -9
  65. data/spec/praxis/routing_config_spec.rb +4 -13
  66. data/spec/praxis/types/multipart_array_spec.rb +4 -21
  67. data/spec/spec_app/config/environment.rb +0 -2
  68. data/spec/spec_app/design/api.rb +1 -1
  69. data/spec/spec_app/design/media_types/instance.rb +0 -8
  70. data/spec/spec_app/design/media_types/volume.rb +0 -12
  71. data/spec/spec_app/design/resources/instances.rb +1 -2
  72. data/spec/spec_helper.rb +6 -0
  73. data/spec/support/spec_media_types.rb +0 -73
  74. metadata +35 -45
  75. data/spec/praxis/handlers/xml_spec.rb +0 -177
  76. data/spec/praxis/links_spec.rb +0 -68
@@ -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 :routes
16
- attr_reader :primary_route
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
- @routes = []
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
- @routes = @routing_config.routes
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
- hash[:urls] = routes.collect do |route|
236
- ActionDefinition.url_description(route: route, params: self.params, params_example: params_example)
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 primary_route.nil?
256
- warn "Warning: No routes defined for #{resource_definition.name}##{name}."
248
+ if route.nil?
249
+ warn "Warning: No route defined for #{resource_definition.name}##{name}."
257
250
  else
258
- route_params = primary_route.path.
251
+ route_params = route.path.
259
252
  named_captures.
260
253
  keys.
261
254
  collect(&:to_sym)
@@ -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
- action.routes.each do |route|
27
- target = target_factory(controller, action_name)
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
@@ -0,0 +1,2 @@
1
+ require 'praxis/extensions/attribute_filtering/filtering_params'
2
+ require 'praxis/extensions/attribute_filtering/filter_tree_node'
@@ -1,178 +1,169 @@
1
+
2
+
1
3
  module Praxis
2
4
  module Extensions
3
- class ActiveRecordFilterQueryBuilder
4
- attr_reader :query, :table, :model
5
-
6
- # Abstract class, which needs to be used by subclassing it through the .for method, to set the mapping of attributes
7
- class << self
8
- def for(definition)
9
- Class.new(self) do
10
- @attr_to_column = case definition
11
- when Hash
12
- definition
13
- when Array
14
- definition.each_with_object({}) { |item, hash| hash[item.to_sym] = item }
15
- else
16
- raise "Cannot use FilterQueryBuilder.of without passing an array or a hash (Got: #{definition.class.name})"
17
- end
18
- class << self
19
- attr_reader :attr_to_column
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
- # Base query to build upon
26
- def initialize(query: , model:)
27
- @query = query
28
- @table = model.table_name
29
- @last_join_alias = model.table_name
30
- @alias_counter = 0;
31
- end
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
- def pick_alias( name )
34
- @alias_counter += 1
35
- "#{name}#{@alias_counter}"
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
- def build_clause(filters)
39
- filters.each do |item|
40
- attr = item[:name]
41
- spec = item[:specs]
42
- column_name = attr_to_column[attr]
43
- raise "Filtering by #{attr} not allowed (no mapping found)" unless column_name
44
- if column_name.is_a?(Proc)
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
- # TODO: Support more relationship types (including things like polymorphic..etc)
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
- def do_join_reflection( query, reflection, source_alias, table_alias )
66
- c = query.connection
67
- case reflection
68
- when ActiveRecord::Reflection::BelongsToReflection
69
- join_clause = "INNER JOIN %s as %s ON %s.%s = %s.%s " % \
70
- [c.quote_table_name(reflection.klass.table_name),
71
- c.quote_table_name(table_alias),
72
- c.quote_table_name(table_alias),
73
- c.quote_column_name(reflection.association_primary_key),
74
- c.quote_table_name(source_alias),
75
- c.quote_column_name(reflection.association_foreign_key)
76
- ]
77
- query.joins(join_clause)
78
- when ActiveRecord::Reflection::HasManyReflection
79
- # join_clause = "INNER JOIN #{reflection.klass.table_name} as #{table_alias} ON" + \
80
- # " \"#{source_alias}\".\"id\" = \"#{table_alias}\".\"#{reflection.foreign_key}\" "
81
- join_clause = "INNER JOIN %s as %s ON %s.%s = %s.%s " % \
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
- query.joins(join_clause)
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
- def expand_binding(column_name:,rest: , op:,value:, use_this_name_for_clause: column_name)
120
- unless rest.empty?
121
- joined_alias = pick_alias(column_name)
122
- @query = do_join(query, column_name, @last_join_alias, joined_alias)
123
- saved_join_alias = @last_join_alias
124
- @last_join_alias = joined_alias
125
- new_column_name, *new_rest = rest
126
- expand_binding(column_name: new_column_name, rest: new_rest, op: op, value: value, use_this_name_for_clause: joined_alias)
127
- @last_join_alias = saved_join_alias
128
- else
129
- column_name = "#{use_this_name_for_clause}.#{column_name}"
130
- add_clause(column_name: column_name, op: op, value: value)
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
- # Private to try to funnel all column names through `build_clause` that restricts
140
- # the attribute names better (to allow more difficult SQL injections )
141
- private def add_clause(column_name:, op:, value:)
142
- likeval = get_like_value(value)
143
- @query = case op
144
- when '='
145
- if likeval
146
- query.where("#{column_name} LIKE ?", likeval)
147
- else
148
- query.where(column_name => value)
149
- end
150
- when '!='
151
- if likeval
152
- query.where("#{column_name} NOT LIKE ?", likeval)
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
- query.where.not(column_name => value)
122
+ raise "Unsupported Operator!!! #{op}"
155
123
  end
156
- when '>'
157
- query.where("#{column_name} > ?", value)
158
- when '<'
159
- query.where("#{column_name} < ?", value)
160
- when '>='
161
- query.where("#{column_name} >= ?", value)
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
- # Returns nil if the value was not a fuzzzy pattern
170
- def get_like_value(value)
171
- if value.is_a?(String) && (value[-1] == '*' || value[0] == '*')
172
- likeval = value.dup
173
- likeval[-1] = '%' if value[-1] == '*'
174
- likeval[0] = '%' if value[0] == '*'
175
- likeval
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