arspy 0.0.5 → 0.0.7

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.
Files changed (41) hide show
  1. data/README.rdoc +10 -0
  2. data/Rakefile +1 -1
  3. data/lib/arspy/class_extensions.rb +2 -2
  4. data/lib/arspy/delegators/active_record_extensions.rb +2 -2
  5. data/lib/arspy/delegators/array_extensions.rb +4 -4
  6. data/lib/arspy/delegators/association_collection_extensions.rb +4 -4
  7. data/lib/arspy/delegators/null_extensions.rb +2 -2
  8. data/lib/arspy/operators/abbreviations.rb +68 -0
  9. data/lib/arspy/operators/attribute_test/base.rb +11 -0
  10. data/lib/arspy/operators/attribute_test/float_test.rb +24 -0
  11. data/lib/arspy/operators/attribute_test/integer_test.rb +24 -0
  12. data/lib/arspy/operators/attribute_test/range_test.rb +24 -0
  13. data/lib/arspy/operators/attribute_test/regexp_test.rb +23 -0
  14. data/lib/arspy/operators/attribute_test/string_test.rb +23 -0
  15. data/lib/arspy/operators/attribute_test/unsupported_test.rb +17 -0
  16. data/lib/arspy/operators/attribute_test.rb +20 -0
  17. data/lib/arspy/operators/interpreter/abbreviated_association_interpreter.rb +22 -0
  18. data/lib/arspy/operators/interpreter/abbreviated_attribute_interpreter.rb +22 -0
  19. data/lib/arspy/operators/interpreter/association_interpreter.rb +18 -0
  20. data/lib/arspy/operators/interpreter/attribute_interpreter.rb +21 -0
  21. data/lib/arspy/operators/interpreter/base.rb +11 -0
  22. data/lib/arspy/operators/interpreter/method_interpreter.rb +14 -0
  23. data/lib/arspy/operators/interpreter/null_interpreter.rb +12 -0
  24. data/lib/arspy/operators/interpreter.rb +29 -0
  25. data/lib/arspy/operators/selector/attribute_selector.rb +33 -0
  26. data/lib/arspy/operators/selector/base.rb +12 -0
  27. data/lib/arspy/operators/selector/hash_selector.rb +30 -0
  28. data/lib/arspy/operators/selector/integer_selector.rb +17 -0
  29. data/lib/arspy/operators/selector/range_selector.rb +17 -0
  30. data/lib/arspy/operators/selector/string_selector.rb +14 -0
  31. data/lib/arspy/operators/selector/unsupported_selector.rb +17 -0
  32. data/lib/arspy/operators/selector.rb +28 -0
  33. data/lib/arspy/operators.rb +40 -111
  34. data/lib/meta_programming/object.rb +2 -2
  35. data/spec/active_record.log +4327 -0
  36. data/spec/database.rb +85 -32
  37. data/spec/list_association_spec.rb +62 -0
  38. data/spec/list_field_spec.rb +5 -0
  39. data/spec/spec_helper.rb +2 -0
  40. data/spec/with_command_spec.rb +106 -0
  41. metadata +39 -7
data/README.rdoc CHANGED
@@ -113,6 +113,15 @@ Expressions using associations
113
113
  Jenny Westmeyers 213 2
114
114
  Maria Stone 8 88
115
115
 
116
+ === Printing objects and classes
117
+
118
+ ARSpy integrates 'awesome print' to print lists of objects and classes. Print objects or
119
+ classes with the 'pr' command, or explicitly print with the 'ap' command.
120
+
121
+ User.ap # => prints the object attributes and columns with awesome print
122
+ User.find(2).blogs.title.pr #=> prints the titles of all blogs
123
+ User.find(2).blogs.pr # => prints the object fields and data usign awesome print
124
+
116
125
 
117
126
  == Iterating Associations and Data
118
127
 
@@ -203,3 +212,4 @@ results that do not meet the conditions passed in the parameters.
203
212
 
204
213
  * ActiveRecord
205
214
  * ActiveSupport
215
+ * Awesome Print
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ spec = Gem::Specification.new do |s|
12
12
  s.description = 'Active Record Spy'
13
13
  s.summary = 'Rails console command line tool for browsing and inspecting the structure, associations and data of an ActiveRecord data model.'
14
14
 
15
- s.add_dependency('awesome_print', '>= 0.1.1')
15
+ s.add_dependency('awesome_print', '>= 0.1.4')
16
16
 
17
17
  exclude_folders = '' # 'spec/rails/{doc,lib,log,nbproject,tmp,vendor,test}'
18
18
  exclude_files = [] # FileList['**/*.log'] + FileList[exclude_folders+'/**/*'] + FileList[exclude_folders]
@@ -2,8 +2,8 @@ module Arspy
2
2
  module ClassExtensions
3
3
  module ActiveRecord
4
4
  module Base
5
- def la; Arspy::Operators.list_associations(self); end
6
- def lf; Arspy::Operators.list_fields(self); end
5
+ def la(*args); Arspy::Operators.list_associations(self, *args); end
6
+ def lf(*args); Arspy::Operators.list_fields(self, *args); end
7
7
  def pr; Arspy::Operators.awesome_print(self); end
8
8
  def ap(opts={}); Arspy::Operators.awesome_print(self, opts); end
9
9
  end
@@ -1,8 +1,8 @@
1
1
  module Arspy
2
2
  module Delegators
3
3
  module ActiveRecordExtensions
4
- def la; Arspy::Operators.list_associations(self.class); end
5
- def lf; Arspy::Operators.list_fields(self.class); end
4
+ def la(*args); Arspy::Operators.list_associations(self.class, *args); end
5
+ def lf(*args); Arspy::Operators.list_fields(self.class, *args); end
6
6
  def pr(*args); Arspy::Operators.print_object(self, *args); end
7
7
  def ap(opts={}); Arspy::Operators.awesome_print(self, opts); end
8
8
  end
@@ -13,11 +13,11 @@ module Arspy
13
13
  end
14
14
  end
15
15
 
16
- def la
17
- Arspy::Operators.list_associations(self.first) unless self.emtpy?
16
+ def la(*args)
17
+ Arspy::Operators.list_associations(self.first, *args) unless self.emtpy?
18
18
  end
19
- def lf
20
- Arspy::Operators.list_fields(self.first) unless self.empty?
19
+ def lf(*args)
20
+ Arspy::Operators.list_fields(self.first, *args) unless self.empty?
21
21
  end
22
22
  def ap(opts={})
23
23
  Arspy::Operators.awesome_print(self, opts={}) unless self.empty?
@@ -9,13 +9,13 @@ module Arspy
9
9
  result
10
10
  end
11
11
  end
12
- def la
12
+ def la(*args)
13
13
  load_target unless loaded?
14
- Arspy::Operators.list_associations(@target.first) unless @target.emtpy?
14
+ Arspy::Operators.list_associations(@target.first, *args) unless @target.emtpy?
15
15
  end
16
- def lf
16
+ def lf(*args)
17
17
  load_target unless loaded?
18
- Arspy::Operators.list_fields(@target.first) unless @target.empty?
18
+ Arspy::Operators.list_fields(@target.first, *args) unless @target.empty?
19
19
  end
20
20
  def pr(*args)
21
21
  load_target unless loaded?
@@ -1,8 +1,8 @@
1
1
  module Arspy
2
2
  module Delegators
3
3
  module NullExtensions
4
- def la; end
5
- def lf; end
4
+ def la(*args); end
5
+ def lf(*args); end
6
6
  def pr(*args); end
7
7
  def ap(opts={}); end
8
8
  end
@@ -0,0 +1,68 @@
1
+ module Abbreviations
2
+ def self.included(base)
3
+ base.extend(ClassMethods)
4
+ end
5
+
6
+ def resolve_abbreviation_for_attributes_and_associations!(object, method_name)
7
+ self.class.resolve_abbreviation_for_attributes_and_associations!(object, method_name)
8
+ end
9
+ module ClassMethods
10
+ def resolve_abbreviation_for_attributes_and_associations!(object, method_name)
11
+ klass = object.class
12
+ setup_abbreviations(object) unless object.instance_variable_defined?('@arspy_abbreviations')
13
+ if (ambiguity = klass.instance_variable_get('@arspy_ambiguous_abbreviations')[method_name])
14
+ raise "Ambiguous abbreviation '#{ambiguity[:abbr]}' could be #{quote_and_join(ambiguity[:methods])}"
15
+ end
16
+ klass.instance_variable_get('@arspy_abbreviations')[method_name]
17
+ end
18
+
19
+ def setup_abbreviations(object)
20
+ associations = object.class.reflect_on_all_associations.map(&:name).map(&:to_sym)
21
+ attributes = object.attribute_names.map(&:to_sym)
22
+ assoc_descriptors = associations.map{|method_name| {:method_name=>method_name, :type=>:association, :abbr=>abbreviate_method_name(method_name)}}
23
+ attrib_descriptors = attributes.map{|method_name| {:method_name=>method_name, :type=>:attribute, :abbr=>abbreviate_method_name(method_name)}}
24
+ all_descriptors = assoc_descriptors + attrib_descriptors
25
+ object.class.instance_variable_set('@arspy_ambiguous_abbreviations', remove_ambiguities(all_descriptors))
26
+ object.class.instance_variable_set('@arspy_abbreviations', Hash[*all_descriptors.map{|desc| [desc[:abbr], desc] }.flatten])
27
+ end
28
+ def remove_ambiguities(descriptors)
29
+ list={}
30
+ ambiguities = {}
31
+ descriptors.each do |desc|
32
+ if list.include?(desc[:abbr])
33
+ if ambiguities[desc[:abbr]]
34
+ ambiguities[desc[:abbr]][:methods] << desc[:method_name]
35
+ else
36
+ ambiguities[desc[:abbr]] = {:abbr=>desc[:abbr], :methods=>[desc[:method_name]]}
37
+ ambiguities[desc[:abbr]][:methods] << list[desc[:abbr]][:method_name]
38
+ end
39
+ else
40
+ list[desc[:abbr]] = desc
41
+ end
42
+ end
43
+ descriptors.reject!{|desc| ambiguities.map{|hash| hash.first}.include?(desc[:abbr])}
44
+ ambiguities
45
+ end
46
+ def abbreviate_method_name(method_name)
47
+ words = method_name.to_s.split('_')
48
+ abbr=[]
49
+ if words.first == ''
50
+ abbr << '_'
51
+ end
52
+ words.reject!{|word| word == ''}
53
+ abbr += words.map do |word|
54
+ chars = word.split(//)
55
+ first = chars.shift
56
+ [first, chars.map{|ch| ch =~ /[0-9]/ ? ch : nil}].compact.flatten.join('')
57
+ end
58
+
59
+ abbr << '_' if (method_name.to_s =~ /_$/)
60
+ abbr.join('').to_sym
61
+ end
62
+ def quote_and_join(array)
63
+ return "'#{array.first}'" if array.size == 1
64
+ last = array.pop
65
+ "'#{array.join("', '")}' or '#{last}'"
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,11 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+ class Base
5
+ def initialize(param)
6
+ @param = param
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,24 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+
5
+
6
+ class FloatTest < Base
7
+
8
+ def self.applies?(param)
9
+ param.is_a? Float
10
+ end
11
+
12
+ def match?(value_or_object)
13
+ case value_or_object
14
+ when ActiveRecord::Base
15
+ false
16
+ else
17
+ value_or_object.to_f == @param rescue nil
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+
5
+
6
+ class IntegerTest < Base
7
+
8
+ def self.applies?(param)
9
+ param.is_a? Integer
10
+ end
11
+
12
+ def match?(value_or_object)
13
+ case value_or_object
14
+ when ActiveRecord::Base
15
+ value_or_object.id == @param
16
+ else
17
+ value_or_object.to_i == @param rescue nil
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,24 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+
5
+
6
+ class RangeTest < Base
7
+
8
+ def self.applies?(param)
9
+ param.is_a? Range
10
+ end
11
+
12
+ def match?(value_or_object)
13
+ case value_or_object
14
+ when ActiveRecord::Base
15
+ @param.include?(value_or_object.id)
16
+ else
17
+ @param.include?(value_or_object.to_i)
18
+ end
19
+ end
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,23 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+
5
+ class RegexpTest < Base
6
+ def self.applies?(param)
7
+ param.is_a? Regexp
8
+ end
9
+
10
+ def match?(value_or_object)
11
+ case value_or_object
12
+ when ActiveRecord::Base
13
+ raise "Test for '#{@param}' on object '#{value_or_object.class.name}' not supported."
14
+ else
15
+ value_or_object =~ @param
16
+ end
17
+ end
18
+
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+
5
+ class StringTest < Base
6
+
7
+ def self.applies?(param)
8
+ param.is_a? String
9
+ end
10
+
11
+ def match?(value_or_object)
12
+ case value_or_object
13
+ when ActiveRecord::Base
14
+ value_or_object.instance_eval{ value_or_object }
15
+ else
16
+ value_or_object.to_s == @param rescue nil
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ module Arspy
2
+ module Operators
3
+ module AttributeTest
4
+ class UnsupportedTest < Base
5
+
6
+ def self.applies?(param)
7
+ true
8
+ end
9
+
10
+ def match?(object)
11
+ raise "#{@param.class.name} '#{@param.inspect}' not supported."
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ require 'arspy/operators/attribute_test/base'
2
+ require 'arspy/operators/attribute_test/integer_test'
3
+ require 'arspy/operators/attribute_test/range_test'
4
+ require 'arspy/operators/attribute_test/regexp_test'
5
+ require 'arspy/operators/attribute_test/string_test'
6
+ require 'arspy/operators/attribute_test/unsupported_test'
7
+
8
+ module Arspy
9
+ module Operators
10
+ module AttributeTest
11
+ @@attribute_test_classes = [
12
+ IntegerTest, RangeTest, StringTest, RegexpTest, UnsupportedTest
13
+ ]
14
+
15
+ def self.for(param)
16
+ @@attribute_test_classes.detect{|klass| klass.applies?(param)}.new(param)
17
+ end
18
+
19
+ end end
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'arspy/operators/abbreviations'
2
+ module Arspy
3
+ module Operators
4
+ module Interpreter
5
+ class AbbreviatedAssociationInterpreter < AssociationInterpreter
6
+ include Abbreviations
7
+
8
+ def self.applies?(array, method_name)
9
+ descriptor = resolve_abbreviation_for_attributes_and_associations!(array.first, method_name)
10
+ descriptor && descriptor[:type] == :association
11
+ end
12
+
13
+ def initialize(array, method_name)
14
+ descriptor = resolve_abbreviation_for_attributes_and_associations!(array.first, method_name)
15
+ super(array, descriptor[:method_name])
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,22 @@
1
+ require 'arspy/operators/abbreviations'
2
+
3
+ module Arspy
4
+ module Operators
5
+ module Interpreter
6
+ class AbbreviatedAttributeInterpreter < AttributeInterpreter
7
+ include Abbreviations
8
+
9
+ def self.applies?(array, method_name)
10
+ descriptor = resolve_abbreviation_for_attributes_and_associations!(array.first, method_name)
11
+ descriptor && descriptor[:type] == :attribute
12
+ end
13
+
14
+ def initialize(array, method_name)
15
+ descriptor = resolve_abbreviation_for_attributes_and_associations!(array.first, method_name)
16
+ super(array, descriptor[:method_name])
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module Arspy
2
+ module Operators
3
+ module Interpreter
4
+
5
+ class AssociationInterpreter < Base
6
+
7
+ def self.applies?(array, method_name)
8
+ array.first.class.reflect_on_all_associations.detect{|a| a.name == method_name}
9
+ end
10
+
11
+ def interpret(*args)
12
+ @array.map(&@method_name).flatten
13
+ end
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,21 @@
1
+ module Arspy
2
+ module Operators
3
+ module Interpreter
4
+ class AttributeInterpreter < Base
5
+
6
+ def self.applies?(array, method_name)
7
+ array.first.attribute_names.include?(method_name.to_s)
8
+ end
9
+
10
+ def interpret(*args)
11
+ return @array.map(&@method_name) if args.empty?
12
+ raise 'Hash not supported as attribute conditionals' if args.any?{|arg| arg.is_a?(Hash)}
13
+ selector = Selector.for({@method_name => args})
14
+ @array.select{|obj| obj && selector.select?(obj)}
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ module Arspy
2
+ module Operators
3
+ module Interpreter
4
+ class Base
5
+ def initialize(array, method_name)
6
+ @array, @method_name = array, method_name
7
+ end
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,14 @@
1
+ module Arspy
2
+ module Operators
3
+ module Interpreter
4
+
5
+ class MethodInterpreter < AttributeInterpreter
6
+ def self.applies?(array, method_name)
7
+ array.first.respond_to?(method_name) && array.first.method(method_name).arity == 0
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,12 @@
1
+ module Arspy
2
+ module Operators
3
+ module Interpreter
4
+ class NullInterpreter < Base
5
+ def self.applies?(array, method_name)
6
+ true
7
+ end
8
+ def interpret(*args); nil; end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+
2
+ require 'arspy/operators/interpreter/base'
3
+ require 'arspy/operators/interpreter/association_interpreter'
4
+ require 'arspy/operators/interpreter/attribute_interpreter'
5
+ require 'arspy/operators/interpreter/method_interpreter'
6
+ require 'arspy/operators/interpreter/abbreviated_association_interpreter'
7
+ require 'arspy/operators/interpreter/abbreviated_attribute_interpreter'
8
+ require 'arspy/operators/interpreter/null_interpreter'
9
+
10
+ module Arspy
11
+ module Operators
12
+ module Interpreter
13
+ @@interpreter_classes = [
14
+ AssociationInterpreter, AttributeInterpreter,
15
+ MethodInterpreter, AbbreviatedAssociationInterpreter,
16
+ AbbreviatedAttributeInterpreter,
17
+ NullInterpreter
18
+ ]
19
+
20
+ def self.for(array, method_name)
21
+ return NullInterpreter.new(array, method_name) unless (array && method_name && array.is_a?(Array) && !array.empty? && array.first.is_a?(ActiveRecord::Base))
22
+ @@interpreter_classes.detect{|klass| klass.applies?(array, method_name)}.new(array, method_name)
23
+ end
24
+
25
+ @@abbreviations_enabled = true
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+
5
+ class AttributeSelector < Base
6
+ def self.applies?(key_value)
7
+ key_value.first.is_a? Symbol
8
+ end
9
+ def initialize(key_value)
10
+ super
11
+ @attribute = key_value.first.to_sym
12
+ @test_values = key_value.last.is_a?(Array) ? key_value.last : [key_value.last]
13
+ end
14
+
15
+ def select?(obj)
16
+ raise "Attribute '#{@attribute}' not found for #{obj.class.name}" unless obj.attribute_names.include?(@attribute.to_s)
17
+ response = obj.__send__ @attribute
18
+ return nil unless response
19
+ raise "Attributes resulting in arrays not supported" if response.is_a?(Array)
20
+ response = [response] unless response.is_a?(Array)
21
+ response.any?{|value_or_object| attribute_tests.any?{|test| test.match?(value_or_object)}}
22
+ end
23
+
24
+ def attribute_tests
25
+ @attribute_tests ||= @test_values.map do |param|
26
+ AttributeTest.for(param)
27
+ end
28
+ end
29
+
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,12 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+ class Base
5
+ def initialize(arg)
6
+ @argument = arg
7
+ end
8
+ end
9
+
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+
5
+ class HashSelector < Base
6
+ @@hash_selector_classes = [
7
+ Selector::AttributeSelector,
8
+ Selector::UnsupportedSelector
9
+ ]
10
+ def self.applies?(arg)
11
+ arg.is_a? Hash
12
+ end
13
+
14
+ def select?(obj)
15
+ selectors.any?{|selector| selector.select?(obj) }
16
+ end
17
+
18
+ def selectors
19
+ @selectors ||= @argument.map do |key_value|
20
+ hash_selector_class_for(key_value).new(key_value)
21
+ end
22
+ end
23
+ def hash_selector_class_for(key_value)
24
+ @@hash_selector_classes.detect{|klass| klass.applies?(key_value) }
25
+ end
26
+
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+ class IntegerSelector < Base
5
+
6
+ def self.applies?(arg)
7
+ arg.is_a? Integer
8
+ end
9
+
10
+ def select?(obj)
11
+ obj.id == @argument
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+ class RangeSelector < Base
5
+
6
+ def self.applies?(arg)
7
+ arg.is_a? Range
8
+ end
9
+
10
+ def select?(obj)
11
+ @argument.include?(obj.id)
12
+ end
13
+
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+ class StringSelector < Base
5
+ def self.applies?(arg)
6
+ arg.is_a? String
7
+ end
8
+ def select?(obj)
9
+ obj.instance_eval { @argument }
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,17 @@
1
+ module Arspy
2
+ module Operators
3
+ module Selector
4
+ class UnsupportedSelector < Base
5
+ def self.applies?(arg)
6
+ true
7
+ end
8
+ def initialize(arg)
9
+ raise "#{arg.inspect} not supported."
10
+ end
11
+ def select?(obj)
12
+ nil
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end