clevic 0.13.0.b3 → 0.13.0.b5

Sign up to get free protection for your applications and to get access to all the features.
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