society 0.13.2 → 1.0.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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/.bowerrc +3 -0
  3. data/.gitignore +2 -14
  4. data/README.md +18 -17
  5. data/bower.json +6 -0
  6. data/lib/society.rb +16 -6
  7. data/lib/society/association_processor.rb +206 -0
  8. data/lib/society/cli.rb +12 -9
  9. data/lib/society/edge.rb +15 -0
  10. data/lib/society/formatter/graph/json.rb +44 -0
  11. data/lib/society/formatter/report/html.rb +73 -0
  12. data/lib/society/formatter/report/json.rb +39 -0
  13. data/lib/society/formatter/report/templates/components/.gitignore +7 -0
  14. data/lib/society/formatter/report/templates/components/d3/d3.min.js +5 -0
  15. data/lib/society/formatter/report/templates/components/society-assets/society.css +70 -0
  16. data/lib/society/formatter/report/templates/components/society-assets/society.js +420 -0
  17. data/lib/society/formatter/report/templates/index.htm.haml +48 -0
  18. data/lib/society/object_graph.rb +3 -2
  19. data/lib/society/parser.rb +49 -32
  20. data/lib/society/reference_processor.rb +60 -0
  21. data/lib/society/version.rb +1 -1
  22. data/society.gemspec +4 -1
  23. data/society_graph.json +1 -0
  24. data/spec/association_processor_spec.rb +174 -0
  25. data/spec/cli_spec.rb +25 -0
  26. data/spec/fixtures/foo.rb +6 -18
  27. data/spec/formatter/graph/json_spec.rb +52 -0
  28. data/spec/formatter/report/html_spec.rb +75 -0
  29. data/spec/formatter/report/json_spec.rb +70 -0
  30. data/spec/object_graph_spec.rb +30 -0
  31. data/spec/parser_spec.rb +61 -0
  32. data/spec/reference_processor_spec.rb +18 -0
  33. data/spec/spec_helper.rb +3 -0
  34. metadata +77 -13
  35. data/lib/society/matrix.rb +0 -36
  36. data/lib/society/node.rb +0 -19
  37. data/spec/fixtures/bar.rb +0 -6
  38. data/spec/matrix_spec.rb +0 -27
  39. data/spec/node_spec.rb +0 -30
@@ -0,0 +1,48 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %meta{:charset => "utf-8"}
5
+ %title Society Relational Analysis
6
+ :css
7
+ @import url(stylesheets/society.css);
8
+ %script{:src => "javascripts/d3.min.js"}
9
+ %script{:src => "javascripts/society.js"}
10
+ :css
11
+ body {
12
+ background-color: #fff;
13
+ padding: 1em;
14
+ font-family: Consolas, Verdana, sans-serif;
15
+ font-size: 12px;
16
+ }
17
+ %body
18
+ %div{:style => "text-align: center;"}
19
+ %h1 Relational Matrices
20
+ %h3
21
+ Generated by
22
+ %a{:href => "https://github.com/Bantik/society"} Society
23
+ %hr{:size => "1"}/
24
+ %div{:style => "text-align: center;"}
25
+ %h2 Heatmap
26
+ #heatmap
27
+ %hr{:size => "1"}/
28
+ %div{:style => "text-align: center;"}
29
+ %h2 Network
30
+ %p
31
+ %em
32
+ Hover over a class name to see incoming references (green) and outgoing
33
+ references (red).
34
+ #network{:style => "height: 100%; width: 100%;"}
35
+ :javascript
36
+ var data = JSON.parse('#{json_data}');
37
+ Society.generate("#network", {
38
+ data: {
39
+ json: data
40
+ }
41
+ });
42
+ Society.generate("#heatmap", {
43
+ type: "heatmap",
44
+ data: {
45
+ json: data
46
+ }
47
+ });
48
+
@@ -2,10 +2,11 @@ module Society
2
2
 
3
3
  class ObjectGraph
4
4
 
5
- attr_accessor :nodes
5
+ attr_accessor :nodes, :edges
6
6
 
7
- def initialize(nodes: nodes=[])
7
+ def initialize(nodes: nodes=[], edges: edges=[])
8
8
  @nodes = nodes
9
+ @edges = edges
9
10
  end
10
11
 
11
12
  end
@@ -1,52 +1,69 @@
1
- require 'fukuzatsu'
2
-
3
1
  module Society
4
2
 
5
- class Parser < Fukuzatsu::Parser
3
+ class Parser
4
+
5
+ def self.for_files(file_path)
6
+ new(::Analyst.for_files(file_path))
7
+ end
8
+
9
+ def self.for_source(source)
10
+ new(::Analyst.for_source(source))
11
+ end
12
+
13
+ attr_reader :analyzer
14
+
15
+ def initialize(analyzer)
16
+ @analyzer = analyzer
17
+ end
18
+
19
+ def report(format, output_path=nil)
20
+ raise ArgumentError, "Unknown format #{format}" unless known_formats.include?(format)
21
+ options = { json_data: json_data }
22
+ options[:output_path] = output_path unless output_path.nil?
23
+ FORMATTERS[format].new(options).write
24
+ end
25
+
26
+ private
6
27
 
7
- attr_reader :parsed_files
28
+ FORMATTERS = {
29
+ html: Society::Formatter::Report::HTML,
30
+ json: Society::Formatter::Report::Json
31
+ }
8
32
 
9
- def initialize(start_path)
10
- super(start_path, :none, 0)
11
- @parsed_files = parse_files
33
+ def classes
34
+ @classes ||= analyzer.classes
12
35
  end
13
36
 
14
37
  def class_graph
15
38
  @class_graph ||= begin
16
- graph = ObjectGraph.new
17
- graph.nodes = parsed_files.map do |parsed_file|
18
- Node.new(
19
- name: parsed_file.class_name,
20
- address: parsed_file.path_to_file,
21
- edges: parsed_file.class_references
22
- )
23
- end
24
- graph
39
+ associations = associations_from(classes) + references_from(classes)
40
+ # TODO: merge identical classes, and (somewhere else) deal with
41
+ # identical associations too. need a WeightedEdge, and each
42
+ # one will be unique on [from, to], but will have a weight
43
+
44
+ ObjectGraph.new(nodes: classes, edges: associations)
25
45
  end
26
46
  end
27
47
 
28
- def method_graph
29
- @method_graph ||= begin
30
- graph = ObjectGraph.new
31
- target = parsed_files.first
32
- graph.nodes = target.methods.map do |method|
33
- Node.new(
34
- name: method.name,
35
- address: target.class_name,
36
- edges: method.references
37
- )
38
- end
39
- graph
40
- end
48
+ def json_data
49
+ Society::Formatter::Graph::JSON.new(class_graph).to_json
50
+ end
51
+
52
+ def known_formats
53
+ FORMATTERS.keys
41
54
  end
42
55
 
43
- def matrix(graph)
44
- Society::Matrix.new(graph.nodes)
56
+ def associations_from(all_classes)
57
+ @association_processor ||= AssociationProcessor.new(all_classes)
58
+ @association_processor.associations
45
59
  end
46
60
 
47
- def reset_output_directory
61
+ def references_from(all_classes)
62
+ @reference_processor ||= ReferenceProcessor.new(all_classes)
63
+ @reference_processor.references
48
64
  end
49
65
 
50
66
  end
51
67
 
52
68
  end
69
+
@@ -0,0 +1,60 @@
1
+ module Society
2
+
3
+ class ReferenceProcessor
4
+
5
+ attr_reader :classes, :references, :unresolved_references
6
+
7
+ def initialize(classes)
8
+ @classes = classes
9
+ @references = []
10
+ @unresolved_references = []
11
+ process
12
+ end
13
+
14
+ private
15
+
16
+ def process
17
+ classes.each do |klass|
18
+ klass.constants.each do |const|
19
+ if assigned_constant(const, klass)
20
+ next
21
+ elsif target = perfect_match_for(const)
22
+ @references << Edge.new(from: klass,
23
+ to: target,
24
+ meta: {
25
+ type: :perfect,
26
+ entity: const})
27
+ elsif target = partial_match_for(const)
28
+ @references << Edge.new(from: klass,
29
+ to: target,
30
+ meta: {
31
+ type: :partial,
32
+ entity: const})
33
+ else
34
+ @unresolved_references << { class: klass,
35
+ target_name: const.full_name,
36
+ constant: const }
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def perfect_match_for(const)
43
+ classes.detect { |klass| klass.full_name == const.name }
44
+ end
45
+
46
+ def partial_match_for(const)
47
+ partial_matches = classes.select do |klass|
48
+ klass.full_name.include? const.full_name
49
+ end
50
+ partial_matches.first if partial_matches.size == 1
51
+ end
52
+
53
+ def assigned_constant(const, klass)
54
+ klass.constant_assignments.map(&:name).include? const.name
55
+ end
56
+
57
+ end
58
+
59
+ end
60
+
@@ -1,3 +1,3 @@
1
1
  module Society
2
- VERSION = "0.13.2"
2
+ VERSION = "1.0.0"
3
3
  end
data/society.gemspec CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "fukuzatsu", ">= 1.0.5"
21
+ spec.add_dependency "activesupport"
22
+ spec.add_dependency "analyst", ">= 1.0.0"
22
23
  spec.add_dependency "parser"
23
24
  spec.add_dependency "rainbow"
24
25
  spec.add_dependency "terminal-table"
@@ -29,5 +30,7 @@ Gem::Specification.new do |spec|
29
30
  spec.add_development_dependency "rake"
30
31
  spec.add_development_dependency "rspec"
31
32
  spec.add_development_dependency "simplecov"
33
+ spec.add_development_dependency "byebug"
34
+ spec.add_development_dependency "awesome_print"
32
35
 
33
36
  end
@@ -0,0 +1 @@
1
+ {"nodes":[{"name":"Account"},{"name":"Attachment"},{"name":"Attempt"},{"name":"Auth"},{"name":"Certificate"},{"name":"Collection"},{"name":"CollectionEnrollment"},{"name":"CollectionGroupEnrollment"},{"name":"CollectionItem"},{"name":"ConcreteConfig"},{"name":"CourseTemplate"},{"name":"CustomField"},{"name":"EnrollingMembership"},{"name":"Enrollment"},{"name":"Group"},{"name":"GroupEnrollment"},{"name":"ImportObject"},{"name":"LearnerCourse"},{"name":"LearnerCustomFieldValue"},{"name":"LearnerFile"},{"name":"LearnerLibraryCourse"},{"name":"LearnerLiveCourse"},{"name":"LearnerSlide"},{"name":"LiveCourse"},{"name":"LiveCourseEnrollment"},{"name":"LiveCourseEnrollmentSource"},{"name":"LiveCourseGroupEnrollment"},{"name":"LiveCourseSession"},{"name":"LiveCourseSessionRegistration"},{"name":"Manager"},{"name":"Membership"},{"name":"MembershipBuilder"},{"name":"NotifiableEnrollment"},{"name":"NotifiableLiveCourseEnrollment"},{"name":"NotifiableLiveCourseSessionRegistration"},{"name":"Notification"},{"name":"NotificationProfile"},{"name":"PasswordToken"},{"name":"Presence"},{"name":"QuestionAnswer"},{"name":"QuestionHash"},{"name":"QuestionResponse"},{"name":"RequestHost"},{"name":"Rule"},{"name":"RulesAttributesNormalizer"},{"name":"SessionToken"},{"name":"Slide"},{"name":"User"},{"name":"Account::Config"},{"name":"Attempt::NullFurthestSlide"},{"name":"Auth::Basic"},{"name":"Auth::CAS"},{"name":"GroupEnrollment::StatisticsCalculator"},{"name":"QuestionHash::Factoid"},{"name":"QuestionHash::Factoid::MultipleChoiceQuestion"},{"name":"QuestionHash::Factoid::Statement"},{"name":"QuestionHash::Factoid::TrueFalseQuestion"},{"name":"QuestionHash::Glossary"},{"name":"QuestionHash::Glossary::MultipleChoiceQuestion"},{"name":"QuestionHash::Glossary::TrueFalseQuestion"},{"name":"QuestionHash::OrderedList"},{"name":"QuestionHash::OrderedList::MultipleChoiceQuestion"},{"name":"QuestionHash::OrderedList::ReorderQuestion"},{"name":"Rule::InvalidPredicate"},{"name":"RulesAttributesNormalizer::FieldRules"},{"name":"Notifications::AuthorCourseDigest"},{"name":"Notifications::CourseAssignedNotificationBuilder"},{"name":"Notifications::CourseDigest"},{"name":"Notifications::LearnerCourseDigest"},{"name":"Notifications::LiveCourseInvitationNotificationBuilder"},{"name":"Notifications::LiveCourseNotificationBuilder"},{"name":"Notifications::LiveCourseRegisteredForSessionNotificationBuilder"},{"name":"Notifications::LiveCourseSessionNotificationBuilder"},{"name":"Notifications::LiveCourseUnregisteredFromCanceledSessionNotificationBuilder"},{"name":"Notifications::LiveCourseUnregisteredFromSessionNotificationBuilder"},{"name":"Notifications::LiveCourseUnregisteredFromUnattendedSessionNotificationBuilder"},{"name":"Notifications::ManagerCourseDigest"},{"name":"Notifications::NotificationBuilder"},{"name":"Notifications::TimeUntilSessionNotificationBuilder"},{"name":"Reports::CourseStatusReport"},{"name":"Reports::PassRateReport"},{"name":"Reports::ScoreDistributionReport"},{"name":"Scorm::Activity"},{"name":"Scorm::Attempt"},{"name":"Scorm::CourseUploadResponse"},{"name":"Scorm::CourseUploadStatusResponse"},{"name":"Scorm::Enrollment"},{"name":"Scorm::RegistrationListResponse"},{"name":"Scorm::RegistrationResponse"},{"name":"Search::CustomFieldValueSearch"},{"name":"Search::EnrollableSearch"}],"edges":[{"from":0,"to":36},{"from":1,"to":46},{"from":2,"to":46},{"from":2,"to":46},{"from":2,"to":13},{"from":2,"to":41},{"from":5,"to":8},{"from":5,"to":6},{"from":5,"to":7},{"from":6,"to":5},{"from":6,"to":47},{"from":7,"to":5},{"from":7,"to":14},{"from":8,"to":5},{"from":8,"to":10},{"from":10,"to":47},{"from":10,"to":8},{"from":10,"to":15},{"from":10,"to":46},{"from":11,"to":18},{"from":11,"to":43},{"from":13,"to":10},{"from":13,"to":47},{"from":13,"to":2},{"from":13,"to":2},{"from":14,"to":30},{"from":14,"to":15},{"from":14,"to":26},{"from":14,"to":43},{"from":14,"to":7},{"from":15,"to":14},{"from":15,"to":10},{"from":16,"to":47},{"from":18,"to":47},{"from":18,"to":11},{"from":23,"to":47},{"from":23,"to":24},{"from":23,"to":27},{"from":23,"to":26},{"from":24,"to":47},{"from":24,"to":23},{"from":24,"to":25},{"from":24,"to":28},{"from":25,"to":24},{"from":26,"to":23},{"from":26,"to":14},{"from":27,"to":23},{"from":27,"to":28},{"from":28,"to":47},{"from":28,"to":27},{"from":28,"to":24},{"from":29,"to":47},{"from":29,"to":47},{"from":30,"to":14},{"from":30,"to":47},{"from":36,"to":0},{"from":41,"to":2},{"from":41,"to":46},{"from":43,"to":14},{"from":43,"to":11},{"from":45,"to":47},{"from":46,"to":10},{"from":46,"to":41},{"from":46,"to":1},{"from":47,"to":13},{"from":47,"to":10},{"from":47,"to":6},{"from":47,"to":24},{"from":47,"to":28},{"from":47,"to":23},{"from":47,"to":45},{"from":47,"to":30},{"from":47,"to":18},{"from":47,"to":29},{"from":47,"to":29},{"from":5,"to":10},{"from":5,"to":47},{"from":5,"to":14},{"from":10,"to":5},{"from":10,"to":14},{"from":11,"to":47},{"from":13,"to":46},{"from":14,"to":10},{"from":14,"to":23},{"from":14,"to":47},{"from":14,"to":5},{"from":23,"to":47},{"from":24,"to":14},{"from":24,"to":27},{"from":27,"to":47},{"from":47,"to":10},{"from":47,"to":23},{"from":47,"to":14},{"from":47,"to":11},{"from":47,"to":47},{"from":47,"to":47},{"from":0,"to":0},{"from":0,"to":3},{"from":0,"to":48},{"from":0,"to":36},{"from":2,"to":49},{"from":2,"to":22},{"from":2,"to":41},{"from":5,"to":13},{"from":5,"to":10},{"from":6,"to":13},{"from":6,"to":2},{"from":10,"to":46},{"from":12,"to":30},{"from":12,"to":30},{"from":13,"to":46},{"from":13,"to":47},{"from":14,"to":10},{"from":14,"to":14},{"from":14,"to":47},{"from":27,"to":78},{"from":27,"to":78},{"from":27,"to":78},{"from":28,"to":78},{"from":31,"to":30},{"from":31,"to":30},{"from":31,"to":30},{"from":32,"to":86},{"from":32,"to":66},{"from":32,"to":66},{"from":33,"to":69},{"from":33,"to":69},{"from":34,"to":71},{"from":34,"to":78},{"from":34,"to":78},{"from":37,"to":78},{"from":37,"to":78},{"from":37,"to":47},{"from":37,"to":37},{"from":40,"to":40},{"from":40,"to":40},{"from":40,"to":55},{"from":40,"to":40},{"from":40,"to":40},{"from":40,"to":40},{"from":40,"to":40},{"from":41,"to":40},{"from":43,"to":63},{"from":45,"to":45},{"from":47,"to":47},{"from":47,"to":47},{"from":47,"to":5},{"from":47,"to":10},{"from":47,"to":14},{"from":47,"to":23},{"from":53,"to":40},{"from":54,"to":40},{"from":53,"to":40},{"from":56,"to":40},{"from":53,"to":55},{"from":57,"to":40},{"from":58,"to":40},{"from":57,"to":40},{"from":59,"to":40},{"from":60,"to":40},{"from":61,"to":40},{"from":60,"to":40},{"from":62,"to":40},{"from":77,"to":35},{"from":82,"to":82},{"from":82,"to":82},{"from":83,"to":83},{"from":83,"to":83},{"from":83,"to":78},{"from":86,"to":83},{"from":86,"to":83},{"from":90,"to":47},{"from":90,"to":14}]}
@@ -0,0 +1,174 @@
1
+ require 'spec_helper'
2
+
3
+ describe Society::AssociationProcessor do
4
+
5
+ describe "#associations" do
6
+
7
+ context "`belongs_to`, `has_many`, and `has_many through:` associations" do
8
+ let(:code) {
9
+ <<-CODE
10
+ class Assembly < ActiveRecord::Base
11
+ has_many :manifests
12
+ has_many :parts, through: :manifests
13
+ end
14
+
15
+ class Manifest < ActiveRecord::Base
16
+ belongs_to :assembly
17
+ belongs_to :part
18
+ end
19
+
20
+ class Part < ActiveRecord::Base
21
+ has_many :manifests
22
+ has_many :assemblies, through: :manifests
23
+ end
24
+ CODE
25
+ }
26
+
27
+ let(:parser) { Society::Parser.for_source(code) }
28
+ let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
29
+ let(:assembly) { processor.classes.detect {|c| c.name == "Assembly" } }
30
+ let(:manifest) { processor.classes.detect {|c| c.name == "Manifest" } }
31
+ let(:part) { processor.classes.detect {|c| c.name == "Part" } }
32
+
33
+ it "records `has_many` and `has_many through:` associations" do
34
+ assembly_associations = processor.associations.select { |edge| edge.from == assembly }
35
+ expect(assembly_associations.map(&:to)).to match_array [manifest, part]
36
+
37
+ part_associations = processor.associations.select { |edge| edge.from == part }
38
+ expect(part_associations.map(&:to)).to eq [manifest, assembly]
39
+ end
40
+
41
+ it "records `belongs_to` associations" do
42
+ manifest_associations = processor.associations.select { |edge| edge.from == manifest }
43
+ expect(manifest_associations.map(&:to)).to match_array [assembly, part]
44
+ end
45
+ end
46
+
47
+ context "associations with `class_name` specified" do
48
+ let(:code) {<<-CODE
49
+ class Post < ActiveRecord::Base
50
+ belongs_to :author, :class_name => "User"
51
+ belongs_to :editor, :class_name => "User"
52
+ end
53
+
54
+ class User < ActiveRecord::Base
55
+ has_many :authored_posts, :foreign_key => "author_id", :class_name => "Post"
56
+ has_many :edited_posts, :foreign_key => "editor_id", :class_name => "Post"
57
+ end
58
+ CODE
59
+ }
60
+ let(:parser) { Society::Parser.for_source(code) }
61
+ let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
62
+ let(:user) { processor.classes.detect {|c| c.name == "User" } }
63
+ let(:post) { processor.classes.detect {|c| c.name == "Post" } }
64
+
65
+ it "records belongs_to's" do
66
+ post_associations = processor.associations.select { |edge| edge.from == post }
67
+ expect(post_associations.map(&:to)).to match_array [user, user]
68
+ end
69
+
70
+ it "records has_many's" do
71
+ user_associations = processor.associations.select { |edge| edge.from == user }
72
+ expect(user_associations.map(&:to)).to match_array [post, post]
73
+ end
74
+ end
75
+
76
+ context "associations with `class_name` or `source` specified" do
77
+ let(:code) { <<-CODE
78
+ class Post < ActiveRecord::Base
79
+ has_many :post_authorings, :foreign_key => :authored_post_id
80
+ has_many :authors, :through => :post_authorings, :source => :post_author
81
+ belongs_to :editor, :class_name => "User"
82
+ end
83
+
84
+ class User < ActiveRecord::Base
85
+ has_many :post_authorings, :foreign_key => :post_author_id
86
+ has_many :authored_posts, :through => :post_authorings
87
+ has_many :edited_posts, :foreign_key => :editor_id, :class_name => "Post"
88
+ end
89
+
90
+ class PostAuthoring < ActiveRecord::Base
91
+ belongs_to :post_author, :class_name => "User"
92
+ belongs_to :authored_post, :class_name => "Post"
93
+ end
94
+ CODE
95
+ }
96
+ let(:parser) { Society::Parser.for_source(code) }
97
+ let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
98
+ let(:user) { processor.classes.detect {|c| c.name == "User" } }
99
+ let(:post) { processor.classes.detect {|c| c.name == "Post" } }
100
+ let(:post_authoring) { processor.classes.detect {|c| c.name == "PostAuthoring" } }
101
+
102
+ it "handles `source` and `class_name` correctly" do
103
+ post_associations = processor.associations.select { |edge| edge.from == post }
104
+ expect(post_associations.map(&:to)).to match_array [post_authoring, user, user]
105
+ end
106
+
107
+ it "handles `through` and `class_name` correctly" do
108
+ user_associations = processor.associations.select { |edge| edge.from == user }
109
+ expect(user_associations.map(&:to)).to match_array [post_authoring, post, post]
110
+ end
111
+
112
+ it "handles `class_name` correctly" do
113
+ pa_associations = processor.associations.select { |edge| edge.from == post_authoring }
114
+ expect(pa_associations.map(&:to)).to match_array [user, post]
115
+ end
116
+ end
117
+
118
+ context "polymorphic associations" do
119
+ let(:code) { <<-CODE
120
+ class Picture < ActiveRecord::Base
121
+ belongs_to :imageable, polymorphic: true
122
+ end
123
+
124
+ class Employee < ActiveRecord::Base
125
+ has_many :pictures, as: :imageable
126
+ end
127
+
128
+ class Product < ActiveRecord::Base
129
+ has_many :pictures, as: :imageable
130
+ end
131
+ CODE
132
+ }
133
+ let(:parser) { Society::Parser.for_source(code) }
134
+ let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
135
+ let(:picture) { processor.classes.detect {|c| c.name == "Picture" } }
136
+ let(:employee) { processor.classes.detect {|c| c.name == "Employee" } }
137
+ let(:product) { processor.classes.detect {|c| c.name == "Product" } }
138
+
139
+ it "records the `polymorphic: true` side of the association" do
140
+ picture_associations = processor.associations.select { |edge| edge.from == picture }
141
+ expect(picture_associations.map(&:to)).to match_array [employee, product]
142
+ end
143
+
144
+ it "records the `as: :something` side of the asociation" do
145
+ employee_associations = processor.associations.select { |edge| edge.from == employee }
146
+ expect(employee_associations.map(&:to)).to match_array [picture]
147
+
148
+ product_associations = processor.associations.select { |edge| edge.from == product }
149
+ expect(product_associations .map(&:to)).to match_array [picture]
150
+ end
151
+ end
152
+
153
+ context "self joins" do
154
+ let(:code) {<<-CODE
155
+ class Employee < ActiveRecord::Base
156
+ has_many :subordinates, class_name: "Employee",
157
+ foreign_key: "manager_id"
158
+
159
+ belongs_to :manager, class_name: "Employee"
160
+ end
161
+ CODE
162
+ }
163
+ let(:parser) { Society::Parser.for_source(code) }
164
+ let(:processor) { Society::AssociationProcessor.new(parser.analyzer.classes) }
165
+ let(:employee) { processor.classes.detect {|c| c.name == "Employee" } }
166
+
167
+ it "records self joins" do
168
+ employee_associations = processor.associations.select { |edge| edge.from == employee }
169
+ expect(employee_associations.map(&:to)).to match_array [employee, employee]
170
+ end
171
+ end
172
+ end
173
+ end
174
+
data/spec/cli_spec.rb ADDED
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+ require_relative '../lib/society/cli'
3
+
4
+ describe Society::CLI do
5
+
6
+ describe "#from" do
7
+
8
+ let(:parser) { Society::Parser.new(nil) }
9
+
10
+ it "invokes Society with a path" do
11
+ allow(parser).to receive(:report)
12
+ expect(Society).to receive(:new) { parser }
13
+ Society::CLI.new.from("./spec/fixtures")
14
+ end
15
+
16
+ it "calls report on a Parser instance" do
17
+ expect(parser).to receive(:report)
18
+ allow(Society).to receive(:new) { parser }
19
+ Society::CLI.new.from("./spec/fixtures")
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+