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