verso 0.0.2 → 0.0.3

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 (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