clevic 0.6.0 → 0.7.0
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.
- data/History.txt +182 -0
- data/Manifest.txt +7 -3
- data/README.txt +36 -23
- data/Rakefile +22 -7
- data/TODO +55 -45
- data/bin/clevic +7 -2
- data/config/hoe.rb +4 -2
- data/lib/clevic.rb +1 -0
- data/lib/clevic/browser.rb +38 -15
- data/lib/clevic/cache_table.rb +24 -8
- data/lib/clevic/db_options.rb +11 -8
- data/lib/clevic/delegates.rb +24 -13
- data/lib/clevic/extensions.rb +6 -2
- data/lib/clevic/field.rb +55 -6
- data/lib/clevic/item_delegate.rb +7 -6
- data/lib/clevic/model_builder.rb +35 -8
- data/lib/clevic/record.rb +28 -0
- data/lib/clevic/table_model.rb +21 -33
- data/lib/clevic/table_view.rb +48 -1
- data/lib/clevic/ui/browser.ui +4 -4
- data/lib/clevic/ui/browser_ui.rb +73 -74
- data/lib/clevic/ui/search_dialog_ui.rb +50 -51
- data/lib/clevic/version.rb +1 -1
- data/{accounts_models.rb → models/accounts_models.rb} +12 -12
- data/models/minimal_models.rb +19 -0
- data/models/times_models.rb +156 -0
- data/{times_models.rb → models/times_sqlite_models.rb} +10 -18
- data/{values_models.rb → models/values_models.rb} +2 -3
- data/script/console +1 -1
- data/sql/accounts.sql +48 -48
- data/sql/times.sql +25 -25
- data/sql/times_sqlite.sql +46 -0
- data/website/index.html +44 -18
- data/website/index.txt +8 -1
- data/website/template.html.erb +2 -1
- metadata +34 -8
data/bin/clevic
CHANGED
@@ -20,7 +20,7 @@ BANNER
|
|
20
20
|
oparser.separator ''
|
21
21
|
|
22
22
|
oparser.on( '-H', '--host HOST', 'RDBMS host', String ) { |o| $options[:host] = o }
|
23
|
-
oparser.on( '-u', '--user USERNAME', String ) { |o| $options[:
|
23
|
+
oparser.on( '-u', '--user USERNAME', String ) { |o| $options[:username] = o }
|
24
24
|
oparser.on( '-p', '--pass PASSWORD', String ) { |o| $options[:password] = o }
|
25
25
|
oparser.on( '-t', '--table TABLE', 'Table to display', String ) { |o| $options[:table] = o }
|
26
26
|
oparser.on( '-d', '--database DATABASE', 'Database name', String ) { |o| $options[:database] = o }
|
@@ -33,6 +33,11 @@ end
|
|
33
33
|
|
34
34
|
args = oparser.parse( ARGV )
|
35
35
|
|
36
|
+
if $options[:debug]
|
37
|
+
require 'pp'
|
38
|
+
pp $options
|
39
|
+
end
|
40
|
+
|
36
41
|
if args.size > 0
|
37
42
|
$options[:definition] = args.shift
|
38
43
|
require_if "#{$options[:definition]}_models"
|
@@ -46,8 +51,8 @@ app = Qt::Application.new( args )
|
|
46
51
|
# show UI
|
47
52
|
main_window = Qt::MainWindow.new
|
48
53
|
browser = Clevic::Browser.new( main_window )
|
49
|
-
browser.open
|
50
54
|
# this must come after Clevic::Browser.new
|
55
|
+
# TODO should really find a better place for this
|
51
56
|
main_window.window_title = $options[:database]
|
52
57
|
main_window.show
|
53
58
|
# make sure any partially edited records are saved when the window is closed
|
data/config/hoe.rb
CHANGED
@@ -8,10 +8,12 @@ RUBYFORGE_PROJECT = 'clevic' # The unix name for your project
|
|
8
8
|
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
9
9
|
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
10
|
EXTRA_DEPENDENCIES = [
|
11
|
-
['qtext', '>=0.
|
12
|
-
['activerecord', '
|
11
|
+
['qtext', '>=0.4.1'],
|
12
|
+
['activerecord', '=2.0.2'],
|
13
|
+
['fastercsv', '>=1.2.3']
|
13
14
|
# This isn't always installed from gems
|
14
15
|
#~ ['qtruby4', '>=1.4.9']
|
16
|
+
# bsearch can't be installed from gems
|
15
17
|
] # An array of rubygem dependencies [name, version]
|
16
18
|
|
17
19
|
@config_file = "~/.rubyforge/user-config.yml"
|
data/lib/clevic.rb
CHANGED
data/lib/clevic/browser.rb
CHANGED
@@ -52,6 +52,7 @@ class Browser < Qt::Widget
|
|
52
52
|
|
53
53
|
# as an example
|
54
54
|
#~ tables_tab.connect SIGNAL( 'currentChanged(int)' ) { |index| puts "other current_changed: #{index}" }
|
55
|
+
load_models
|
55
56
|
end
|
56
57
|
|
57
58
|
# activated by Ctrl-D for debugging
|
@@ -155,15 +156,36 @@ class Browser < Qt::Widget
|
|
155
156
|
Qt::Application.translate("Browser", st, nil, Qt::Application::UnicodeUTF8)
|
156
157
|
end
|
157
158
|
|
158
|
-
# return the list of
|
159
|
-
|
160
|
-
|
161
|
-
if
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
159
|
+
# return the list of descendants of ActiveRecord::Base
|
160
|
+
def find_models
|
161
|
+
models = []
|
162
|
+
ObjectSpace.each_object( Class ) {|x| models << x if x.ancestors.include?( Clevic::Record ) }
|
163
|
+
models
|
164
|
+
end
|
165
|
+
|
166
|
+
# define a default ui with plain fields for all
|
167
|
+
# columns (except id) in the model. Could combine this with
|
168
|
+
# DrySQL to automate the process.
|
169
|
+
def define_default_ui( model )
|
170
|
+
reflections = model.reflections.keys.map{|x| x.to_s}
|
171
|
+
ui_columns = model.columns.reject{|x| x.name == 'id' }.map do |x|
|
172
|
+
att = x.name.gsub( '_id', '' )
|
173
|
+
if reflections.include?( att )
|
174
|
+
att
|
175
|
+
else
|
176
|
+
x.name
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
Clevic::TableView.new( model, tables_tab ).create_model do
|
181
|
+
ui_columns.each do |column|
|
182
|
+
if model.reflections.has_key?( column.to_sym )
|
183
|
+
relational column.to_sym
|
184
|
+
else
|
185
|
+
plain column.to_sym
|
186
|
+
end
|
187
|
+
end
|
188
|
+
records :order => 'id'
|
167
189
|
end
|
168
190
|
end
|
169
191
|
|
@@ -171,17 +193,18 @@ class Browser < Qt::Widget
|
|
171
193
|
#
|
172
194
|
# models parameter can be an array of Model objects, in order of display.
|
173
195
|
# if models is nil, find_models is called
|
174
|
-
def
|
175
|
-
models =
|
196
|
+
def load_models
|
197
|
+
models = Clevic::Record.models || find_models
|
176
198
|
|
177
199
|
# Add all existing model objects as tabs, one each
|
178
|
-
|
200
|
+
models.each do |model|
|
201
|
+
tab =
|
179
202
|
if model.respond_to?( :ui )
|
180
|
-
|
181
|
-
tab.connect( SIGNAL( 'status_text(QString)' ) ) { |msg| @layout.statusbar.show_message( msg, 20000 ) }
|
203
|
+
model.ui( tables_tab )
|
182
204
|
else
|
183
|
-
|
205
|
+
define_default_ui( model )
|
184
206
|
end
|
207
|
+
tab.connect( SIGNAL( 'status_text(QString)' ) ) { |msg| @layout.statusbar.show_message( msg, 20000 ) }
|
185
208
|
tables_tab.add_tab( tab, translate( model.name.humanize ) )
|
186
209
|
end
|
187
210
|
end
|
data/lib/clevic/cache_table.rb
CHANGED
@@ -3,8 +3,6 @@ require 'active_record'
|
|
3
3
|
require 'active_record/dirty.rb'
|
4
4
|
require 'bsearch'
|
5
5
|
|
6
|
-
require 'profiler'
|
7
|
-
|
8
6
|
=begin rdoc
|
9
7
|
Store the SQL order_by attributes with ascending and descending values
|
10
8
|
=end
|
@@ -73,7 +71,7 @@ for each call?
|
|
73
71
|
class CacheTable < Array
|
74
72
|
# the number of records loaded in one call to the db
|
75
73
|
attr_accessor :preload_count
|
76
|
-
attr_reader :options
|
74
|
+
attr_reader :options, :model_class
|
77
75
|
|
78
76
|
def initialize( model_class, find_options = {} )
|
79
77
|
@preload_count = 20
|
@@ -106,13 +104,25 @@ class CacheTable < Array
|
|
106
104
|
@model_class.connection.quote_column_name( field_name )
|
107
105
|
end
|
108
106
|
|
107
|
+
# return a string containing the correct
|
108
|
+
# boolean value depending on the DB adapter
|
109
|
+
# because Postgres wants real true and false in complex statements, not 't' and 'f'
|
110
|
+
def sql_boolean( value )
|
111
|
+
case model_class.connection.adapter_name
|
112
|
+
when 'PostgreSQL'
|
113
|
+
value ? 'true' : 'false'
|
114
|
+
else
|
115
|
+
value ? model_class.connection.quoted_true : model_class.connection.quoted_false
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
109
119
|
# recursively create a case statement to do the comparison
|
110
120
|
# because and ... and ... and filters on *each* one rather than
|
111
121
|
# consecutively.
|
112
122
|
# operator is either '<' or '>'
|
113
123
|
def build_recursive_comparison( operator, index = 0 )
|
114
124
|
# end recursion
|
115
|
-
return
|
125
|
+
return sql_boolean( false ) if index == @order_attributes.size
|
116
126
|
|
117
127
|
# fetch the current attribute
|
118
128
|
attribute = @order_attributes[index]
|
@@ -120,11 +130,12 @@ class CacheTable < Array
|
|
120
130
|
# build case statement, including recusion
|
121
131
|
st = <<-EOF
|
122
132
|
case
|
123
|
-
when #{quote_column attribute} #{operator} :#{attribute} then true
|
133
|
+
when #{quote_column attribute} #{operator} :#{attribute} then #{sql_boolean true}
|
124
134
|
when #{quote_column attribute} = :#{attribute} then #{build_recursive_comparison( operator, index+1 )}
|
125
|
-
else false
|
135
|
+
else #{sql_boolean false}
|
126
136
|
end
|
127
137
|
EOF
|
138
|
+
st.gsub!( /^/, ' ' * index )
|
128
139
|
end
|
129
140
|
|
130
141
|
# return a Hash containing
|
@@ -143,6 +154,13 @@ EOF
|
|
143
154
|
# build the sql comparison where clause fragment
|
144
155
|
sql = build_recursive_comparison( operator )
|
145
156
|
|
157
|
+
# only Postgres seems to understand real booleans
|
158
|
+
# everything else needs the big case statement to be compared
|
159
|
+
# to something
|
160
|
+
unless model_class.connection.adapter_name == 'PostgreSQL'
|
161
|
+
sql += " = #{sql_boolean true}"
|
162
|
+
end
|
163
|
+
|
146
164
|
# build parameter values
|
147
165
|
params = {}
|
148
166
|
@order_attributes.each {|x| params[x.to_sym] = entity.send( x.attribute )}
|
@@ -234,10 +252,8 @@ EOF
|
|
234
252
|
|
235
253
|
# only load one record at a time
|
236
254
|
preload_limit( 1 ) do
|
237
|
-
#~ puts "entity: #{entity.inspect}"
|
238
255
|
# do the binary search based on what we know about the search order
|
239
256
|
bsearch do |candidate|
|
240
|
-
#~ puts "candidate: #{candidate.inspect}"
|
241
257
|
# find using all sort attributes
|
242
258
|
order_attributes.inject(0) do |result,attribute|
|
243
259
|
if result == 0
|
data/lib/clevic/db_options.rb
CHANGED
@@ -8,7 +8,7 @@ connection to a particular database. Like this:
|
|
8
8
|
Clevic::DbOptions.connect( $options ) do
|
9
9
|
database :accounts
|
10
10
|
adapter :postgresql
|
11
|
-
username '
|
11
|
+
username 'accounts_user'
|
12
12
|
end
|
13
13
|
|
14
14
|
When the block ends, a check is done to see that the :database key
|
@@ -21,7 +21,9 @@ with the options value passed in (in this case $options).
|
|
21
21
|
Values have to_s called on them so they can be symbols or strings.
|
22
22
|
=end
|
23
23
|
class DbOptions
|
24
|
-
|
24
|
+
attr_reader :options
|
25
|
+
|
26
|
+
def initialize( options = nil )
|
25
27
|
@options = options || {}
|
26
28
|
# make sure the relevant entries exist, so method_missing works
|
27
29
|
@options[:adapter] ||= ''
|
@@ -44,21 +46,22 @@ class DbOptions
|
|
44
46
|
end
|
45
47
|
|
46
48
|
# connect to db
|
47
|
-
ActiveRecord::Base.establish_connection(
|
48
|
-
ActiveRecord::Base.logger = Logger.new(STDOUT) if
|
49
|
+
ActiveRecord::Base.establish_connection( options )
|
50
|
+
ActiveRecord::Base.logger = Logger.new(STDOUT) if options[:verbose]
|
49
51
|
#~ ActiveRecord.colorize_logging = @options[:verbose]
|
50
|
-
puts "using database #{ActiveRecord::Base.connection.raw_connection.db}" if
|
52
|
+
puts "using database #{ActiveRecord::Base.connection.raw_connection.db}" if options[:debug]
|
53
|
+
self
|
51
54
|
end
|
52
55
|
|
53
56
|
# convenience method so we can do things like
|
54
57
|
# Clevic::DbOptions.connect( $options ) do
|
55
58
|
# database :accounts
|
56
59
|
# adapter :postgresql
|
57
|
-
# username '
|
60
|
+
# username 'accounts_user'
|
58
61
|
# end
|
59
62
|
# the block is evaluated in the context of the a new DbOptions
|
60
63
|
# object.
|
61
|
-
def self.connect( args, &block )
|
64
|
+
def self.connect( args = nil, &block )
|
62
65
|
inst = self.new( args )
|
63
66
|
# using the Rails implementation, included in Qt
|
64
67
|
block.bind( inst )[*args]
|
@@ -70,7 +73,7 @@ class DbOptions
|
|
70
73
|
# variable
|
71
74
|
def method_missing(meth, *args, &block)
|
72
75
|
if @options.has_key? meth.to_sym
|
73
|
-
@options[meth.to_sym] = args[0].to_s
|
76
|
+
@options[meth.to_sym] = args[0].to_s if @options[meth.to_sym].empty?
|
74
77
|
else
|
75
78
|
super
|
76
79
|
end
|
data/lib/clevic/delegates.rb
CHANGED
@@ -219,7 +219,7 @@ class DistinctDelegate < ComboDelegate
|
|
219
219
|
@attribute = attribute
|
220
220
|
@options = options
|
221
221
|
# hackery for amateur query building in populate
|
222
|
-
@options[:conditions] ||= '
|
222
|
+
@options[:conditions] ||= '1=1'
|
223
223
|
super( parent )
|
224
224
|
end
|
225
225
|
|
@@ -259,10 +259,13 @@ class DistinctDelegate < ComboDelegate
|
|
259
259
|
# to be in the select list where distinct is involved
|
260
260
|
conn = @ar_model.connection
|
261
261
|
query =
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
262
|
+
case
|
263
|
+
when @options[:description]
|
264
|
+
query_order_description( conn, model_index )
|
265
|
+
when @options[:frequency]
|
266
|
+
query_order_frequency( conn, model_index )
|
267
|
+
else
|
268
|
+
query_order_frequency( conn, model_index )
|
266
269
|
end
|
267
270
|
rs = conn.execute( query )
|
268
271
|
rs.each do |row|
|
@@ -304,17 +307,21 @@ end
|
|
304
307
|
|
305
308
|
# Edit a relation from an id and display a list of relevant entries.
|
306
309
|
#
|
307
|
-
#
|
308
|
-
# model to the values displayed in the combo box.
|
310
|
+
# attribute is the method to call on the row entity to retrieve the related object.
|
309
311
|
#
|
310
312
|
# The ids of the ActiveRecord models are stored in the item data
|
311
313
|
# and the item text is fetched from them using attribute_path.
|
312
314
|
class RelationalDelegate < ComboDelegate
|
313
|
-
|
314
|
-
def initialize( parent,
|
315
|
-
@model_class = ( options[:class_name] ||
|
316
|
-
|
315
|
+
|
316
|
+
def initialize( parent, attribute, options )
|
317
|
+
@model_class = ( options[:class_name] || attribute.to_s.classify ).constantize
|
318
|
+
# TODO this doesn't seem to be used
|
319
|
+
@attribute = attribute.to_s
|
317
320
|
@options = options.clone
|
321
|
+
unless @options[:conditions].nil?
|
322
|
+
@options[:conditions].gsub!( /true/, @model_class.connection.quoted_true )
|
323
|
+
@options[:conditions].gsub!( /false/, @model_class.connection.quoted_false )
|
324
|
+
end
|
318
325
|
[ :class_name, :sample, :format ].each {|x| @options.delete x }
|
319
326
|
super( parent )
|
320
327
|
end
|
@@ -338,7 +345,7 @@ class RelationalDelegate < ComboDelegate
|
|
338
345
|
if item
|
339
346
|
item_index = editor.find_data( item.id.to_variant )
|
340
347
|
if item_index == -1
|
341
|
-
|
348
|
+
add_to_list( editor, model_index, item )
|
342
349
|
end
|
343
350
|
end
|
344
351
|
end
|
@@ -347,10 +354,14 @@ class RelationalDelegate < ComboDelegate
|
|
347
354
|
def populate( editor, model_index )
|
348
355
|
# add set of all possible related entities
|
349
356
|
@model_class.find( :all, collect_finder_options( @options ) ).each do |x|
|
350
|
-
|
357
|
+
add_to_list( editor, model_index, x )
|
351
358
|
end
|
352
359
|
end
|
353
360
|
|
361
|
+
def add_to_list( editor, model_index, item )
|
362
|
+
editor.add_item( model_index.field.transform_attribute( item ), item.id.to_variant )
|
363
|
+
end
|
364
|
+
|
354
365
|
# send data to the editor
|
355
366
|
def setEditorData( editor, model_index )
|
356
367
|
if is_combo?( editor )
|
data/lib/clevic/extensions.rb
CHANGED
@@ -48,8 +48,12 @@ module Qt
|
|
48
48
|
class ModelIndex
|
49
49
|
# the value to be displayed in the gui for this index
|
50
50
|
def gui_value
|
51
|
-
|
52
|
-
|
51
|
+
field.value_for( entity )
|
52
|
+
end
|
53
|
+
|
54
|
+
# return the Clevic::Field for this index
|
55
|
+
def field
|
56
|
+
@field ||= model.field_for_index( self )
|
53
57
|
end
|
54
58
|
|
55
59
|
# set the value returned from the gui, as whatever the underlying
|
data/lib/clevic/field.rb
CHANGED
@@ -8,13 +8,14 @@ This defines a field in the UI, and how it hooks up to a field in the DB.
|
|
8
8
|
class Field
|
9
9
|
include QtFlags
|
10
10
|
|
11
|
-
attr_accessor :attribute, :path, :label, :delegate, :class_name
|
12
|
-
|
11
|
+
attr_accessor :attribute, :path, :label, :delegate, :class_name
|
12
|
+
attr_accessor :alignment, :format, :tooltip, :path_block
|
13
|
+
attr_writer :sample, :read_only
|
13
14
|
|
14
15
|
# attribute is the symbol for the attribute on the model_class
|
15
16
|
def initialize( attribute, model_class, options )
|
16
17
|
# sanity checking
|
17
|
-
unless model_class.has_attribute?( attribute )
|
18
|
+
unless model_class.has_attribute?( attribute ) or model_class.instance_methods.include?( attribute.to_s )
|
18
19
|
msg = <<EOF
|
19
20
|
#{attribute} not found in #{model_class.name}. Possibilities are:
|
20
21
|
#{model_class.attribute_names.join("\n")}
|
@@ -27,7 +28,14 @@ EOF
|
|
27
28
|
@model_class = model_class
|
28
29
|
|
29
30
|
options.each do |key,value|
|
30
|
-
self.send( "#{key}=", value ) if respond_to?( key )
|
31
|
+
self.send( "#{key}=", value ) if respond_to?( "#{key}=" )
|
32
|
+
end
|
33
|
+
|
34
|
+
# TODO could convert everything to a block here, even paths
|
35
|
+
if options[:display].kind_of?( Proc )
|
36
|
+
@path_block = options[:display]
|
37
|
+
else
|
38
|
+
@path = options[:display]
|
31
39
|
end
|
32
40
|
|
33
41
|
# default the label
|
@@ -53,6 +61,31 @@ EOF
|
|
53
61
|
end
|
54
62
|
end
|
55
63
|
|
64
|
+
# Return the attribute value for the given entity, which may
|
65
|
+
# be an ActiveRecord instance
|
66
|
+
# entity is an ActiveRecord instance
|
67
|
+
def value_for( entity )
|
68
|
+
return nil if entity.nil?
|
69
|
+
transform_attribute( entity.send( attribute ) )
|
70
|
+
end
|
71
|
+
|
72
|
+
# apply path, or path_block, to the given
|
73
|
+
# attribute value. Otherwise just return
|
74
|
+
# attribute_value itself
|
75
|
+
def transform_attribute( attribute_value )
|
76
|
+
return nil if attribute_value.nil?
|
77
|
+
case
|
78
|
+
when !path_block.nil?
|
79
|
+
path_block.call( attribute_value )
|
80
|
+
|
81
|
+
when !path.nil?
|
82
|
+
attribute_value.evaluate_path( path.split( /\./ ) )
|
83
|
+
|
84
|
+
else
|
85
|
+
attribute_value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
56
89
|
# return true if it's a date, a time or a datetime
|
57
90
|
# cache result because the type won't change in the lifetime of the field
|
58
91
|
def is_date_time?
|
@@ -65,11 +98,19 @@ EOF
|
|
65
98
|
@model_class.columns_hash[attribute.to_s] || @model_class.reflections[attribute]
|
66
99
|
end
|
67
100
|
|
101
|
+
# return true if this field can be used in a filter
|
102
|
+
# virtual fields (ie those that don't exist in this field's
|
103
|
+
# table) can't be filtered on.
|
104
|
+
def filterable?
|
105
|
+
!meta.nil?
|
106
|
+
end
|
107
|
+
|
68
108
|
# return the name of the field for this Field, quoted for the dbms
|
69
109
|
def quoted_field
|
70
110
|
@model_class.connection.quote_column_name( meta.name )
|
71
111
|
end
|
72
112
|
|
113
|
+
# return the result of the attribute + the path
|
73
114
|
def column
|
74
115
|
[attribute.to_s, path].compact.join('.')
|
75
116
|
end
|
@@ -81,6 +122,11 @@ EOF
|
|
81
122
|
pieces.map{|x| x.to_sym}
|
82
123
|
end
|
83
124
|
|
125
|
+
# is the field read-only. Defaults to false.
|
126
|
+
def read_only?
|
127
|
+
@read_only || false
|
128
|
+
end
|
129
|
+
|
84
130
|
# format this value. Use strftime for date_time types, or % for everything else
|
85
131
|
def do_format( value )
|
86
132
|
if self.format != nil
|
@@ -103,7 +149,7 @@ EOF
|
|
103
149
|
when :string, :text
|
104
150
|
string_sample( 'n'*40 )
|
105
151
|
|
106
|
-
when :date, :time, :datetime
|
152
|
+
when :date, :time, :datetime, :timestamp
|
107
153
|
date_time_sample
|
108
154
|
|
109
155
|
when :numeric, :decimal, :integer, :float
|
@@ -112,8 +158,11 @@ EOF
|
|
112
158
|
# TODO return a width, or something like that
|
113
159
|
when :boolean; 'W'
|
114
160
|
|
161
|
+
when ActiveRecord::Reflection::AssociationReflection
|
162
|
+
#TODO width for relations
|
163
|
+
|
115
164
|
else
|
116
|
-
puts "#{@model_class.name}.#{attribute} is a #{meta.type}"
|
165
|
+
puts "#{@model_class.name}.#{attribute} is a #{meta.type.inspect}"
|
117
166
|
end
|
118
167
|
|
119
168
|
if $options[:debug]
|