arspy 0.0.3 → 0.0.4
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/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
|
|