arspy 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +205 -0
- data/lib/arspy/operators.rb +34 -34
- metadata +2 -2
data/README.rdoc
CHANGED
@@ -0,0 +1,205 @@
|
|
1
|
+
= ARSpy
|
2
|
+
|
3
|
+
A Rails console command line tool for exploring, browsing and inspecting
|
4
|
+
the structure, associations and data of ActiveRecord data models.
|
5
|
+
|
6
|
+
== Resources
|
7
|
+
|
8
|
+
Install
|
9
|
+
|
10
|
+
* sudo gem install arspy
|
11
|
+
|
12
|
+
Use
|
13
|
+
|
14
|
+
* require 'arspy'
|
15
|
+
|
16
|
+
|
17
|
+
== Description
|
18
|
+
|
19
|
+
ARSpy provides a number of functions for inspecting a Rails application's
|
20
|
+
ActiveRecord model. To use the gem all you need to to is the following:
|
21
|
+
|
22
|
+
sudo gem install arspy
|
23
|
+
ruby script/console
|
24
|
+
require 'arspy'
|
25
|
+
|
26
|
+
With ARSpy, you can view associations, fields and data related to tables and relations
|
27
|
+
in an easy-to-read format.
|
28
|
+
|
29
|
+
== Inspecting ActiveRecord Associations and Attributes (Fields)
|
30
|
+
|
31
|
+
=== 'Listing fields' with the 'lf' command
|
32
|
+
|
33
|
+
Append the 'lf' command to an ActiveRecord object, class, or association method to
|
34
|
+
print the name, type and database variable type for each attribute.
|
35
|
+
|
36
|
+
User.lf
|
37
|
+
first_name :string (varchar(50))
|
38
|
+
last_name :string (varchar(50))
|
39
|
+
active :boolean (boolean)
|
40
|
+
age :age (int(11))
|
41
|
+
|
42
|
+
|
43
|
+
=== 'Listing associations' with the 'la' command
|
44
|
+
|
45
|
+
Append the 'la' command to an ActiveRecord object, class or association method to
|
46
|
+
print the name, type, class and configuration information for each association.
|
47
|
+
|
48
|
+
User.la
|
49
|
+
blogs has_many (Blog)
|
50
|
+
comments has_many (Comment)
|
51
|
+
friendships has_many (Friendship)
|
52
|
+
friends has_many (User) {:through=>:friendships}
|
53
|
+
assets has_many (Asset) {:as=>:asset}
|
54
|
+
|
55
|
+
=== Applying 'lf' and 'la' commands
|
56
|
+
|
57
|
+
The 'la' and 'lf' command can be used on any ActiveRecord object, class, association
|
58
|
+
or array of these.
|
59
|
+
|
60
|
+
u = User.find_by_last_name('Smith')
|
61
|
+
u.la
|
62
|
+
u.blogs.lf
|
63
|
+
u.blogs.title
|
64
|
+
u.blogs.la
|
65
|
+
u.blogs.comments.la
|
66
|
+
u.blogs.comments.user.lf
|
67
|
+
u.blogs.comments.user.pr(:first_name, :last_name) #see 'pr' command below
|
68
|
+
|
69
|
+
|
70
|
+
== Printing in Columns
|
71
|
+
|
72
|
+
=== Printing arrays with the 'pr' command
|
73
|
+
|
74
|
+
Array data in the console is not so easy to read.
|
75
|
+
|
76
|
+
names
|
77
|
+
["Peter Smith", "Sarah Johnson", "Randy Wade", "Alex Parsons", "Beth Silverton",
|
78
|
+
"Jenny Westmeyers", "Benjamin Grant", "Maria Stone"]
|
79
|
+
|
80
|
+
Use the 'pr' command to print in a column.
|
81
|
+
|
82
|
+
names.pr
|
83
|
+
Peter Smith
|
84
|
+
Sara Johnson
|
85
|
+
...
|
86
|
+
|
87
|
+
=== Print columns of object attributes with the 'pr' command
|
88
|
+
|
89
|
+
Arrays of ActiveRecord objects are even more difficult to read and analyze. Use
|
90
|
+
'pr' command to print attributes (fields) in columns.
|
91
|
+
|
92
|
+
User.find(2).friends.pr :first_name, :last_name
|
93
|
+
Randy Wade
|
94
|
+
Benjamin Grant
|
95
|
+
Jenny Westmeyers
|
96
|
+
Maria Stone
|
97
|
+
|
98
|
+
=== Printing expressions with 'pr' command
|
99
|
+
|
100
|
+
Pass an expression as one of the columns to the 'pr' command.
|
101
|
+
|
102
|
+
User.find(2).friends.pr '"#{last_name}, #{first_name}"', :age
|
103
|
+
Wade, Randy 25
|
104
|
+
Grant, Benjamin 28
|
105
|
+
Westmeyers, Jenny 24
|
106
|
+
Stone, Maria 33
|
107
|
+
|
108
|
+
Expressions using associations
|
109
|
+
|
110
|
+
User.find(2).friends.pr :first_name, :last_name, 'comments.count', 'blogs.count'
|
111
|
+
Randy Wade 23 3
|
112
|
+
Benjamin Grant 98 20
|
113
|
+
Jenny Westmeyers 213 2
|
114
|
+
Maria Stone 8 88
|
115
|
+
|
116
|
+
|
117
|
+
== Iterating Associations and Data
|
118
|
+
|
119
|
+
=== Chaining associations to get data sets
|
120
|
+
|
121
|
+
Chain associations to get an array of the results. For example, the following will
|
122
|
+
collect all of the comment objects for all blogs written by User with ID=2.
|
123
|
+
|
124
|
+
User.find(2).blogs.comments
|
125
|
+
[#<Comment id: 21, user_id: 8, blog_id: 85, rating: 5, ...
|
126
|
+
|
127
|
+
Clean this up by further chainging to return an array of the 'comment' field
|
128
|
+
of each comment object and use 'pr' to print in a column.
|
129
|
+
|
130
|
+
User.find(2).blogs.comments.comment.pr
|
131
|
+
I agree with the blogger
|
132
|
+
I'm new to ActiveRecord. Is there anyway to...
|
133
|
+
How do you use polymorphic associations to...
|
134
|
+
...
|
135
|
+
|
136
|
+
Note that chaining is accumulative. To limit the results set, use the 'wi' or 'wo'
|
137
|
+
commands.
|
138
|
+
|
139
|
+
== Limiting data sets
|
140
|
+
|
141
|
+
=== Limiting with the 'with' command
|
142
|
+
|
143
|
+
The 'with' command is abbreviated to 'wi' in an expression. It's like saying only show
|
144
|
+
me the results with this condition OR this condition, etc.
|
145
|
+
|
146
|
+
Say we want to limit comments in the above example to only those written by Randy Wade
|
147
|
+
and Maria Stone. If their user ids are [5, 9], then we can write
|
148
|
+
|
149
|
+
User.find(2).blogs.comments.wi(:user_id=>[5, 9]).comments.pr
|
150
|
+
|
151
|
+
We could use ARSpy to look up the user ids.
|
152
|
+
|
153
|
+
User.find(2).blogs.comments.user.pr :first_name, :last_name, :id
|
154
|
+
|
155
|
+
Note in this example, the 'wi' command is acting on attributes of the object immediately
|
156
|
+
preceding it, namely, the Comment object.
|
157
|
+
|
158
|
+
=== ORing and ANDing with the 'wi' command
|
159
|
+
|
160
|
+
The 'wi' command can take an unlimited number of parameters. Multiple parameters
|
161
|
+
works as an OR operation on the results. So the above example could have been written
|
162
|
+
|
163
|
+
User.find(2).blogs.comments.wi(:user_id=>5, :user_id=>9).comments.pr
|
164
|
+
|
165
|
+
with the same results.
|
166
|
+
|
167
|
+
To get an AND operation, chain the 'wi' command.
|
168
|
+
|
169
|
+
User.find(2).blogs.comments.wi(:blog_id=>6).wi(:user_id=>5).comments.pr
|
170
|
+
|
171
|
+
This example returns only comments for blog with ID 6 and user associated with
|
172
|
+
the comment with ID 5. On the other hand,
|
173
|
+
|
174
|
+
User.find(2).blogs.comments.wi(:blog_id=>6, :user_id=>5).comments.pr
|
175
|
+
|
176
|
+
returns all comments associated with blog id 6 OR user id 5.
|
177
|
+
|
178
|
+
=== Parameters of the 'wi' command
|
179
|
+
|
180
|
+
The 'wi' command can take integers, strings and a hash of conditions.
|
181
|
+
|
182
|
+
Strings are expressions evaluated against the preceding object.
|
183
|
+
|
184
|
+
User.find(2).blogs.comments.wi('user.last_name.include?("Grant")').comment.pr
|
185
|
+
|
186
|
+
Integers are IDs for the object.
|
187
|
+
|
188
|
+
User.find(2).blogs.comments.wi(20,21,22).comment.pr
|
189
|
+
|
190
|
+
A hash of {attribute=>[values]}. The following displays comments belonging
|
191
|
+
to blogs with ids 6, 7 or 8.
|
192
|
+
|
193
|
+
User.find(2).blogs.comments.wi(:blog_id=>[6,7,8]).comments.pr
|
194
|
+
|
195
|
+
|
196
|
+
=== Excluding with the 'wo' (without) command
|
197
|
+
|
198
|
+
The 'wo' command does the exact opposite of the 'wi' command, showing only those
|
199
|
+
results that do not meet the conditions passed in the parameters.
|
200
|
+
|
201
|
+
|
202
|
+
== Dependencies
|
203
|
+
|
204
|
+
* ActiveRecord
|
205
|
+
* ActiveSupport
|
data/lib/arspy/operators.rb
CHANGED
@@ -2,31 +2,31 @@ module Arspy
|
|
2
2
|
module Operators
|
3
3
|
def self.list_associations(active_record_klass)
|
4
4
|
counts = {}
|
5
|
-
rows = active_record_klass.reflect_on_all_associations.map do |
|
6
|
-
counts[
|
7
|
-
counts[
|
8
|
-
self.format_column_association(
|
5
|
+
rows = active_record_klass.reflect_on_all_associations.map do |assoc|
|
6
|
+
counts[assoc.macro] ||= 0
|
7
|
+
counts[assoc.macro] += 1
|
8
|
+
self.format_column_association(assoc)
|
9
9
|
end
|
10
|
-
rows.sort!{|
|
10
|
+
rows.sort!{|row1,row2| row1.first <=> row2.first}
|
11
11
|
self.print_matrix(rows)
|
12
|
-
"Total: #{counts.inject(0){|sum,
|
12
|
+
"Total: #{counts.inject(0){|sum, count| sum+count.last}} (" + counts.map{|count| "#{count.last} #{count.first}" }.join(', ') + ")"
|
13
13
|
end
|
14
14
|
|
15
15
|
def self.list_fields(active_record_klass)
|
16
|
-
rows = active_record_klass.columns.map do |
|
17
|
-
self.format_column_field(
|
16
|
+
rows = active_record_klass.columns.map do |column|
|
17
|
+
self.format_column_field(column)
|
18
18
|
end
|
19
|
-
rows.sort!{|
|
19
|
+
rows.sort!{|row1,row2| row1.first <=> row2.first}
|
20
20
|
self.print_matrix(rows)
|
21
21
|
"Total #{active_record_klass.columns.size} field#{active_record_klass.columns.size == 1 ? '' : 's'}"
|
22
22
|
end
|
23
23
|
|
24
|
-
def self.format_column_association(
|
25
|
-
select_options =
|
26
|
-
[
|
24
|
+
def self.format_column_association(assoc)
|
25
|
+
select_options = assoc.options.select{|k,v| [:through, :as, :polymorphic].include?(k)}
|
26
|
+
[assoc.name.to_s, assoc.macro.to_s, "(#{assoc.options[:class_name] || assoc.name.to_s.singularize.camelize})", select_options.empty? ? '' : Hash[*select_options.flatten].inspect]
|
27
27
|
end
|
28
|
-
def self.format_column_field(
|
29
|
-
[
|
28
|
+
def self.format_column_field(field)
|
29
|
+
[field.name.to_s, ":#{field.type}", "(#{field.sql_type})"]
|
30
30
|
end
|
31
31
|
|
32
32
|
def self.print_array(array, *args)
|
@@ -46,7 +46,7 @@ module Arspy
|
|
46
46
|
end
|
47
47
|
|
48
48
|
def self.print_object(object, *args)
|
49
|
-
print_matrix([args.map{|
|
49
|
+
print_matrix([args.map{|arg| object[arg]}]) if args
|
50
50
|
puts(object.inspect) unless args
|
51
51
|
nil
|
52
52
|
end
|
@@ -56,7 +56,7 @@ module Arspy
|
|
56
56
|
when String then obj.instance_eval(arg) rescue false
|
57
57
|
when Integer then obj.id == arg
|
58
58
|
when Hash
|
59
|
-
arg.any?{|
|
59
|
+
arg.any?{|key,val| self.test_attribute(obj, key, (val.is_a?(Array) ? val : [val]) ) }
|
60
60
|
else
|
61
61
|
false
|
62
62
|
end
|
@@ -64,11 +64,11 @@ module Arspy
|
|
64
64
|
end
|
65
65
|
def self.with(array, *args)
|
66
66
|
return array if (args.empty? || array.nil? || array.empty?)
|
67
|
-
array.select{|
|
67
|
+
array.select{|obj| obj && self.test_object(obj, args)}
|
68
68
|
end
|
69
69
|
def self.without(array, *args)
|
70
70
|
return array if (args.empty? || array.nil? || array.empty?)
|
71
|
-
array.select{|
|
71
|
+
array.select{|obj| obj && !self.test_object(obj, args)}
|
72
72
|
end
|
73
73
|
def self.enable_abbreviations; @@abbreviations_enabled = true; end
|
74
74
|
def self.disable_abbreviations; @@abbreviations_enabled = false; end
|
@@ -110,35 +110,35 @@ module Arspy
|
|
110
110
|
attrib_descriptors = attributes.map{|method_name| {:method_name=>method_name, :type=>:attribute, :abbr=>abbreviate_method_name(method_name)}}
|
111
111
|
all_descriptors = assoc_descriptors + attrib_descriptors
|
112
112
|
object.class.instance_variable_set('@arspy_ambiguous_abbreviations', remove_ambiguities(all_descriptors))
|
113
|
-
object.class.instance_variable_set('@arspy_abbreviations', Hash[*all_descriptors.map{|
|
113
|
+
object.class.instance_variable_set('@arspy_abbreviations', Hash[*all_descriptors.map{|desc| [desc[:abbr], desc] }.flatten])
|
114
114
|
end
|
115
115
|
def self.remove_ambiguities(descriptors)
|
116
116
|
list={}
|
117
117
|
ambiguities = {}
|
118
|
-
descriptors.each do |
|
119
|
-
if list.include?(
|
120
|
-
if ambiguities[
|
121
|
-
ambiguities[
|
118
|
+
descriptors.each do |desc|
|
119
|
+
if list.include?(desc[:abbr])
|
120
|
+
if ambiguities[desc[:abbr]]
|
121
|
+
ambiguities[desc[:abbr]][:methods] << desc[:method_name]
|
122
122
|
else
|
123
|
-
ambiguities[
|
124
|
-
ambiguities[
|
123
|
+
ambiguities[desc[:abbr]] = {:abbr=>desc[:abbr], :methods=>[desc[:method_name]]}
|
124
|
+
ambiguities[desc[:abbr]][:methods] << list[desc[:abbr]][:method_name]
|
125
125
|
end
|
126
126
|
else
|
127
|
-
list[
|
127
|
+
list[desc[:abbr]] = desc
|
128
128
|
end
|
129
129
|
end
|
130
|
-
descriptors.reject!{|
|
130
|
+
descriptors.reject!{|desc| ambiguities.map{|h| h.first}.include?(desc[:abbr])}
|
131
131
|
ambiguities
|
132
132
|
end
|
133
133
|
def self.abbreviate_method_name(method_name)
|
134
|
-
|
134
|
+
words = method_name.to_s.split('_')
|
135
135
|
abbr=[]
|
136
|
-
if
|
136
|
+
if words.first == ''
|
137
137
|
abbr << '_'
|
138
138
|
end
|
139
|
-
|
140
|
-
abbr +=
|
141
|
-
chars =
|
139
|
+
words.reject!{|word| word == ''}
|
140
|
+
abbr += words.map do |word|
|
141
|
+
chars = word.split(//)
|
142
142
|
first = chars.shift
|
143
143
|
[first, chars.map{|ch| ch =~ /[0-9]/ ? ch : nil}].compact.flatten.join('')
|
144
144
|
end
|
@@ -156,8 +156,8 @@ module Arspy
|
|
156
156
|
end
|
157
157
|
def self.interpret_attribute_or_method(array, method_name, *args)
|
158
158
|
return array.map(&method_name) if args.empty?
|
159
|
-
raise 'Hash not allowed as attribute conditionals' if args.any?{|
|
160
|
-
array.select{|
|
159
|
+
raise 'Hash not allowed as attribute conditionals' if args.any?{|arg| arg.is_a?(Hash)}
|
160
|
+
array.select{|obj| obj && self.test_attribute(obj, method_name, args)}
|
161
161
|
end
|
162
162
|
public
|
163
163
|
|