clevic 0.8.0 → 0.11.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.
Files changed (52) hide show
  1. data/History.txt +9 -0
  2. data/Manifest.txt +13 -10
  3. data/README.txt +6 -9
  4. data/Rakefile +35 -24
  5. data/TODO +29 -17
  6. data/bin/clevic +84 -37
  7. data/config/hoe.rb +7 -3
  8. data/lib/clevic.rb +2 -4
  9. data/lib/clevic/browser.rb +37 -49
  10. data/lib/clevic/cache_table.rb +55 -165
  11. data/lib/clevic/db_options.rb +32 -21
  12. data/lib/clevic/default_view.rb +66 -0
  13. data/lib/clevic/delegates.rb +51 -67
  14. data/lib/clevic/dirty.rb +101 -0
  15. data/lib/clevic/extensions.rb +24 -38
  16. data/lib/clevic/field.rb +400 -99
  17. data/lib/clevic/item_delegate.rb +32 -33
  18. data/lib/clevic/model_builder.rb +315 -148
  19. data/lib/clevic/order_attribute.rb +53 -0
  20. data/lib/clevic/record.rb +57 -57
  21. data/lib/clevic/search_dialog.rb +71 -67
  22. data/lib/clevic/sql_dialects.rb +33 -0
  23. data/lib/clevic/table_model.rb +73 -120
  24. data/lib/clevic/table_searcher.rb +165 -0
  25. data/lib/clevic/table_view.rb +140 -100
  26. data/lib/clevic/ui/.gitignore +1 -0
  27. data/lib/clevic/ui/browser_ui.rb +55 -56
  28. data/lib/clevic/ui/search_dialog_ui.rb +50 -51
  29. data/lib/clevic/version.rb +2 -2
  30. data/lib/clevic/view.rb +89 -0
  31. data/models/accounts_models.rb +12 -9
  32. data/models/minimal_models.rb +4 -2
  33. data/models/times_models.rb +41 -25
  34. data/models/times_sqlite_models.rb +1 -145
  35. data/models/values_models.rb +15 -16
  36. data/test/test_cache_table.rb +138 -0
  37. data/test/test_helper.rb +131 -0
  38. data/test/test_model_index_extensions.rb +22 -0
  39. data/test/test_order_attribute.rb +62 -0
  40. data/test/test_sql_dialects.rb +77 -0
  41. data/test/test_table_searcher.rb +188 -0
  42. metadata +36 -20
  43. data/bin/import-times +0 -128
  44. data/config/jamis.rb +0 -589
  45. data/env.sh +0 -1
  46. data/lib/active_record/dirty.rb +0 -87
  47. data/lib/clevic/field_builder.rb +0 -42
  48. data/website/index.html +0 -170
  49. data/website/index.txt +0 -17
  50. data/website/screenshot.png +0 -0
  51. data/website/stylesheets/screen.css +0 -131
  52. data/website/template.html.erb +0 -41
@@ -1,20 +1,23 @@
1
1
  # extensions specific to clevic
2
2
 
3
3
  require 'qtext/flags.rb'
4
+ require 'qtext/hash_collector.rb'
5
+
6
+ class Object
7
+ # recursively calls each entry in path_ary
8
+ # will return nil if any entry in path_ary
9
+ # results in a nil value.
10
+ def evaluate_path( path_ary )
11
+ path_ary.inject( self ) do |value, att|
12
+ value.nil? ? nil : value.send( att )
13
+ end
14
+ end
15
+ end
4
16
 
5
17
  module ActiveRecord
6
18
  class Base
7
- # recursively calls each entry in path_ary
8
- def evaluate_path( path_ary )
9
- path_ary.inject( self ) do |value, att|
10
- if value.nil?
11
- nil
12
- else
13
- value.send( att )
14
- end
15
- end
16
- end
17
-
19
+ # checks to see if attribute_sym is either in the column
20
+ # name list, or in the set of reflections.
18
21
  def self.has_attribute?( attribute_sym )
19
22
  if column_names.include?( attribute_sym.to_s )
20
23
  true
@@ -56,23 +59,16 @@ module Qt
56
59
  @field ||= model.field_for_index( self )
57
60
  end
58
61
 
59
- # set the value returned from the gui, as whatever the underlying
60
- # entity wants it to be
61
- # TODO this will break for more than 2 objects in a path
62
- def gui_value=( obj )
63
- entity.send( "#{model.dots[column]}=", obj )
64
- end
65
-
66
62
  def dump
63
+ if valid?
67
64
  <<-EOF
68
- field_name: #{field_name}
69
- field_value: #{field_value}
70
- dotted_path: #{dotted_path.inspect}
71
- attribute_path: #{attribute_path.inspect}
72
- attribute: #{attribute.inspect}
73
- attribute_value: #{attribute_value.inspect}
65
+ field: #{field_name} => #{field_value}
66
+ attribute: #{attribute.inspect} => #{attribute_value.inspect}
74
67
  metadata: #{metadata.inspect}
75
68
  EOF
69
+ else
70
+ 'invalid'
71
+ end
76
72
  end
77
73
 
78
74
  # return the attribute of the underlying entity corresponding
@@ -90,22 +86,12 @@ module Qt
90
86
  end
91
87
 
92
88
  # set the value of the attribute, without following the
93
- # full path
89
+ # full path.
90
+ # TODO remove need to constantly recalculate the attribute writer
94
91
  def attribute_value=( obj )
95
92
  entity.send( "#{attribute.to_s}=", obj )
96
93
  end
97
94
 
98
- # the dotted attribute path, same as a 'column' in the model
99
- def dotted_path
100
- model.dots[column]
101
- end
102
-
103
- # return an array of path elements from dotted_path
104
- def attribute_path
105
- return nil if model.nil?
106
- model.attribute_paths[column]
107
- end
108
-
109
95
  # returns the ActiveRecord column_for_attribute
110
96
  def metadata
111
97
  # use the optimised version
@@ -118,7 +104,7 @@ module Qt
118
104
  metadata.name
119
105
  end
120
106
 
121
- # return the value of the field, it the _id value
107
+ # return the value of the field, it may be the _id value
122
108
  def field_value
123
109
  entity.send( field_name )
124
110
  end
@@ -126,7 +112,6 @@ module Qt
126
112
  # the underlying entity
127
113
  def entity
128
114
  return nil if model.nil?
129
- #~ puts "fetching entity from collection for xy=(#{row},#{column})" if @entity.nil?
130
115
  @entity ||= model.collection[row]
131
116
  end
132
117
 
@@ -148,6 +133,7 @@ module Qt
148
133
  def errors
149
134
  [ entity.errors[field_name.to_sym] ].flatten
150
135
  end
136
+
151
137
  end
152
138
 
153
139
  end
@@ -1,90 +1,224 @@
1
- require 'qtext/flags.rb'
2
-
3
- require 'clevic/field_builder.rb'
1
+ require 'gather.rb'
4
2
 
5
3
  module Clevic
6
4
 
7
5
  =begin rdoc
8
6
  This defines a field in the UI, and how it hooks up to a field in the DB.
7
+
8
+ Attributes marked PROPERTY are DSL-style accessors, where the value can be
9
+ set with either an assignment or by passing a parameter. For example
10
+ property :ixnay
11
+
12
+ will allow
13
+
14
+ # reader
15
+ instance.ixnay
16
+
17
+ #writer
18
+ instance.ixnay = 'nix, baby'
19
+
20
+ #writer
21
+ instance.ixnay 'nix baby'
22
+
23
+ Generally properties are for options that can be passed to the field creation
24
+ method in ModelBuilder, whereas ruby attributes are for the internal workings.
25
+
26
+ #--
27
+ TODO decide whether value_for type methods take an entity and do_something methods
28
+ take a value.
29
+
30
+ TODO this class is a bit confused about whether it handles metadata or record data, or both.
31
+
32
+ TODO meta needs to handle virtual fields better. Also is_date_time?
9
33
  =end
10
34
  class Field
11
- include QtFlags
35
+ # For defining properties
36
+ include Gather
37
+
38
+ # The value to be displayed after being optionally format-ed
39
+ #
40
+ # Takes a String, a Symbol, or a Proc.
41
+ #
42
+ # A String will be a dot-separated path of attributes starting on the object returned by attribute.
43
+ # Paths longer than 1 element haven't been tested much.
44
+ #
45
+ # A Symbol refers to a method to be called on the current entity
46
+ #
47
+ # A Proc will be passed the current entity. This can be used to display 'virtual'
48
+ # fields from related tables, or calculated fields.
49
+ #
50
+ # Defaults to nil, in other words the value of the attribute for this field.
51
+ property :display
52
+
53
+ # The label to be displayed in the column headings. Defaults to the humanised field name.
54
+ property :label
55
+
56
+ # For relational fields, this is the class_name for the related AR entity.
57
+ # TODO not used anymore?
58
+ property :class_name
59
+
60
+ # One of the alignment specifiers - :left, :centre, :right or :justified.
61
+ # Defaults to right for numeric fields, centre for boolean, and left for
62
+ # other values.
63
+ property :alignment
64
+
65
+ # something to do with the icon that Qt displays. Not implemented yet.
66
+ property :decoration
67
+
68
+ # This defines how to format the value returned by :display. It takes a string or a Proc.
69
+ # Generally the string is something
70
+ # that can be understood by strftime (for time and date fields) or understood
71
+ # by % (for everything else). It can also be a Proc that has one parameter -
72
+ # the current entity. There are sensible defaults for common field types.
73
+ property :format
74
+
75
+ # This is just like format, except that it's used to format the value just
76
+ # before it's edited. A good use of this is to display dates with a 2-digit year
77
+ # but edit them with a 4 digit year.
78
+ # Defaults to a sensible value for some fields, for others it will default to the value of :format.
79
+ property :edit_format
80
+
81
+ # Whether the field is currently visible or not.
82
+ property :visible
83
+
84
+ # Sample is used if the programmer wishes to provide a value (that will be converted
85
+ # using to_s) that can be used
86
+ # as the basis for calculating the width of the field. By default this will be
87
+ # calculated from the database, but this may be an expensive operation, and
88
+ # doesn't always work properly. So we
89
+ # have the option to override that if we wish.
90
+ property :sample
91
+
92
+ # Takes a boolean. Set the field to read-only.
93
+ property :read_only
94
+
95
+ # The foreground and background colors.
96
+ # Can take a Proc, a string, or a symbol.
97
+ # - A Proc is called with an entity
98
+ # - A String is treated as a constant which may be one of the string constants understood by Qt::Color
99
+ # - A symbol is treated as a method to be call on an entity
100
+ #
101
+ # The result can be a Qt::Color, or one of the strings in
102
+ # http://www.w3.org/TR/SVG/types.html#ColorKeywords.
103
+ property :foreground, :background
104
+
105
+ # Can take a Proc, a string, or a symbol.
106
+ # - A Proc is called with an entity
107
+ # - A String is treated as a constant
108
+ # - A symbol is treated as a method to be call on an entity
109
+ property :tooltip
110
+
111
+ # The set of allowed values for restricted fields. If it's a hash, the
112
+ # keys will be stored in the db, and the values displayed in the UI.
113
+ property :set
114
+
115
+ # Only for the distinct field type. The values will be sorted either with the
116
+ # most used values first (:frequency => true) or in alphabetical order (:description => true).
117
+ property :frequency, :description
118
+
119
+ # Not implemented. Default value for this field for new records. Not sure how to populate it though.
120
+ property :default
121
+
122
+ # properties for ActiveRecord options
123
+ # There are actually from ActiveRecord::Base.VALID_FIND_OPTIONS, but it's protected
124
+ # each element becomes a property.
125
+ AR_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :from, :lock ]
126
+ AR_FIND_OPTIONS.each{|x| property x}
127
+
128
+ # Return a list of find options and their values, but only
129
+ # if the values are not nil
130
+ def find_options
131
+ AR_FIND_OPTIONS.inject(Hash.new) do |ha,x|
132
+ option_value = self.send(x)
133
+ unless option_value.nil?
134
+ ha[x] = option_value
135
+ end
136
+ ha
137
+ end
138
+ end
139
+
140
+ # The UI delegate class for the field. In Qt, this is a subclass of AbstractItemDelegate.
141
+ attr_accessor :delegate
12
142
 
13
- attr_accessor :attribute, :path, :label, :delegate, :class_name
14
- attr_accessor :alignment, :format, :tooltip, :path_block
15
- attr_accessor :visible
143
+ # The attribute on the AR entity that forms the basis for this field.
144
+ # Accessing the returned attribute (using send, or the [] method on an entity)
145
+ # will give a simple value, or another AR entity in the case of relational fields.
146
+ # In other words, this is *not* the same as the name of the field in the DB, which
147
+ # would normally have an _id suffix for relationships.
148
+ attr_accessor :attribute
16
149
 
17
- attr_writer :sample, :read_only
150
+ # The ActiveRecord::Base subclass this field uses to get data from.
151
+ attr_reader :entity_class
18
152
 
19
- # attribute is the symbol for the attribute on the model_class
20
- def initialize( attribute, model_class, options )
153
+ # Create a new Field object that displays the contents of a database field in
154
+ # the UI using the given parameters.
155
+ # - attribute is the symbol for the attribute on the entity_class.
156
+ # - entity_class is the ActiveRecord::Base subclass which this Field talks to.
157
+ # - options is a hash of writable attributes in Field, which can be any of the properties defined in this class.
158
+ def initialize( attribute, entity_class, options, &block )
21
159
  # sanity checking
22
- unless model_class.has_attribute?( attribute ) or model_class.instance_methods.include?( attribute.to_s )
160
+ unless attribute.is_a?( Symbol )
161
+ raise "attribute #{attribute.inspect} must be a symbol"
162
+ end
163
+
164
+ unless entity_class.ancestors.include?( ActiveRecord::Base )
165
+ raise "entity_class must be a descendant of ActiveRecord::Base"
166
+ end
167
+
168
+ unless entity_class.has_attribute?( attribute ) or entity_class.instance_methods.include?( attribute.to_s )
23
169
  msg = <<EOF
24
- #{attribute} not found in #{model_class.name}. Possibilities are:
25
- #{model_class.attribute_names.join("\n")}
170
+ #{attribute} not found in #{entity_class.name}. Possibilities are:
171
+ #{entity_class.attribute_names.join("\n")}
26
172
  EOF
27
173
  raise msg
28
174
  end
29
175
 
30
- # set values
176
+ # instance variables
31
177
  @attribute = attribute
32
- @model_class = model_class
178
+ @entity_class = entity_class
33
179
  @visible = true
34
180
 
35
- options.each do |key,value|
36
- self.send( "#{key}=", value ) if respond_to?( "#{key}=" )
37
- end
181
+ # initialise
182
+ @value_cache = {}
38
183
 
39
- # TODO could convert everything to a block here, even paths
40
- if options[:display].kind_of?( Proc )
41
- @path_block = options[:display]
42
- else
43
- @path = options[:display]
44
- end
184
+ # handle options
185
+ gather( options, &block )
45
186
 
46
- # default the label
47
- @label ||= attribute.to_s.humanize
48
-
49
- # default formats
50
- if @format.nil?
51
- case meta.type
52
- when :time; @format = '%H:%M'
53
- when :date; @format = '%d-%h-%y'
54
- when :datetime; @format = '%d-%h-%y %H:%M:%S'
55
- when :decimal, :float; @format = "%.2f"
56
- end
57
- end
58
-
59
- # default alignments
60
- if @alignment.nil?
61
- @alignment =
62
- case meta.type
63
- when :decimal, :integer, :float; qt_alignright
64
- when :boolean; qt_aligncenter
65
- end
66
- end
187
+ # set various sensible defaults. They're not lazy accessors because
188
+ # they might stay nil, and we don't want to keep evaluating them.
189
+ default_label!
190
+ default_format!
191
+ default_edit_format!
192
+ default_alignment!
67
193
  end
68
194
 
69
- # Return the attribute value for the given entity, which will probably
70
- # be an ActiveRecord instance
195
+ # Return the attribute value for the given ActiveRecord entity, or nil
196
+ # if entity is nil. Will call transform_attribute.
71
197
  def value_for( entity )
72
- return nil if entity.nil?
73
- transform_attribute( entity.send( attribute ) )
198
+ begin
199
+ return nil if entity.nil?
200
+ transform_attribute( entity.send( attribute ) )
201
+ rescue Exception => e
202
+ puts "error for #{entity}.#{entity.send( attribute ).inspect} in value_for: #{e.message}"
203
+ puts e.backtrace
204
+ end
74
205
  end
75
206
 
76
- # apply path, or path_block, to the given
77
- # attribute value. Otherwise just return
78
- # attribute_value itself
207
+ # Apply display, to the given
208
+ # attribute value. Otherwise just return the
209
+ # attribute_value itself.
79
210
  def transform_attribute( attribute_value )
80
211
  return nil if attribute_value.nil?
81
- case
82
- when !path_block.nil?
83
- path_block.call( attribute_value )
84
-
85
- when !path.nil?
86
- attribute_value.evaluate_path( path.split( /\./ ) )
212
+ case display
213
+ when Proc
214
+ display.call( attribute_value )
87
215
 
216
+ when String
217
+ attribute_value.evaluate_path( display.split( '.' ) )
218
+
219
+ when Symbol
220
+ attribute_value.send( display )
221
+
88
222
  else
89
223
  attribute_value
90
224
  end
@@ -95,29 +229,57 @@ EOF
95
229
  meta.type == ActiveRecord::Reflection::AssociationReflection
96
230
  end
97
231
 
98
- # return true if it's a date, a time or a datetime
99
- # cache result because the type won't change in the lifetime of the field
100
- def is_date_time?
101
- @is_date_time ||= [:time, :date, :datetime].include?( meta.type )
232
+ # Return true if the field is a date, a time or a datetime.
233
+ # If display is nil, the value is calculated, so we need
234
+ # to check the value. Otherwise use the field metadata.
235
+ # Cache the result for the first non-nil value.
236
+ def is_date_time?( value )
237
+ if value.nil?
238
+ false
239
+ else
240
+ @is_date_time ||=
241
+ if display.nil?
242
+ [:time, :date, :datetime].include?( meta.type )
243
+ else
244
+ # it's a virtual field, so we need to use the value
245
+ value.is_a?( Date ) || value.is_a?( Time )
246
+ end
247
+ end
102
248
  end
103
249
 
104
250
  # return ActiveRecord::Base.columns_hash[attribute]
105
251
  # in other words an ActiveRecord::ConnectionAdapters::Column object,
106
252
  # or an ActiveRecord::Reflection::AssociationReflection object
107
253
  def meta
108
- @model_class.columns_hash[attribute.to_s] || @model_class.reflections[attribute]
254
+ @meta ||= @entity_class.columns_hash[attribute.to_s] || @entity_class.reflections[attribute]
255
+ end
256
+
257
+ # return the type of this attribute. Usually one of :string, :integer, :float
258
+ # or some entity class (ActiveRecord::Base subclass)
259
+ def attribute_type
260
+ @attribute_type ||=
261
+ if meta.kind_of?( ActiveRecord::Reflection::MacroReflection )
262
+ meta.klass
263
+ else
264
+ meta.type
265
+ end
109
266
  end
110
267
 
111
268
  # return true if this field can be used in a filter
112
269
  # virtual fields (ie those that don't exist in this field's
113
- # table) can't be filtered on.
270
+ # table) can't be used to filter on.
114
271
  def filterable?
115
272
  !meta.nil?
116
273
  end
117
274
 
118
- # return the name of the field for this Field, quoted for the dbms
275
+ # Return the name of the database field for this Field, quoted for the dbms.
119
276
  def quoted_field
120
- @model_class.connection.quote_column_name( meta.name )
277
+ quote_field( meta.name )
278
+ end
279
+
280
+ # Quote the given string as a field name for SQL.
281
+ def quote_field( field_name )
282
+ @entity_class.connection.quote_column_name( field_name )
121
283
  end
122
284
 
123
285
  # return the result of the attribute + the path
@@ -128,30 +290,57 @@ EOF
128
290
  # return an array of the various attribute parts
129
291
  def attribute_path
130
292
  pieces = [ attribute.to_s ]
131
- pieces.concat( path.split( /\./ ) ) unless path.nil?
293
+ pieces.concat( display.to_s.split( '.' ) ) unless display.is_a? Proc
132
294
  pieces.map{|x| x.to_sym}
133
295
  end
134
296
 
135
- # is the field read-only. Defaults to false.
297
+ # Return true if the field is read-only. Defaults to false.
136
298
  def read_only?
137
299
  @read_only || false
138
300
  end
139
301
 
140
- # format this value. Use strftime for date_time types, or % for everything else
141
- def do_format( value )
142
- if self.format != nil
143
- if is_date_time?
144
- value.strftime( format )
302
+ # apply format to value. Use strftime for date_time types, or % for everything else.
303
+ # If format is a proc, pass value to it.
304
+ def do_generic_format( format, value )
305
+ begin
306
+ unless format.nil?
307
+ if format.is_a? Proc
308
+ format.call( value )
309
+ else
310
+ if is_date_time?( value )
311
+ value.strftime( format )
312
+ else
313
+ format % value
314
+ end
315
+ end
145
316
  else
146
- self.format % value
317
+ value
147
318
  end
148
- else
149
- value
319
+ rescue Exception => e
320
+ puts "format: #{format.inspect}"
321
+ puts "value.class: #{value.class.inspect}"
322
+ puts "value: #{value.inspect}"
323
+ puts e.message
324
+ puts e.backtrace
325
+ nil
150
326
  end
151
327
  end
152
328
 
153
- # return a sample for the field which can be used to size a column in the table
154
- def sample
329
+ def do_format( value )
330
+ do_generic_format( format, value )
331
+ end
332
+
333
+ def do_edit_format( value )
334
+ do_generic_format( edit_format, value )
335
+ end
336
+
337
+ # return a sample for the field which can be used to size the UI field widget
338
+ def sample( *args )
339
+ if !args.empty?
340
+ self.sample = *args
341
+ return
342
+ end
343
+
155
344
  if @sample.nil?
156
345
  self.sample =
157
346
  case meta.type
@@ -168,44 +357,148 @@ EOF
168
357
  # TODO return a width, or something like that
169
358
  when :boolean; 'W'
170
359
 
171
- when ActiveRecord::Reflection::AssociationReflection
172
- #TODO width for relations
360
+ when ActiveRecord::Reflection::AssociationReflection.class
361
+ related_sample
173
362
 
174
363
  else
175
- puts "#{@model_class.name}.#{attribute} is a #{meta.type.inspect}"
364
+ puts "#{@entity_class.name}.#{attribute} is a #{meta.type.inspect}"
176
365
  end
177
366
 
178
- if $options[:debug]
179
- puts "@sample for #{@model_class.name}.#{attribute} #{meta.type}: #{@sample.inspect}"
180
- end
367
+ #~ if $options && $options[:debug]
368
+ #~ puts "@sample for #{@entity_class.name}.#{attribute} #{meta.type}: #{@sample.inspect}"
369
+ #~ end
181
370
  end
182
371
  # if we don't know how to figure it out from the data, just return the label size
183
372
  @sample || self.label
184
373
  end
374
+
375
+ # Called by Clevic::TableModel to get the tooltip value
376
+ def tooltip_for( entity )
377
+ cache_value_for( :background, entity )
378
+ end
379
+
380
+ # TODO Doesn't do anything useful yet.
381
+ def decoration_for( entity )
382
+ nil
383
+ end
384
+
385
+ # Convert something that responds to to_s to a Qt::Color,
386
+ # or just return the argument if it's already a Qt::Color
387
+ def string_or_color( s_or_c )
388
+ case s_or_c
389
+ when Qt::Color
390
+ s_or_c
391
+ else
392
+ Qt::Color.new( s_or_c.to_s )
393
+ end
394
+ end
395
+
396
+ # Called by Clevic::TableModel to get the foreground color value
397
+ def foreground_for( entity )
398
+ cache_value_for( :foreground, entity ) {|x| string_or_color(x)}
399
+ end
400
+
401
+ # Called by Clevic::TableModel to get the background color value
402
+ def background_for( entity )
403
+ cache_value_for( :background, entity ) {|x| string_or_color(x)}
404
+ end
405
+
406
+ protected
407
+
408
+ # call the conversion_block with the value, or just return the
409
+ # value if conversion_block is nil
410
+ def convert_or_identity( value, &conversion_block )
411
+ if conversion_block.nil?
412
+ value
413
+ else
414
+ conversion_block.call( value )
415
+ end
416
+ end
417
+
418
+ # symbol is the property name to fetch a value for.
419
+ # It can be a Proc, a symbol, or a value responding to to_s.
420
+ # In all cases, conversion block will be called
421
+ # conversion_block takes the value expected back from the property
422
+ # and converts it to something that Qt will understand. Mostly
423
+ # this applies to non-strings, ie colors for foreground and background,
424
+ # and an icon resource for decoration - that kind of thing.
425
+ def cache_value_for( symbol, entity, &conversion_block )
426
+ value = send( symbol )
427
+ case value
428
+ when Proc; convert_or_identity( value.call( entity ), &conversion_block ) unless entity.nil?
429
+ when Symbol; convert_or_identity( entity.send( value ), &conversion_block ) unless entity.nil?
430
+ else; @value_cache[symbol] ||=convert_or_identity( value, &conversion_block )
431
+ end
432
+ end
433
+
434
+ def default_label!
435
+ @label ||= attribute.to_s.humanize
436
+ end
437
+
438
+ def default_format!
439
+ if @format.nil?
440
+ @format =
441
+ case meta.type
442
+ when :time; '%H:%M'
443
+ when :date; '%d-%h-%y'
444
+ when :datetime; '%d-%h-%y %H:%M:%S'
445
+ when :decimal, :float; "%.2f"
446
+ end
447
+ end
448
+ @format
449
+ end
450
+
451
+ def default_edit_format!
452
+ if @edit_format.nil?
453
+ @edit_format =
454
+ case meta.type
455
+ when :date; '%d-%h-%Y'
456
+ when :datetime; '%d-%h-%Y %H:%M:%S'
457
+ end || default_format!
458
+ end
459
+ @edit_format
460
+ end
461
+
462
+ def default_alignment!
463
+ if @alignment.nil?
464
+ @alignment =
465
+ case meta.type
466
+ when :decimal, :integer, :float; :right
467
+ when :boolean; :centre
468
+ end
469
+ end
470
+ end
185
471
 
186
472
  private
187
473
 
188
474
  def format_result( result_set )
189
475
  unless result_set.size == 0
190
476
  obj = result_set[0][attribute]
191
- unless obj.nil?
192
- do_format( obj )
193
- end
477
+ do_format( obj ) unless obj.nil?
194
478
  end
195
479
  end
196
480
 
197
- def string_sample( max_sample = nil )
198
- result_set = @model_class.connection.execute <<-EOF
199
- select distinct #{quoted_field}
200
- from #{@model_class.table_name}
481
+ def string_sample( max_sample = nil, entity_class = @entity_class, field_name = meta.name )
482
+ statement = <<-EOF
483
+ select distinct #{quote_field field_name}
484
+ from #{entity_class.table_name}
201
485
  where
202
- length( #{quoted_field} ) = (
203
- select max( length( #{quoted_field} ) )
204
- from #{@model_class.table_name}
486
+ length( #{quote_field field_name} ) = (
487
+ select max( length( #{quote_field field_name} ) )
488
+ from #{entity_class.table_name}
205
489
  )
206
490
  EOF
491
+ result_set = @entity_class.connection.execute statement
207
492
  unless result_set.entries.size == 0
208
- result = result_set[0][0]
493
+ row = result_set[0]
494
+ result =
495
+ case row
496
+ when Array
497
+ row[0]
498
+ when Hash
499
+ row.values[0]
500
+ end
501
+
209
502
  if max_sample.nil?
210
503
  result
211
504
  else
@@ -215,9 +508,9 @@ private
215
508
  end
216
509
 
217
510
  def date_time_sample
218
- result_set = @model_class.find_by_sql <<-EOF
511
+ result_set = @entity_class.find_by_sql <<-EOF
219
512
  select #{quoted_field}
220
- from #{@model_class.table_name}
513
+ from #{@entity_class.table_name}
221
514
  where #{quoted_field} is not null
222
515
  limit 1
223
516
  EOF
@@ -228,13 +521,21 @@ private
228
521
  # TODO Use precision from metadata, not for integers
229
522
  # returns nil for floats. So it's probably not useful
230
523
  #~ puts "meta.precision: #{meta.precision.inspect}"
231
- result_set = @model_class.find_by_sql <<-EOF
524
+ result_set = @entity_class.find_by_sql <<-EOF
232
525
  select max( #{quoted_field} )
233
- from #{@model_class.table_name}
526
+ from #{@entity_class.table_name}
234
527
  EOF
235
528
  format_result( result_set )
236
529
  end
237
530
 
531
+ def related_sample
532
+ # TODO this isn't really the right way to do this
533
+ return nil if meta.nil?
534
+ if meta.klass.attribute_names.include?( attribute_path[1].to_s )
535
+ string_sample( nil, meta.klass, attribute_path[1] )
536
+ end
537
+ end
538
+
238
539
  end
239
540
 
240
541
  end