clevic 0.13.0.b3 → 0.13.0.b5

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 (82) hide show
  1. data/History.txt +21 -0
  2. data/Manifest.txt +91 -85
  3. data/README.txt +33 -18
  4. data/Rakefile +2 -3
  5. data/TODO +8 -14
  6. data/bin/clevic +18 -20
  7. data/lib/clevic.rb +7 -1
  8. data/lib/clevic/action_builder.rb +4 -1
  9. data/lib/clevic/ar_methods.rb +72 -57
  10. data/lib/clevic/attribute_list.rb +4 -0
  11. data/lib/clevic/cache_table.rb +43 -69
  12. data/lib/clevic/dataset_roller.rb +22 -0
  13. data/lib/clevic/delegate.rb +11 -5
  14. data/lib/clevic/delegates/combo_delegate.rb +156 -0
  15. data/lib/clevic/delegates/distinct_delegate.rb +48 -0
  16. data/lib/clevic/delegates/relational_delegate.rb +59 -0
  17. data/lib/clevic/delegates/set_delegate.rb +31 -0
  18. data/lib/clevic/field.rb +39 -55
  19. data/lib/clevic/field_valuer.rb +23 -10
  20. data/lib/clevic/filter_command.rb +22 -36
  21. data/lib/clevic/framework.rb +37 -0
  22. data/lib/clevic/generic_format.rb +5 -1
  23. data/lib/clevic/many_field.rb +28 -3
  24. data/lib/clevic/model_builder.rb +27 -32
  25. data/lib/clevic/ordered_dataset.rb +45 -0
  26. data/lib/clevic/qt.rb +4 -1
  27. data/lib/clevic/qt/action_builder.rb +9 -1
  28. data/lib/clevic/qt/browser.rb +1 -1
  29. data/lib/clevic/qt/clipboard.rb +3 -3
  30. data/lib/clevic/qt/combo_delegate.rb +25 -89
  31. data/lib/clevic/qt/delegate.rb +25 -0
  32. data/lib/clevic/qt/distinct_delegate.rb +5 -23
  33. data/lib/clevic/qt/extensions.rb +8 -1
  34. data/lib/clevic/qt/qt_combo_box.rb +58 -0
  35. data/lib/clevic/qt/relational_delegate.rb +18 -58
  36. data/lib/clevic/qt/set_delegate.rb +4 -34
  37. data/lib/clevic/qt/simplest_delegate.rb +19 -0
  38. data/lib/clevic/qt/table_model.rb +10 -10
  39. data/lib/clevic/qt/table_view.rb +7 -23
  40. data/lib/clevic/qt/text_delegate.rb +2 -2
  41. data/lib/clevic/qt/ui/browser_ui.rb +1 -1
  42. data/lib/clevic/qt/ui/search_dialog_ui.rb +1 -1
  43. data/lib/clevic/rails_models_loaders.rb +13 -0
  44. data/lib/clevic/record.rb +2 -2
  45. data/lib/clevic/sampler.rb +85 -39
  46. data/lib/clevic/sequel_ar_adapter.rb +1 -28
  47. data/lib/clevic/sequel_clevic.rb +68 -0
  48. data/lib/clevic/sequel_meta.rb +1 -13
  49. data/lib/clevic/subclasses.rb +18 -0
  50. data/lib/clevic/swing.rb +2 -1
  51. data/lib/clevic/swing/action.rb +27 -3
  52. data/lib/clevic/swing/action_builder.rb +0 -2
  53. data/lib/clevic/swing/browser.rb +1 -10
  54. data/lib/clevic/swing/combo_delegate.rb +45 -133
  55. data/lib/clevic/swing/delegate.rb +2 -0
  56. data/lib/clevic/swing/distinct_delegate.rb +2 -14
  57. data/lib/clevic/swing/relational_delegate.rb +2 -20
  58. data/lib/clevic/swing/set_delegate.rb +13 -28
  59. data/lib/clevic/swing/table_view.rb +1 -1
  60. data/lib/clevic/table_model.rb +3 -4
  61. data/lib/clevic/table_searcher.rb +10 -31
  62. data/lib/clevic/table_view.rb +97 -43
  63. data/lib/clevic/ui/browser_ui.rb +133 -0
  64. data/lib/clevic/ui/search_dialog_ui.rb +106 -0
  65. data/lib/clevic/version.rb +2 -2
  66. data/models/accounts_models.rb +24 -21
  67. data/models/times_models.rb +34 -28
  68. data/models/times_psql_models.rb +9 -3
  69. data/models/times_sqlite_models.rb +24 -1
  70. data/sql/times_sqlite.sql +3 -3
  71. data/tasks/clevic.rake +2 -2
  72. data/test/test_cache_table.rb +9 -19
  73. data/test/test_table_searcher.rb +2 -5
  74. metadata +95 -91
  75. data/lib/clevic/order_attribute.rb +0 -63
  76. data/lib/clevic/qt/boolean_delegate.rb +0 -8
  77. data/lib/clevic/qt/delegates.rb +0 -1
  78. data/lib/clevic/qt/item_delegate.rb +0 -66
  79. data/lib/clevic/sql_dialects.rb +0 -33
  80. data/tasks/website.rake +0 -25
  81. data/test/test_order_attribute.rb +0 -62
  82. data/test/test_sql_dialects.rb +0 -77
@@ -0,0 +1,48 @@
1
+ require 'clevic/delegates/combo_delegate.rb'
2
+
3
+ module Clevic
4
+
5
+ # Provide a list of all values in this field,
6
+ # and allow new values to be entered.
7
+ # :frequency can be set as an option. Boolean. If it's true
8
+ # the options are sorted in order of most frequently used first.
9
+ class DistinctDelegate
10
+
11
+ def needs_combo?
12
+ # works except when there is a null in the column
13
+ dataset.count > 0
14
+ end
15
+
16
+ # TODO move away from ar_methods. Partly done.
17
+ # TODO ordering by either recentness, or frequency. OR both.
18
+ # TODO make sure nil is in the list. And the current item is at the top.
19
+ # TODO and the current item is in the list, even if it's older
20
+ # we only use the first column, so use the second
21
+ # column to sort by, since SQL requires the order by clause
22
+ # to be in the select list where distinct is involved
23
+ def dataset
24
+ base_dataset =
25
+ unless field.find_options.empty?
26
+ puts "conditions and order are deprecated. Use dataset instead."
27
+ require 'clevic/ar_methods'
28
+ field.entity_class.plugin :ar_methods
29
+ field.entity_class.translate( field.find_options )
30
+ else
31
+ field.dataset_roller.dataset
32
+ end
33
+
34
+ # now pull out the field and the distinct values
35
+ base_dataset. \
36
+ distinct. \
37
+ select( field.attribute ). \
38
+ order( field.attribute ). \
39
+ naked
40
+ end
41
+
42
+ def population
43
+ dataset.map( field.attribute )
44
+ end
45
+
46
+ end
47
+
48
+ end
@@ -0,0 +1,59 @@
1
+ require 'clevic/delegates/combo_delegate'
2
+ require 'clevic/dataset_roller.rb'
3
+
4
+ module Clevic
5
+
6
+ # Display a collection of possible related entities in the combo box.
7
+ # TODO this should be a module
8
+ class RelationalDelegate
9
+ def needs_combo?
10
+ dataset.count > 0
11
+ end
12
+
13
+ def empty_set_message
14
+ "There must be records in #{field.related_class} for this field to be editable."
15
+ end
16
+
17
+ def population
18
+ # dataset contains the set of all possible related entities,
19
+
20
+ # dataset is defined in Delegate
21
+ # entity is set in init_component
22
+ # field and entity are used by FieldValuer
23
+
24
+ # including the current entity.
25
+ # Could also use
26
+ # dataset.or( entity_class.primary_key => entity_key.pk )
27
+ # but that would put current entity in the list somewhere
28
+ # other than the top, which seems to be the most sensible
29
+ # place for it. Could also create a special enumerator
30
+ # which gives back the entity first, followed by the dataset.
31
+ dataset.all.with do |values|
32
+ # make sure there's only one instance of the current value,
33
+ # and make sure it's at the top of the list
34
+ values.delete( attribute_value )
35
+ values.unshift( attribute_value )
36
+ end
37
+ end
38
+
39
+ # don't allow new values
40
+ def restricted?; true; end
41
+
42
+ protected
43
+ # Return an instance of the ORM dataset,
44
+ # right now that's Sequel::Dataset.
45
+ # This exists because convincing this functionality to
46
+ # coexist in the same method as dataset would be tricky.
47
+ def dataset
48
+ unless field.find_options.empty?
49
+ require 'clevic/ar_methods'
50
+ field.related_class.plugin :ar_methods
51
+ field.related_class.translate( field.find_options )
52
+ else
53
+ field.dataset_roller.dataset
54
+ end
55
+ end
56
+
57
+ end
58
+
59
+ end
@@ -0,0 +1,31 @@
1
+ require 'clevic/delegates/combo_delegate.rb'
2
+
3
+ module Clevic
4
+
5
+ # A Combo box which allows a set of values. May or may not
6
+ # be restricted to the set.
7
+ # TODO this should be a module
8
+ class SetDelegate
9
+ # options must contain a :set => [ ... ] to specify the set of values.
10
+ def initialize( field )
11
+ raise "SetDelegate must have a :set in options" if field.set.nil?
12
+ super
13
+ end
14
+
15
+ def needs_combo?
16
+ true
17
+ end
18
+
19
+ def restricted?
20
+ field.restricted || false
21
+ end
22
+
23
+ # Items here could either be single values,
24
+ # or two-value arrays (from a hash-like set), so use key as db value
25
+ # and value as display value
26
+ def population
27
+ field.set_for( entity )
28
+ end
29
+ end
30
+
31
+ end
data/lib/clevic/field.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require 'gather'
2
2
  require 'clevic/sampler.rb'
3
3
  require 'clevic/generic_format.rb'
4
+ require 'clevic/dataset_roller.rb'
5
+ require 'clevic/many_field.rb'
4
6
 
5
7
  module Clevic
6
8
 
@@ -136,7 +138,7 @@ class Field
136
138
  # An Enumerable of allowed values for restricted fields. If each yields
137
139
  # two values (like it does for a Hash), the
138
140
  # first will be stored in the db, and the second displayed in the UI.
139
- # If it's a proc, it must return an Enumerable as above.
141
+ # If it's a proc, that must return an Enumerable as above.
140
142
  property :set
141
143
 
142
144
  ##
@@ -150,6 +152,7 @@ class Field
150
152
  # Only for the distinct field type. The values will be sorted either with the
151
153
  # most used values first (:frequency => true) or in
152
154
  # alphabetical order (:description => true).
155
+ # FIXME re-implement this with Dataset
153
156
  property :frequency, :description
154
157
 
155
158
  ##
@@ -160,7 +163,8 @@ class Field
160
163
 
161
164
  ##
162
165
  # The property used for finding the field, ie by TableModel#field_column.
163
- # Defaults to the attribute.
166
+ # Defaults to the attribute. If there are several display fields based on
167
+ # one db field, their attribute will be the same, but their id must be different.
164
168
  property :id
165
169
 
166
170
  ##
@@ -168,11 +172,26 @@ class Field
168
172
  # Either a proc( clevic_view, table_view, model_index ) or a symbol
169
173
  # for a method( view, model_index ) on the Clevic::View object.
170
174
  property :notify_data_changed
175
+
176
+ ##
177
+ # This is the dataset of related objects.
178
+ # Called in configuration for a field that works with a relationship.
179
+ # dataset.filter( :blah => 'etc' ).order( :interesting_field )
180
+ def dataset
181
+ dataset_roller
182
+ end
183
+
184
+ # TODO Still getting the Builder/Built conflict
185
+ def dataset_roller
186
+ # related class if it's an association, entity_class otherwise
187
+ @dataset_roller ||= DatasetRoller.new( ( association? ? related_class : entity_class ).dataset )
188
+ end
171
189
 
172
190
  # The list of properties for ActiveRecord options.
173
191
  # There are actually from ActiveRecord::Base.VALID_FIND_OPTIONS, but it's protected.
174
192
  # Each element becomes a property.
175
- # TODO remove these? That will destroy the migration path.
193
+ # TODO deprecate these
194
+ # TODO warn or raise if these are used together with a dataset call
176
195
  AR_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :order, :select, :readonly, :group, :from, :lock ]
177
196
  AR_FIND_OPTIONS.each{|x| property x}
178
197
 
@@ -188,6 +207,10 @@ class Field
188
207
  end
189
208
  end
190
209
 
210
+ # The model object (eg TableModel) this field is part of.
211
+ # Set to TableModel by ModelBuilder#build
212
+ attr_accessor :model
213
+
191
214
  # The UI delegate class for the field. The delegate class knows how to create a UI
192
215
  # for this field using whatever GUI toolkit is selected
193
216
  attr_accessor :delegate
@@ -243,36 +266,6 @@ EOF
243
266
  default_display! if association?
244
267
  end
245
268
 
246
- # x_to_many fields are by definition collections of other entities
247
- def many( &block )
248
- if block
249
- many_view( &block )
250
- else
251
- many_view do |mb|
252
- # TODO should fetch this from one of the field definitions
253
- mb.plain related_attribute
254
- end
255
- end
256
- end
257
-
258
- def many_builder
259
- @many_view.builder
260
- end
261
-
262
- def many_fields
263
- many_builder.fields
264
- end
265
-
266
- # return an instance of Clevic::View that represents the many items
267
- # for this field
268
- def many_view( &block )
269
- @many_view ||= View.new( :entity_class => related_class, &block )
270
- end
271
-
272
- # The model object (eg TableModel) this field is part of.
273
- # Set to TableModel by ModelBuilder#build
274
- attr_accessor :model
275
-
276
269
  # Return the attribute value for the given Object Relational Model instance, or nil
277
270
  # if entity is nil. Will call transform_attribute.
278
271
  def value_for( entity )
@@ -315,13 +308,6 @@ EOF
315
308
  entity_class.meta[attribute]
316
309
  end
317
310
 
318
- # return the type of this attribute. Usually one of :string, :integer, :float
319
- # or some entity class
320
- # TODO remove
321
- def attribute_type
322
- meta.type
323
- end
324
-
325
311
  # return true if this field can be used in a filter
326
312
  # virtual fields (ie those that don't exist in this field's
327
313
  # table) can't be used to filter on.
@@ -337,7 +323,7 @@ EOF
337
323
  # return the class object of a related class if this is a relational
338
324
  # field, otherwise nil
339
325
  def related_class
340
- return nil unless entity_class.meta.has_key?( attribute )
326
+ return nil unless association? && entity_class.meta.has_key?( attribute )
341
327
  @related_class ||= eval( entity_class.meta[attribute].class_name || attribute.to_s.classify )
342
328
  end
343
329
 
@@ -363,26 +349,24 @@ EOF
363
349
  do_generic_format( edit_format, value )
364
350
  end
365
351
 
366
- # Return a sample for the field which can be used to size the UI field widget.
352
+ # Set or return a sample for the field which can be used to size the UI field widget.
367
353
  def sample( *args )
368
354
  if !args.empty?
369
355
  @sample = args.first
370
356
  self
371
357
  else
372
358
  if @sample.nil?
373
- if meta.type == :boolean
374
- @sample = self.label
375
- else
376
- begin
377
- @sample ||= Sampler.new( entity_class, attribute, display ) do |value|
378
- do_format( value )
379
- end.compute
380
- rescue
381
- puts $!
382
- ensure
383
- # if we don't know how to figure it out from the data, just return the label size
384
- @sample ||= self.label
385
- end
359
+ begin
360
+ @sample ||= Sampler.new( self ) do |value|
361
+ do_format( value )
362
+ end.compute
363
+ rescue
364
+ puts "for #{entity_class.name}"
365
+ puts $!.message
366
+ puts $!.backtrace
367
+ ensure
368
+ # if we don't know how to figure it out from the data, just return the label size
369
+ @sample ||= self.label
386
370
  end
387
371
  end
388
372
  @sample
@@ -1,6 +1,6 @@
1
1
  module Clevic
2
- # to be included in something that responds to entity and field
3
- # used for getting values from the entity based on the definitions
2
+ # To be included in something that responds to entity and field methods.
3
+ # Used for getting values from the entity based on the definitions
4
4
  # in the field.
5
5
  module FieldValuer
6
6
 
@@ -26,13 +26,19 @@ module Clevic
26
26
  self.attribute_value =
27
27
  case
28
28
  # allow flexibility in entering dates. For example
29
- # 16jun, 16-jun, 16 jun, 16 jun 2007 would be accepted here
30
- # TODO need to be cleverer about which year to use
31
- # for when you're entering 16dec and you're in the next
32
- # year
29
+ # 16jun, 16-jun, 16/jun would be accepted here.
30
+ # Previous year is used if data capture is in January
31
+ # or February, and date.month is aug-dec.
33
32
  when [:date,:datetime].include?( field.meta.type ) && value =~ %r{^(\d{1,2})[ /-]?(\w{3})$}
34
- Date.parse( "#$1 #$2 #{Time.now.year.to_s}" )
35
-
33
+ today = Date.today
34
+ date = Date.parse( "#$1 #$2 #{Time.now.year.to_s}" )
35
+
36
+ # change year to last year
37
+ if (1..2).include?( today.month ) and (8..12).include?( date.month )
38
+ date = Date.civil( Date.today.year - 1, date.month, date.day )
39
+ end
40
+ date
41
+
36
42
  # if a digit only is entered, fetch month and year from
37
43
  # previous row
38
44
  when [:date,:datetime].include?( field.meta.type ) && value =~ %r{^(\d{1,2})$}
@@ -44,8 +50,10 @@ module Clevic
44
50
  value
45
51
  end
46
52
 
47
- # this one is mostly to fix date strings that have come
53
+ # Mostly to fix date strings that have come
48
54
  # out of the db and been formatted
55
+ # Accepts dd mmm yy.
56
+ # Assumes 20 as the century.
49
57
  when [:date,:datetime].include?( field.meta.type ) && value =~ %r{^(\d{2})[ /-](\w{3})[ /-](\d{2})$}
50
58
  Date.parse( "#$1 #$2 20#$3" )
51
59
 
@@ -60,6 +68,7 @@ module Clevic
60
68
  # do various transforms
61
69
  case
62
70
  # accept a space or a comma instead of a . for floats
71
+ # as long as there are only 2 decimal places
63
72
  when value =~ /(.*?)(\d)[ ,](\d{2})$/
64
73
  "#$1#$2.#$3"
65
74
  else
@@ -72,7 +81,7 @@ module Clevic
72
81
  end
73
82
 
74
83
  def find_related( attribute, value )
75
- candidates = field.related_class.adaptor.find( :all, :conditions => {attribute => value} )
84
+ candidates = field.related_class.filter( attribute.to_sym => value ).all
76
85
  if candidates.size != 1
77
86
  raise "#{candidates.size} != 1 candidates for #{value}: #{candidates.inspect}"
78
87
  end
@@ -152,6 +161,10 @@ module Clevic
152
161
  end
153
162
  end
154
163
 
164
+ def valuer( entity )
165
+ Valuer.new(field,entity)
166
+ end
167
+
155
168
  def self.valuer( field, entity )
156
169
  Valuer.new(field,entity)
157
170
  end
@@ -1,41 +1,32 @@
1
1
  module Clevic
2
2
  class FilterCommand
3
- # TODO seems like either filter_indexes, filter_conditions can be
4
- # passed. Better docs needed, and better api.
5
- def initialize( table_view, filter_indexes, filter_conditions )
3
+ # filter_block will be passed a Dataset to filter.
4
+ # filter_message will be displayed.
5
+ def initialize( table_view, message, &filter_block )
6
6
  @table_view = table_view
7
- @filter_conditions = filter_conditions
8
- @filter_indexes = filter_indexes
9
-
10
- # Better make the status message now, before the indexes become invalid
11
- @status_message =
12
- if filter_indexes.empty?
13
- # no indexes, so use filter_conditions.
14
- "Filtered on #{filter_conditions.inspect}"
15
- else
16
- "Filtered on #{filter_indexes.first.field.label} = #{filter_indexes.first.display_value}"
17
- end
7
+ @message = message
8
+ @filter_block = filter_block
18
9
  end
19
10
 
11
+ attr_reader :message
12
+
20
13
  # Do the filtering. Return true if successful, false otherwise.
21
14
  def doit
22
- begin
23
- # store current AR conditions
24
- @stored_conditions = @table_view.model.cache_table.find_options
25
-
26
- # store auto_new
27
- @auto_new = @table_view.model.auto_new
28
-
29
- # reload cache table with new conditions
30
- @table_view.model.auto_new = false
31
- @table_view.model.reload_data( @filter_conditions )
32
- rescue Exception => e
33
- puts
34
- puts e.message
35
- puts e.backtrace
36
- false
37
- end
15
+ # store current dataset
16
+ @previous_dataset = @table_view.model.cache_table.dataset
17
+
18
+ # store auto_new
19
+ @auto_new = @table_view.model.auto_new
20
+
21
+ # reload cache table with new conditions
22
+ @table_view.model.auto_new = false
23
+ @table_view.model.reload_data( &@filter_block )
38
24
  true
25
+ rescue Exception => e
26
+ puts
27
+ puts e.message
28
+ puts e.backtrace
29
+ false
39
30
  end
40
31
 
41
32
  def undo
@@ -43,12 +34,7 @@ module Clevic
43
34
  @table_view.model.auto_new = @auto_new
44
35
 
45
36
  # reload cache table with stored AR conditions
46
- @table_view.model.reload_data( @stored_conditions )
47
- end
48
-
49
- # return a message based on the conditions
50
- def status_message
51
- @status_message
37
+ @table_view.model.reload_data( @previous_dataset )
52
38
  end
53
39
  end
54
40
  end