Skip to content

Commit

Permalink
cleaned up code
Browse files Browse the repository at this point in the history
  • Loading branch information
mattparmett committed Oct 22, 2012
1 parent e05ab8f commit 0280e20
Show file tree
Hide file tree
Showing 5 changed files with 54 additions and 100 deletions.
45 changes: 25 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,21 @@ pcr-ruby is a simple, intuitive way to retrieve course data from the Penn Course

## How to use pcr-ruby #

**NOTE: As of gem version 0.1, these instructions are out of date. The readme will be updated as soon as possible to reflect the changes. (The code has been cleaned up significantly and made easier to use with custom tokens; stay tuned.)**

*This section will change a lot as pcr-ruby is developed. As such, this section may not be fully accurate, but I will try to keep the instructions as current as possible.*
*This section may change a lot as pcr-ruby is developed. As such, this section may not be fully accurate, but I will try to keep the instructions as current as possible.*

pcr-ruby follows the structure of the PCR API, with a few name changes to make object identities and roles clearer in your code. (Before using pcr-ruby, you should most definitely read the PCR API documentation, the link to which you should recieve upon being granted your API token.)

The PCR API essentially consists of four types of objects: 'Courses', 'Sections', 'Instructors', and 'Course Histories'. pcr-ruby aims to provide intuitive access to the data contained in these four object types while abstracting you and your user from background processing and unnecessary data. To that end, pcr-ruby (thus far) consists of two types of objects: 'Courses' and 'Sections' ('Instructors' coming soon).
The PCR API essentially consists of four types of objects: 'Courses', 'Sections', 'Instructors', and 'Course Histories'. pcr-ruby aims to provide intuitive access to the data contained in these four object types while abstracting you and your user from background processing and unnecessary data. To that end, pcr-ruby (thus far) consists of two types of objects: 'Courses', 'Sections', and 'Instructors'.

### 'Courses' in pcr-ruby ###

Course objects in the PCR API are essentially a group of that Course's Sections which were offered in a certain semester. Courses in pcr-ruby are different, and match up most directly with 'Course History' objects of the PCR API. It is my belief that when students think of a "course," they think of the entire history of the course and *not* the course offering for a specific semester. Therefore, pcr-ruby does not associate Courses with specific semesters -- rather, Courses exist across time and represent a single curriculum and course code.

To create a Course:
```ruby
course = PCR::Course.new(:course_code => "DEPT-###")
require 'pcr-ruby'
pcr = PCR.new(api_token)
course = pcr.course(course_code)
```
All other instance variables will auto-populate based on data from the PCR API.

Expand All @@ -40,7 +40,9 @@ In pcr-ruby, Sections are single offerings of a Course. Each Section is associa

To create a Section:
```ruby
section = PCR::Section.new(:instance_variable => value)
require 'pcr-ruby'
pcr = PCR.new(api_token)
section = pcr.section(id)
```
Possible instance variables available for setting in the Section initialize method are: aliases, id, name, path, semester.

Expand All @@ -53,31 +55,28 @@ Sections have the following instance variables:
* **description** -- a string containing the class description, which is written by the Section's Instructor and details the scope and characteristics of the class.
* **comments** -- a string containing PCR's comments about the Section. The comments are the most major part of the written review, and are sourced from student exit surveys.
* **ratings** -- a Hash of metrics and the ratings of the Section for each metric.
* **instructor** (to be developed) -- the Instructor object for the Section's professor.

Sections have the following instance methods:
* **reviews()** -- retrieves the Section's review data from PCR. Returns a Hash in the format {"comments" => @comments, "ratings" => @ratings}.
* **instructors** an Array of the Section's instructors.
* **reviews** -- a Hash of the Section's review data, in the format {"comments" => @comments, "ratings" => @ratings}.

### 'Instructors' in pcr-ruby ###

Instructors are arguably the most important part of PCR -- many students use PCR as a substitute for RateMyProfessor and base their course decisions on PCR professor ratings. The Instructor object represents a single professor through time. As of now, pcr-ruby allows you to retrieve both the average and most recent ratings of a professor; I may add the ability to look up ratings for specific courses/semesters taught in the future.

To create an Instructor:
```ruby
instructor = PCR::Instructor.new(:id => id [, :name => name, :path => path, :sections => sections])
require 'pcr-ruby'
pcr = PCR.new(api_token)
instructor = pcr.instructor(id)
```
(You really only need to pass the id argument, as pcr-ruby will automatically retrieve the other information from PCR.)

Insctructors have the following instance variables:
* **id** -- the Instructor's PCR id, a String in the form of "ID#-FIRST-M-LAST".
Insctructors have the following instance variables. All variables other than **id**, which is user-specified, will be filled from the PCR API:
* **id** -- the Instructor's PCR id, a String in the form of "ID#-FIRST-M-LAST". The PCR API also accepts a String in the form of "ID#".
* **name** -- the Instructor's name, a String in the form of "FIRST-M-LAST".
* **path** -- the PCR sub-path that leads to the Instructor, a String in the form of "/instructors/id".
* **sections** -- a Hash of sections taught by Instructor.
* **reviews** -- a Hash of reviews of Instructor in JSON.

Instructors have the following instance methods:
* **getInfo** -- a utility method to fill in missing info in Instructor object (used to minimize unnecessary API hits)
* **getReviews** -- a utility method to get review info for Instructor object (used to minimize unnecessary API hits)
* **average(metric)** -- returns the average value, across all Sections taught by Instructor, of "metric" as a Float. "Metric" must be a recognized rating in the PCR API. (Currently the names of these ratings are not intuitive, so I may provide plain-English access to rating names in the future.)
* **recent(metric)** -- returns the average value of "metric" for the most recent semester in which the Instructor taught as a float. (For example, if the professor taught 3 classes in the most recent semester, this would return the average of "metric" over the three classes.) "Metric" must be a recognized rating in the PCR API. (Currently the names of these ratings are not intuitive, so I may provide plain-English access to rating names in the future.)

Expand All @@ -91,32 +90,38 @@ Let's say we want to find the average course quality rating for Introduction to
```ruby
require 'pcr.rb'
course_code = "PSCI-150"
course = PCR::Course.new(:course_code => course_code)
pcr = PCR.new(API_TOKEN)
course = pcr.course(course_code)
puts course.average("rCourseQuality") #=> 3.041
```

Or, even more briefly:

```ruby
require 'pcr.rb'
puts PCR::Course.new(:course_code => "PSCI-150").average("rCourseQuality") #=> 3.041
pcr = PCR.new(API_TOKEN)
puts pcr.course("PSCI-150")course.average("rCourseQuality")
#=> 3.041
```

### Get most recent course difficulty rating ###
Finding the most recent section's course difficulty rating is just as easy:

```ruby
require 'pcr.rb'
course = PCR::Course.new(:course_code => "PSCI-150")
pcr = PCR.new(API_TOKEN)
course = pcr.course("PSCI-150")
puts course.recent("rDifficulty") #=> 2.5
```

### Get professor's average "ability to stimulate student interest" rating ###
```ruby
require 'pcr.rb'
instructor = PCR::Instructor.new(:id => "1090-LINDA-H-ZHAO")
pcr = PCR.new(API_TOKEN)
instructor = pcr.instructor("1090-LINDA-H-ZHAO")
puts instructor.average("rStimulateInterest").round(2) #=> 1.7
```

## TODO ##
* Implement stricter checks on course code arguments
* Implement search by professor last/first name rather than by ID. ID is unintuitive. Will probably need to see if I can make a lookup method, or simply pull down a database of all instructors and do a search on that database.
19 changes: 5 additions & 14 deletions lib/classes/course.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,9 @@
class Course < PCR
attr_accessor :course_code, :sections, :id, :name, :path, :reviews

def initialize(args)
#Set indifferent access for args hash
args.default_proc = proc do |h, k|
case k
when String then sym = k.to_sym; h[sym] if h.key?(sym)
when Symbol then str = k.to_s; h[str] if h.key?(str)
end
end

#Initialization actions
if args[:course_code].is_a? String and args[:course_code].isValidCourseCode?
@course_code = args[:course_code]
def initialize(course_code)
if course_code.is_a? String and course_code.isValidCourseCode?
@course_code = course_code

#Read JSON from the PCR API
api_url = @@api_endpt + "coursehistories/" + self.course_code + "/?token=" + @@token
Expand All @@ -23,7 +14,7 @@ def initialize(args)
#Create array of Section objects, containing all Sections found in the API JSON for the Course
@sections = []
json["result"]["courses"].each do |c|
@sections << Section.new(c["id"], :aliases => c["aliases"], :name => c["name"], :path => c["path"], :semester => c["semester"], :hit_api => true)
@sections << Section.new(c["id"])
end

#Set variables according to Course JSON data
Expand Down Expand Up @@ -64,7 +55,7 @@ def average(metric)
end

#Return average score as a float
return (total/n)
(total/n)

else
raise CourseError, "Invalid metric format. Metric must be a string or symbol."
Expand Down
29 changes: 8 additions & 21 deletions lib/classes/instructor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,32 +2,18 @@
class Instructor < PCR
attr_accessor :id, :name, :path, :sections, :reviews

def initialize(id, args = {})
#Set indifferent access for args
if args.length > 0
args.default_proc = proc do |h, k|
case k
when String then sym = k.to_sym; h[sym] if h.key?(sym)
when Symbol then str = k.to_s; h[str] if h.key?(str)
end
end
end

def initialize(id)
#Assign args. ID is necessary because that's how we look up Instructors in the PCR API.
if id.is_a? String
@id = id
else
raise InstructorError("Invalid Instructor ID specified.")
end

@name = args[:name].downcase.titlecase if args[:name].is_a? String
@path = args[:path] if args[:path].is_a? String
@sections = args[:sections] if args[:sections].is_a? Hash

#Hit PCR API to get missing info based on id
unless args[:hit_api] == false
self.getInfo; self.getReviews
end
self.getInfo
self.getReviews

end

#Hit the PCR API to get all missing info
Expand Down Expand Up @@ -76,7 +62,7 @@ def average(metric)
end

#Return average score as a float
return (total/n)
(total/n)

else
raise CourseError, "Invalid metric format. Metric must be a string or symbol."
Expand All @@ -97,7 +83,7 @@ def recent(metric)
self.getReviews
self.reviews.each do |review|
if section_ids.index(review["section"]["id"].to_i).nil?
s = PCR::Section.new(:id => review["section"]["id"].to_i, :hit_api => false)
s = PCR::Section.new(review["section"]["id"].to_i, false)
sections << s
section_ids << s.id
end
Expand Down Expand Up @@ -134,7 +120,8 @@ def recent(metric)
end
end

return total / num
# Return recent rating
total / num

else
raise CourseError, "Invalid metric format. Metric must be a string or symbol."
Expand Down
53 changes: 12 additions & 41 deletions lib/classes/section.rb
Original file line number Diff line number Diff line change
@@ -1,35 +1,17 @@
#Section is an individual class under the umbrella of a general Course
class Section < PCR
attr_accessor :aliases, :id, :name, :path, :semester, :description, :comments, :ratings, :instructor
attr_accessor :aliases, :id, :name, :path, :semester, :description, :comments, :ratings, :instructors, :reviews

def initialize(id, args = {})
#Set indifferent access for args
if args.length > 0
args.default_proc = proc do |h, k|
case k
when String then sym = k.to_sym; h[sym] if h.key?(sym)
when Symbol then str = k.to_s; h[str] if h.key?(str)
end
end
end
def initialize(id, hit_api = true)
# Set instance vars
@id = id

@aliases = args[:aliases] if args[:aliases].is_a? Array
@id = id if id.is_a? Integer
@name = args[:name] if args[:name].is_a? String
@path = args[:path] if args[:path].is_a? String
@semester = args[:semester] if args[:semester].is_a? String
@comments = ""
@ratings = {}
@instructor = {}
# Hit api to fill additional info
self.hit_api() unless hit_api == false

unless args[:hit_api] == false
unless args[:get_reviews] == false
self.hit_api(:get_reviews => true)
end
end
end

def hit_api(args)
def hit_api()
data = ["aliases", "name", "path", "semester", "description"]
api_url = @@api_endpt + "courses/" + self.id.to_s + "?token=" + @@token
json = JSON.parse(open(api_url).read)
Expand All @@ -49,12 +31,12 @@ def hit_api(args)
end
end

if args[:get_reviews]
self.reviews()
end
# Get review data
self.get_reviews

end

def reviews()
def get_reviews()
api_url = @@api_endpt + "courses/" + self.id.to_s + "/reviews?token=" + @@token
json = JSON.parse(open(api_url).read)
@comments = []
Expand All @@ -65,18 +47,7 @@ def reviews()
@ratings << {a["instructor"]["id"] => a["ratings"]}
@instructors << a["instructor"]
end
# @comments = json["result"]["values"][0]["comments"]
# @ratings = json["result"]["values"][0]["ratings"]
# @instructor = json["result"]["values"][0]["instructor"]

return {:comments => @comments, :ratings => @ratings}
@reviews = {"comments" => @comments, "ratings" => @ratings}
end

def after(s)
if s.is_a? Section
self.semester.compareSemester(s.semester)
elsif s.is_a? String
self.semester.compareSemester(s)
end
end
end
8 changes: 4 additions & 4 deletions lib/pcr-ruby.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,12 @@ def course(course_code)
Course.new(course_code)
end

def section(*args)
Section.new(*args)
def section(id, hit_api = true)
Section.new(id, hit_api)
end

def instructor(id, *args)
Instructor.new(id, *args)
def instructor(id)
Instructor.new(id)
end
end

Expand Down

0 comments on commit 0280e20

Please sign in to comment.