verso 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/.gitignore +2 -0
  2. data/README.md +10 -22
  3. data/lib/verso.rb +10 -2
  4. data/lib/verso/base.rb +50 -0
  5. data/lib/verso/cluster.rb +55 -25
  6. data/lib/verso/cluster_list.rb +15 -10
  7. data/lib/verso/correlation_list.rb +30 -40
  8. data/lib/verso/course.rb +106 -43
  9. data/lib/verso/course_list.rb +57 -21
  10. data/lib/verso/credential.rb +94 -20
  11. data/lib/verso/credential_list.rb +30 -15
  12. data/lib/verso/duty_area.rb +23 -10
  13. data/lib/verso/edition_list.rb +18 -10
  14. data/lib/verso/emphasis.rb +26 -12
  15. data/lib/verso/emphasis_list.rb +18 -10
  16. data/lib/verso/examination_list.rb +28 -12
  17. data/lib/verso/extra.rb +34 -32
  18. data/lib/verso/extras_list.rb +28 -12
  19. data/lib/verso/frontmatter.rb +43 -11
  20. data/lib/verso/hash.rb +19 -0
  21. data/lib/verso/http_gettable.rb +31 -0
  22. data/lib/verso/occupation.rb +36 -14
  23. data/lib/verso/occupation_data.rb +35 -14
  24. data/lib/verso/occupation_list.rb +23 -20
  25. data/lib/verso/pathway.rb +32 -14
  26. data/lib/verso/program_area.rb +42 -21
  27. data/lib/verso/program_area_list.rb +15 -11
  28. data/lib/verso/standard.rb +45 -23
  29. data/lib/verso/standards_list.rb +41 -30
  30. data/lib/verso/task.rb +52 -17
  31. data/lib/verso/task_list.rb +40 -17
  32. data/lib/verso/version.rb +1 -1
  33. data/spec/cluster_list_spec.rb +78 -5
  34. data/spec/cluster_spec.rb +106 -9
  35. data/spec/correlation_list_spec.rb +108 -50
  36. data/spec/course_list_spec.rb +112 -23
  37. data/spec/course_spec.rb +321 -127
  38. data/spec/credential_list_spec.rb +83 -52
  39. data/spec/credential_spec.rb +358 -19
  40. data/spec/duty_area_spec.rb +47 -17
  41. data/spec/edition_list_spec.rb +90 -4
  42. data/spec/emphasis_list_spec.rb +75 -11
  43. data/spec/emphasis_spec.rb +37 -21
  44. data/spec/examination_list_spec.rb +146 -20
  45. data/spec/extra_spec.rb +61 -22
  46. data/spec/extras_list_spec.rb +80 -17
  47. data/spec/frontmatter_spec.rb +141 -6
  48. data/spec/hash_spec.rb +49 -0
  49. data/spec/occupation_data_spec.rb +31 -13
  50. data/spec/occupation_list_spec.rb +88 -15
  51. data/spec/occupation_spec.rb +72 -28
  52. data/spec/pathway_spec.rb +47 -27
  53. data/spec/program_area_list_spec.rb +78 -4
  54. data/spec/program_area_spec.rb +70 -22
  55. data/spec/standard_spec.rb +94 -36
  56. data/spec/standards_list_spec.rb +130 -36
  57. data/spec/task_list_spec.rb +160 -51
  58. data/spec/task_spec.rb +120 -33
  59. data/verso.gemspec +3 -1
  60. metadata +41 -17
  61. data/lib/verso/http_get.rb +0 -9
  62. data/lib/verso/sol_correlation_list.rb +0 -53
  63. data/spec/sol_correlation_list_spec.rb +0 -74
data/.gitignore CHANGED
@@ -3,3 +3,5 @@
3
3
  Gemfile.lock
4
4
  pkg/*
5
5
  tmp/*
6
+ doc/*
7
+ .yardoc/*
data/README.md CHANGED
@@ -6,8 +6,7 @@ Technical Education curriculum, course, and occupation data.
6
6
 
7
7
  ## Installation
8
8
 
9
- The verso gem has been tested with Ruby 1.9.3. It does not currently work with
10
- Ruby 1.8.7.
9
+ The verso gem has been tested with Ruby 1.9.3 and 1.8.7.
11
10
 
12
11
  gem install verso
13
12
 
@@ -17,28 +16,17 @@ Ruby 1.8.7.
17
16
  cd verso
18
17
  rake install
19
18
 
20
- ## Caveat
21
-
22
- This gem has been *extracted* from a larger project, the Web application that
23
- presents [Verso](http://www.cteresource.org/verso/), the [Administrative
24
- Planning Guide](http://www.cteresource.org/apg/), and the [Career Planning
25
- Guide](http://www.cteresource.org/cpg/) on the Virginia CTE Resource Center Web
26
- site. For this reason the object interfaces are sometimes inconsistent (for
27
- instance collection objects do not always respond to all the expected
28
- methods -- `#to_a` is your friend), the specs are incomplete, and the
29
- documentation is nil.
30
-
31
- All that said, the wrapper is reasonably complete, and should make a great
32
- starting pointing for getting course, curriculum, and occupation data out of
33
- Verso. The classes available in lib/verso correspond to the resources described
34
- in the [API documentation](http://api.cteresource.org/docs). The usual points
35
- of entry are the [Cluster List](http://api.cteresource.org/docs/clusters), the
19
+ ## Quick Start
20
+
21
+ The wrapper should make a great starting pointing for getting course,
22
+ curriculum, and occupation data out of Verso. The classes available in
23
+ lib/verso correspond to the resources described in the [API
24
+ documentation](http://api.cteresource.org/docs). The usual points of entry
25
+ are the [Cluster List](http://api.cteresource.org/docs/clusters), the
36
26
  [Course List](http://api.cteresource.org/docs/courses), the [Occupation
37
27
  List](http://api.cteresource.org/docs/occupations), and the [Credential
38
28
  List](http://api.cteresource.org/docs/credentials).
39
29
 
40
- ## Quick Start
41
-
42
30
  ### Start with Clusters
43
31
 
44
32
  require 'verso'
@@ -51,8 +39,8 @@ List](http://api.cteresource.org/docs/credentials).
51
39
 
52
40
  require 'verso'
53
41
  course = Verso::CourseList.new(:code => "6321").first
54
- course.title # => "Accouting, Advanced"
55
- da = course.duty_areas.to_a[5] # I warned you about the incomplete interface . . . .
42
+ course.title # => "Accounting, Advanced"
43
+ da = course.duty_areas[5]
56
44
  da.title # => "Using Technology to Implement Accounting Procedures"
57
45
  da.tasks.last.statement # => "Apply emerging technology trends used in the accounting profession."
58
46
 
@@ -1,9 +1,16 @@
1
+ # external
1
2
  require 'addressable/uri'
3
+ require 'forwardable'
2
4
  require 'json'
3
5
  require 'net/http'
4
6
  require 'ostruct'
5
7
 
6
- require 'verso/http_get'
8
+ # base/mixin/override
9
+ require 'verso/base'
10
+ require 'verso/hash'
11
+ require 'verso/http_gettable'
12
+
13
+ # resources and children
7
14
  require 'verso/cluster'
8
15
  require 'verso/cluster_list'
9
16
  require 'verso/correlation_list'
@@ -25,9 +32,10 @@ require 'verso/occupation_list'
25
32
  require 'verso/pathway'
26
33
  require 'verso/program_area_list'
27
34
  require 'verso/program_area'
28
- require 'verso/sol_correlation_list'
29
35
  require 'verso/standard'
30
36
  require 'verso/standards_list'
31
37
  require 'verso/task'
32
38
  require 'verso/task_list'
39
+
40
+ # gem version
33
41
  require 'verso/version'
@@ -0,0 +1,50 @@
1
+ # A wrapper for the Virginia CTE Resource Center's Web API, providing access
2
+ # to Virginia's Career and Technical Education curriculum, course, and
3
+ # occupation data.
4
+ #
5
+ # @see http://api.cteresource.org/docs Web API documentation
6
+ module Verso
7
+ # @abstract Classes derived from Verso::Base should define attr_readers or
8
+ # appropriate methods (using Verso::Base#get_attr w/in methods) to access
9
+ # attributes stored in the attrs hash.
10
+ #
11
+ # @!attribute [r] attrs
12
+ # attribute hash
13
+ class Base
14
+ attr_reader :attrs
15
+ alias to_hash attrs
16
+
17
+ # Initialize new object
18
+ #
19
+ # @param attrs [Hash]
20
+ # @return [Verso::Base]
21
+ def initialize(attrs={})
22
+ @attrs = attrs.symbolize_nested_keys!
23
+ end
24
+
25
+ # Define methods to retrieve values from attribute hash
26
+ #
27
+ # @param attrs [Symbol]
28
+ def self.attr_reader(*attrs)
29
+ attrs.each do |attr|
30
+ class_eval do
31
+ define_method attr do
32
+ get_attr(attr.to_sym)
33
+ end
34
+ end
35
+ end
36
+ end
37
+
38
+ protected
39
+
40
+ # Get attribute value from attrs attribute store or raise NoMethodError
41
+ # if the key is not defined.
42
+ #
43
+ # @param attr [Symbol]
44
+ # @return attribute value
45
+ # @raise [NoMethodError]
46
+ def get_attr(attr)
47
+ attrs.has_key?(attr) ? attrs[attr] : method_missing(attr)
48
+ end
49
+ end
50
+ end
@@ -1,43 +1,73 @@
1
1
  module Verso
2
- class Cluster
3
- include HTTPGet
2
+ # Career Cluster Resource
3
+ #
4
+ # @see http://api.cteresource.org/docs/clusters/cluster
5
+ # @see http://www.careertech.org/career-clusters/glance/clusters.html
6
+ #
7
+ # @!attribute [r] code
8
+ # @return [String] Cluster code
9
+ # @!attribute [r] description
10
+ # @return [String] HTML-formatted Cluster description
11
+ # @!attribute [r] id
12
+ # @return [Fixnum] Cluster id
13
+ # @!attribute [r] postsecondary_info
14
+ # @return [String] HTML-formatted postsecondary preparation info
15
+ #
16
+ # @overload initialize(attrs={})
17
+ # @note Any attributes may be set upon instantiation, using Options Hash.
18
+ # The following are required:
19
+ # @option attrs [Fixnum] :id Cluster id *Required*
20
+ class Cluster < Verso::Base
21
+ include HTTPGettable
22
+ attr_reader :code, :description, :id, :postsecondary_info
4
23
 
5
- def initialize(opts)
6
- @raw_cluster = opts
24
+ # Return VDOE Cluster contact. The contact will respond to #name, #email,
25
+ # and #phone, returning Strings.
26
+ #
27
+ # @return [OpenStruct]
28
+ def contact
29
+ @contact ||= OpenStruct.new(get_attr(:contact))
7
30
  end
8
31
 
9
- def method_missing(mname)
10
- if !@raw_cluster.has_key?(mname.to_s)
11
- @raw_cluster.merge!(JSON.parse(http_get("/clusters/#{id}"))["cluster"])
12
- end
13
- @raw_cluster[mname.to_s]
32
+ # Courses related to the Cluster
33
+ #
34
+ # @return [Verso::CourseList]
35
+ def courses
36
+ @courses ||= CourseList.new(:cluster => slug.gsub('-', ' ')).
37
+ sort_by { |c| c.title + c.edition }.
38
+ uniq { |c| c.code + c.edition }
14
39
  end
15
40
 
16
- def id
17
- @raw_cluster["id"]
41
+ # The Cluster's Pathways
42
+ #
43
+ # @see http://www.careertech.org/career-clusters/clusters-occupations.html
44
+ # @see Verso::Pathway
45
+ #
46
+ # @return [Array]
47
+ def pathways
48
+ @pathways ||= get_attr(:pathways).
49
+ collect { |p| Pathway.new(p) }
18
50
  end
19
51
 
20
- def title
21
- @title ||= @raw_cluster["title"] || @raw_cluster["cluster"]["title"]
52
+ # @return [String] parameterized title
53
+ def slug
54
+ title.parameterize
22
55
  end
23
56
 
24
- def contact
25
- @contact ||= OpenStruct.new(method_missing(:contact))
57
+ # @return [String] Cluster title
58
+ def title
59
+ @title ||= attrs[:title] || attrs[:cluster][:title]
26
60
  end
27
61
 
28
- def slug
29
- @raw_cluster["title"].parameterize
30
- end
62
+ private
31
63
 
32
- def pathways
33
- @pathways ||= method_missing(:pathways).
34
- collect { |p| Pathway.new(p) }
64
+ def fetch
65
+ super[:cluster]
35
66
  end
36
67
 
37
- def courses
38
- @courses ||= CourseList.new(:cluster => slug.gsub('-', ' ')).
39
- sort_by { |c| c.title + c.edition }.
40
- uniq { |c| c.code + c.edition }
68
+ # @return [String] URI for Cluster resource
69
+ def path
70
+ "/clusters/#{id}"
41
71
  end
42
72
  end
43
73
  end
@@ -1,19 +1,24 @@
1
1
  module Verso
2
- class ClusterList
2
+ # Career Cluster List Resource
3
+ #
4
+ # A collection of {Verso::Cluster} objects
5
+ #
6
+ # @see http://api.cteresource.org/docs/clusters
7
+ # @see http://www.careertech.org/career-clusters/glance/clusters.html
8
+ class ClusterList < Verso::Base
3
9
  include Enumerable
4
- include HTTPGet
10
+ include HTTPGettable
11
+ extend Forwardable
12
+ def_delegators :clusters, :[], :each, :empty?, :last, :length
5
13
 
6
- def clusters
7
- @clusters ||= JSON.parse(http_get('/clusters/'))["clusters"].
8
- collect { |c| Cluster.new(c) }
9
- end
14
+ private
10
15
 
11
- def each &block
12
- clusters.each &block
16
+ def clusters
17
+ @clusters ||= get_attr(:clusters).collect { |c| Cluster.new(c) }
13
18
  end
14
19
 
15
- def last
16
- clusters[-1]
20
+ def path
21
+ "/clusters/"
17
22
  end
18
23
  end
19
24
  end
@@ -1,53 +1,43 @@
1
1
  module Verso
2
- class CorrelationList
2
+ # Correlation List Resource
3
+ #
4
+ # A crosswalk of competencies to standards body goals.
5
+ # @see http://api.cteresource.org/docs/courses/course/standards/standard/correlations
6
+ #
7
+ # @!attribute [r] code
8
+ # @return [String] Course code
9
+ # @!attribute [r] edition
10
+ # @return [String] Edition year
11
+ # @!attribute [r] name
12
+ # @return [String] Standards Body name, used as an identifier and not to
13
+ # be confused with its title
14
+ #
15
+ # @overload initialize(attrs={})
16
+ # @note Certain attributes are required for instantiation.
17
+ # @option attrs [String] :code Course code *Required*
18
+ # @option attrs [String] :edition Edition year *Required*
19
+ # @option attrs [String] :name Standards Body name *Required*
20
+ class CorrelationList < Verso::Base
3
21
  include Enumerable
4
- include HTTPGet
5
-
6
- def initialize(context, name)
7
- @context = context
8
- @name = name
9
- end
22
+ include HTTPGettable
23
+ extend Forwardable
24
+ def_delegators :correlations, :[], :each, :empty?, :last, :length
25
+ attr_reader :code, :edition, :name
10
26
 
27
+ # @return [String] Standards body title
11
28
  def title
12
- @title ||= @context.standards.find { |s| s.name == @name }.title
29
+ @title ||= first.goals.first.title
13
30
  end
14
31
 
15
- def code
16
- @context.code
17
- end
18
-
19
- def edition
20
- @context.edition
21
- end
22
-
23
- def tasklist
24
- @context.duty_areas
25
- end
26
-
27
- def sol_names
28
- @sol_names ||= @context.standards.sols.collect { |s| s.name }
29
- end
32
+ private
30
33
 
31
34
  def correlations
32
- @correlations ||= JSON.parse(
33
- http_get("/courses/#{code},#{edition}/standards/#{@name}/correlations")
34
- )["correlations"]
35
+ @correlations ||= get_attr(:correlations).
36
+ collect { |c| Task.new(c.merge(:code => code, :edition => edition)) }
35
37
  end
36
38
 
37
- def each &block
38
- count = 0
39
- tasklist.each do |da|
40
- da.tasks.each do |task|
41
- count += 1
42
- related = correlations.find { |c| c["id"] == task.id }
43
- if related.nil?
44
- next
45
- else
46
- related["number"] = count
47
- yield Task.new(related)
48
- end
49
- end
50
- end
39
+ def path
40
+ "/courses/#{code},#{edition}/standards/#{name}/correlations"
51
41
  end
52
42
  end
53
43
  end
@@ -1,23 +1,79 @@
1
1
  module Verso
2
- class Course
3
- include HTTPGet
2
+ # Course Resource
3
+ #
4
+ # The chief container of Virginia CTE course data and the *parent of
5
+ # curriculum data.
6
+ #
7
+ # @see http://api.cteresource.org/docs/courses/course
8
+ #
9
+ # @!attribute [r] co_op
10
+ # @return [Boolean] Is this a co-op course?
11
+ # @!attribute [r] code
12
+ # @return [String] Course code
13
+ # @!attribute [r] complement
14
+ # @return [Boolean] Is this a complementary course?
15
+ # @!attribute [r] description
16
+ # @return [String] HTML-formatted course description
17
+ # @!attribute [r] duration
18
+ # @return [Fixnum] Course duration in weeks
19
+ # @!attribute [r] edition
20
+ # @return [String] Edition year
21
+ # @!attribute [r] grade_levels
22
+ # @return [String] Space-separated list of recommended grade levels
23
+ # @!attribute [r] hours
24
+ # @return [nil,Fixnum] Course hours
25
+ # @!attribute [r] hs_credit_in_ms
26
+ # @return [Boolean] High school credit in middle school?
27
+ # @!attribute [r] osha_exempt
28
+ # @return [Boolean] Is this course exempt from the OSHA memo?
29
+ # @!attribute [r] related_resources
30
+ # @return [Array] Array of strings specifying related resources
31
+ # @see http://api.cteresource.org/docs/courses/course
32
+ # @!attribute [r] title
33
+ # @return [String] Course title
34
+ #
35
+ # @overload initialize(attrs={})
36
+ # @note Any attributes may be set upon instantiation, using Options Hash.
37
+ # The following are required:
38
+ # @option attrs [String] :code Course code *Required*
39
+ # @option attrs [String] :edition Edition year *Required*
40
+ class Course < Verso::Base
41
+ include HTTPGettable
42
+ attr_reader :co_op, :code, :complement, :description, :duration,
43
+ :edition, :grade_levels, :hours, :hs_credit_in_ms, :osha_exempt,
44
+ :related_resources, :title
45
+ alias co_op? co_op
46
+ alias complement? complement
47
+ alias hs_credit_in_ms? hs_credit_in_ms
48
+ alias osha_exempt? osha_exempt
4
49
 
5
- def initialize(raw_course)
6
- @raw_course = raw_course
50
+ # @return [Array] Collection of certification {Verso::Credential} objects
51
+ def certifications
52
+ @certfications ||= credentials.select { |c| c.type == "Certification" }
7
53
  end
8
54
 
9
- def method_missing(mname)
10
- if !@raw_course.has_key?(mname.to_s)
11
- @raw_course = JSON.parse(http_get("/courses/#{code},#{edition}"))
12
- end
13
- @raw_course.has_key?(mname.to_s) ? @raw_course[mname.to_s] : super
55
+ # @return [Array] Collection of {Verso::Credential} objects
56
+ def credentials
57
+ @credentials ||= get_attr(:credentials).collect { |c| Credential.new(c) }
14
58
  end
15
59
 
16
- def task(tid)
17
- @tasks ||= {}
18
- @tasks[tid] ||= Task.new("code" => code, "edition" => edition, "id" => tid)
60
+ # @return [Verso::TaskList] TaskList is a collection of {Verso::DutyArea}
61
+ # objects.
62
+ def duty_areas
63
+ @duty_areas ||= TaskList.new(:code => code, :edition => edition)
64
+ end
65
+ alias tasklist duty_areas
66
+
67
+ # @return [Verso::ExtrasList]
68
+ def extras
69
+ @extras ||= if related_resources.include?("extras")
70
+ ExtrasList.new(:code => code, :edition => edition)
71
+ else
72
+ []
73
+ end
19
74
  end
20
75
 
76
+ # @return [Verso:Frontmatter]
21
77
  def frontmatter
22
78
  @frontmatter ||= if self.related_resources.include?("frontmatter")
23
79
  Frontmatter.new("code" => code, "edition" => edition)
@@ -26,59 +82,66 @@ module Verso
26
82
  end
27
83
  end
28
84
 
29
- def duty_areas
30
- @duty_areas ||= TaskList.new(:code => code, :edition => edition)
31
- end
32
-
33
- def occupation_data
34
- @occupation_data ||= method_missing(:occupation_data).
35
- collect { |od| OccupationData.new(od) }
85
+ # @return [Boolean] Is this a middle school course?
86
+ def is_ms?
87
+ grade_levels.split.first.to_i < 9
36
88
  end
37
89
 
38
- def prerequisites
39
- @prerequisites ||= prerequisite_courses.collect { |c| Course.new(c) }
90
+ # @return [Boolean] is this a high school course?
91
+ def is_hs?
92
+ grade_levels.split.last.to_i > 8
40
93
  end
41
94
 
42
- def related_courses
43
- @related_courses ||= method_missing(:related_courses).
44
- collect { |c| Course.new(c) }
95
+ # @return [Array] Colection of license {Verso::Credential} objects
96
+ def licenses
97
+ @licenses ||= credentials.select { |c| c.type == "License" }
45
98
  end
46
99
 
47
- def credentials
48
- @credentials ||= method_missing(:credentials).
49
- collect { |c| Credential.new(c) }
100
+ # @return [Array] Collection of prerequisite {Verso::Course} objects
101
+ def prerequisite_courses
102
+ @prerequisites ||= get_attr(:prerequisite_courses).
103
+ collect { |c| Course.new(c) }
50
104
  end
105
+ alias prerequisites prerequisite_courses
51
106
 
52
- def certifications
53
- @certfications ||= credentials.select { |c| c.type == "Certification" }
107
+ # @return [Array] Collection of {Verso::OccupationData} objects
108
+ def occupation_data
109
+ @occupation_data ||= get_attr(:occupation_data).
110
+ collect { |od| OccupationData.new(od) }
54
111
  end
55
112
 
56
- def licenses
57
- @licenses ||= credentials.select { |c| c.type == "License" }
113
+ # @return [Array] Collection of related {Verso::Course} objects
114
+ # forming sequences with this course.
115
+ def related_courses
116
+ @related_courses ||= get_attr(:related_courses).
117
+ collect { |c| Course.new(c) }
58
118
  end
59
119
 
120
+ # @return [Verso::StandardsList] Standards bodies correlated to this
121
+ # course's tasks.
60
122
  def standards
61
123
  @standards ||= if self.related_resources.include?("standards")
62
124
  StandardsList.from_course(self)
63
125
  else
64
- StandardsList.new([], self)
126
+ StandardsList.new(:code => code, :edition => edition,
127
+ :standards => [])
65
128
  end
66
129
  end
67
130
 
68
- def is_ms?
69
- grade_levels.split.first.to_i < 9
131
+ # Fetch a complete task given a task id.
132
+ #
133
+ # @param [Fixnum] tid The task id
134
+ # @return [Verso::Task]
135
+ def task(tid)
136
+ @tasks ||= {}
137
+ @tasks[tid] ||= Task.new("code" => code, "edition" => edition,
138
+ "id" => tid)
70
139
  end
71
140
 
72
- def is_hs?
73
- grade_levels.split.last.to_i > 8
74
- end
141
+ private
75
142
 
76
- def extras
77
- @extras ||= if related_resources.include?("extras")
78
- ExtrasList.new(:code => code, :edition => edition)
79
- else
80
- []
81
- end
143
+ def path
144
+ "/courses/#{code},#{edition}"
82
145
  end
83
146
  end
84
147
  end