society 0.13.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.bowerrc +3 -0
- data/.gitignore +2 -14
- data/README.md +18 -17
- data/bower.json +6 -0
- data/lib/society.rb +16 -6
- data/lib/society/association_processor.rb +206 -0
- data/lib/society/cli.rb +12 -9
- data/lib/society/edge.rb +15 -0
- data/lib/society/formatter/graph/json.rb +44 -0
- data/lib/society/formatter/report/html.rb +73 -0
- data/lib/society/formatter/report/json.rb +39 -0
- data/lib/society/formatter/report/templates/components/.gitignore +7 -0
- data/lib/society/formatter/report/templates/components/d3/d3.min.js +5 -0
- data/lib/society/formatter/report/templates/components/society-assets/society.css +70 -0
- data/lib/society/formatter/report/templates/components/society-assets/society.js +420 -0
- data/lib/society/formatter/report/templates/index.htm.haml +48 -0
- data/lib/society/object_graph.rb +3 -2
- data/lib/society/parser.rb +49 -32
- data/lib/society/reference_processor.rb +60 -0
- data/lib/society/version.rb +1 -1
- data/society.gemspec +4 -1
- data/society_graph.json +1 -0
- data/spec/association_processor_spec.rb +174 -0
- data/spec/cli_spec.rb +25 -0
- data/spec/fixtures/foo.rb +6 -18
- data/spec/formatter/graph/json_spec.rb +52 -0
- data/spec/formatter/report/html_spec.rb +75 -0
- data/spec/formatter/report/json_spec.rb +70 -0
- data/spec/object_graph_spec.rb +30 -0
- data/spec/parser_spec.rb +61 -0
- data/spec/reference_processor_spec.rb +18 -0
- data/spec/spec_helper.rb +3 -0
- metadata +77 -13
- data/lib/society/matrix.rb +0 -36
- data/lib/society/node.rb +0 -19
- data/spec/fixtures/bar.rb +0 -6
- data/spec/matrix_spec.rb +0 -27
- 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
|
+
|
data/lib/society/object_graph.rb
CHANGED
data/lib/society/parser.rb
CHANGED
@@ -1,52 +1,69 @@
|
|
1
|
-
require 'fukuzatsu'
|
2
|
-
|
3
1
|
module Society
|
4
2
|
|
5
|
-
class 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
|
-
|
28
|
+
FORMATTERS = {
|
29
|
+
html: Society::Formatter::Report::HTML,
|
30
|
+
json: Society::Formatter::Report::Json
|
31
|
+
}
|
8
32
|
|
9
|
-
def
|
10
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
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
|
44
|
-
|
56
|
+
def associations_from(all_classes)
|
57
|
+
@association_processor ||= AssociationProcessor.new(all_classes)
|
58
|
+
@association_processor.associations
|
45
59
|
end
|
46
60
|
|
47
|
-
def
|
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
|
+
|
data/lib/society/version.rb
CHANGED
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 "
|
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
|
data/society_graph.json
ADDED
@@ -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
|
+
|