great_schools 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +58 -0
- data/Rakefile +6 -0
- data/great_schools.gemspec +28 -0
- data/lib/great_schools.rb +88 -0
- data/lib/great_schools/census.rb +52 -0
- data/lib/great_schools/city.rb +72 -0
- data/lib/great_schools/district.rb +29 -0
- data/lib/great_schools/error.rb +51 -0
- data/lib/great_schools/ethnicity.rb +5 -0
- data/lib/great_schools/model.rb +45 -0
- data/lib/great_schools/rank.rb +5 -0
- data/lib/great_schools/result.rb +5 -0
- data/lib/great_schools/review.rb +47 -0
- data/lib/great_schools/school.rb +177 -0
- data/lib/great_schools/score.rb +47 -0
- data/lib/great_schools/test.rb +22 -0
- data/lib/great_schools/version.rb +14 -0
- data/spec/fixtures/browse_districts.xml +35 -0
- data/spec/fixtures/browse_schools.xml +46 -0
- data/spec/fixtures/city_overview.xml +12 -0
- data/spec/fixtures/error.xml +7 -0
- data/spec/fixtures/nearby_cities.xml +25 -0
- data/spec/fixtures/nearby_schools.xml +37 -0
- data/spec/fixtures/school_census_data.xml +40 -0
- data/spec/fixtures/school_profile.xml +70 -0
- data/spec/fixtures/school_reviews.xml +21 -0
- data/spec/fixtures/school_search.xml +38 -0
- data/spec/fixtures/school_test_scores.xml +33 -0
- data/spec/great_schools/census_spec.rb +28 -0
- data/spec/great_schools/city_spec.rb +72 -0
- data/spec/great_schools/district_spec.rb +34 -0
- data/spec/great_schools/ethnicity_spec.rb +17 -0
- data/spec/great_schools/rank_spec.rb +33 -0
- data/spec/great_schools/result_spec.rb +26 -0
- data/spec/great_schools/review_spec.rb +72 -0
- data/spec/great_schools/school_spec.rb +167 -0
- data/spec/great_schools/score_spec.rb +20 -0
- data/spec/great_schools/test_spec.rb +35 -0
- data/spec/great_schools_spec.rb +14 -0
- data/spec/spec_helper.rb +18 -0
- metadata +195 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
|
3
|
+
module GreatSchools # :nodoc:
|
4
|
+
# = GreatSchools Base Model
|
5
|
+
class Model
|
6
|
+
class << self # Class methods
|
7
|
+
protected
|
8
|
+
# Makes a URL slug from the string.
|
9
|
+
#
|
10
|
+
# Replaces dashes with underscores, spaces with dashes, and URL encodes
|
11
|
+
# any special characters.
|
12
|
+
#
|
13
|
+
# ==== Examples
|
14
|
+
#
|
15
|
+
# parameterize('San Francisco') # => 'San-Francisco'
|
16
|
+
# parameterize('Cardiff-By-The-Sea') # => 'Cardiff_By_The_Sea'
|
17
|
+
def parameterize(string)
|
18
|
+
CGI.escape(string.gsub('-', '_').gsub(' ', '-'))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Base initializer, map camelCased XML attributes from the GreatSchools API
|
23
|
+
# response to the appropriately underscored attribute setters.
|
24
|
+
def initialize(attributes = {})
|
25
|
+
attributes.each do |key, value|
|
26
|
+
key = underscore(key)
|
27
|
+
send("#{key}=", value) if respond_to?("#{key}=")
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
|
33
|
+
# Makes an underscored, lowercase form from the expression in the string.
|
34
|
+
#
|
35
|
+
# ==== Examples
|
36
|
+
#
|
37
|
+
# underscore('myACRONYMString') # => 'my_acronym_string'
|
38
|
+
def underscore(word)
|
39
|
+
word = word.to_s.dup # unfreeze any frozen strings
|
40
|
+
word.gsub!(/([a-z])([A-Z])/,'\1_\2') # myACRONYMString => my_ACRONYMString
|
41
|
+
word.gsub!(/([A-Z]+)(?=[A-Z][a-z])/, '\1_\2') # my_ACRONYMString => my_ACRONYM_String
|
42
|
+
word.downcase # my_ACRONYM_String => my_acronym_string
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module GreatSchools # :nodoc:
|
2
|
+
# = GreatSchools Review
|
3
|
+
class Review < Model
|
4
|
+
attr_accessor :school_name, :school_address
|
5
|
+
attr_accessor :review_link, :rating, :submitter, :posted_date, :comments
|
6
|
+
|
7
|
+
class << self # Class methods
|
8
|
+
# Returns a list of the most recent reviews for any schools in a city.
|
9
|
+
#
|
10
|
+
# ==== Attributes
|
11
|
+
#
|
12
|
+
# * +state+ - Two letter state abbreviation
|
13
|
+
# * +city+ - Name of city
|
14
|
+
#
|
15
|
+
# ==== Options
|
16
|
+
#
|
17
|
+
# * +:cutoff_age+ - Reviews must have been published after this many days
|
18
|
+
# ago to be returned.
|
19
|
+
# * +:limit+ - Maximum number of reviews to return. This defaults to 5.
|
20
|
+
def for_city(state, city, options = {})
|
21
|
+
options[:cutoffAge] = options.delete(:cutoff_age) # TODO: make helper method to camelCase or map query attributes
|
22
|
+
|
23
|
+
response = GreatSchools::API.get("reviews/city/#{state.upcase}/#{parameterize(city)}", options.slice(:cutoffAge, :limit))
|
24
|
+
|
25
|
+
Array.wrap(response).map { |review| new(review) }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns a list of the most recent reviews for a school.
|
29
|
+
#
|
30
|
+
# ==== Attributes
|
31
|
+
#
|
32
|
+
# * +state+ - Two letter state abbreviation
|
33
|
+
# * +id+ - Numeric id of a school. This GreatSchools ID is included in
|
34
|
+
# other listing requests like +GreatSchools::School#browse+
|
35
|
+
# and +GreatSchools::School#nearby+
|
36
|
+
#
|
37
|
+
# ==== Options
|
38
|
+
#
|
39
|
+
# * +:limit+ - Maximum number of reviews to return. This defaults to 5.
|
40
|
+
def for_school(state, id, options = {})
|
41
|
+
response = GreatSchools::API.get("reviews/school/#{state.upcase}/#{id}", options.slice(:limit))
|
42
|
+
|
43
|
+
Array.wrap(response).map { |review| new(review) }
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
module GreatSchools # :nodoc:
|
2
|
+
# = GreatSchools School
|
3
|
+
class School < Model
|
4
|
+
attr_accessor :id, :name, :type, :grade_range, :enrollment, :district_id, :district, :district_nces_id, :nces_id
|
5
|
+
attr_accessor :city, :state, :address, :phone, :fax, :website, :latitude, :longitude
|
6
|
+
attr_accessor :overview_link, :ratings_link, :reviews_link, :parent_reviews, :parent_rating
|
7
|
+
|
8
|
+
alias_method :gs_id=, :id=
|
9
|
+
alias_method :lat=, :latitude=
|
10
|
+
alias_method :lon=, :longitude=
|
11
|
+
|
12
|
+
class << self # Class methods
|
13
|
+
# Returns a list of schools in a city.
|
14
|
+
#
|
15
|
+
# ==== Attributes
|
16
|
+
#
|
17
|
+
# * +state+ - Two letter state abbreviation
|
18
|
+
# * +city+ - Name of city
|
19
|
+
#
|
20
|
+
# ==== Options
|
21
|
+
#
|
22
|
+
# * +:school_types+ - Type of school(s) you wish to appear in the list:
|
23
|
+
# 'public', 'charter', and/or 'private'
|
24
|
+
# * +:level+ - Level of school you wish to appear in the list:
|
25
|
+
# 'elementary-schools', 'middle-schools', or 'high-schools'
|
26
|
+
# * +:sort+ - How to sort the results, either by name (ascending),
|
27
|
+
# by GreatSchools rating (descending), or by overall
|
28
|
+
# parent rating (descending). The default sort is name
|
29
|
+
# (ascending). When sorted by rating, schools without
|
30
|
+
# a rating will appear last. Options: 'name',
|
31
|
+
# 'gs_rating', or 'parent_rating'
|
32
|
+
# * +:limit+ - Maximum number of schools to return. This defaults
|
33
|
+
# to 200. To get all results, use -1.
|
34
|
+
# --
|
35
|
+
# TODO: handle validations
|
36
|
+
# ++
|
37
|
+
def browse(state, city, options = {})
|
38
|
+
school_types = Array.wrap(options.delete(:school_types)).join('-')
|
39
|
+
level = options.delete(:level)
|
40
|
+
|
41
|
+
url = "schools/#{state.upcase}/#{parameterize(city)}"
|
42
|
+
url << "/#{school_types}" if school_types.present?
|
43
|
+
url << "/#{level}" if level.present?
|
44
|
+
|
45
|
+
response = GreatSchools::API.get(url, options.slice(:sort, :limit))
|
46
|
+
|
47
|
+
Array.wrap(response).map { |school| new(school) }
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns a list of schools closest to the center of a city, ZIP Code or
|
51
|
+
# address.
|
52
|
+
#
|
53
|
+
# ==== Attributes
|
54
|
+
#
|
55
|
+
# * +state+ - Two letter state abbreviation
|
56
|
+
#
|
57
|
+
# ==== Options
|
58
|
+
#
|
59
|
+
# * +:zip_code+ - 5 digit zip code
|
60
|
+
# * +:city+ - Name of city
|
61
|
+
# * +:address+ - Address of location
|
62
|
+
# * +:latitude+ - Latitude of location
|
63
|
+
# * +:longitude+ - Longitude of location
|
64
|
+
# * +:school_types+ - Type of school(s) you wish to appear in the list:
|
65
|
+
# 'public', 'charter', and/or 'private'
|
66
|
+
# * +:level+ - Level of school you wish to appear in the list:
|
67
|
+
# 'elementary-schools', 'middle-schools', or 'high-schools'
|
68
|
+
# * +:minimum_schools+ - Minimum number of schools to return. When provided,
|
69
|
+
# if the initial query field yields fewer schools than
|
70
|
+
# this value, the radius will be increased in 5 mile
|
71
|
+
# increments to a maximum of 50 miles or until the
|
72
|
+
# result set has enough schools to meet this value.
|
73
|
+
# Maximum value is 200. This value must be less than
|
74
|
+
# the limit or else it is ignored.
|
75
|
+
# * +:radius+ - Miles radius to confine search to. This default to 5,
|
76
|
+
# with a maximum of 50.
|
77
|
+
# * +:limit+ - Maximum number of schools to return. This defaults to 200.
|
78
|
+
# --
|
79
|
+
# TODO: handle validations
|
80
|
+
# ++
|
81
|
+
def nearby(state, options = {})
|
82
|
+
options[:lat] = options.delete(:latitude)
|
83
|
+
options[:levelCode] = options.delete(:level)
|
84
|
+
options[:lon] = options.delete(:longitude)
|
85
|
+
options[:minimumSchools] = options.delete(:minimum_schools)
|
86
|
+
options[:schoolType] = options.delete(:school_types).try(:join, '-')
|
87
|
+
options[:state] = state
|
88
|
+
options[:zip] = options.delete(:zip_code)
|
89
|
+
|
90
|
+
options.slice!(:address, :city, :lat, :levelCode, :limit, :lon, :minimumSchools, :radius, :schoolType, :state, :zip)
|
91
|
+
|
92
|
+
response = GreatSchools::API.get('schools/nearby', options)
|
93
|
+
|
94
|
+
Array.wrap(response).map { |school| new(school) }
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns profile data for a specific school.
|
98
|
+
#
|
99
|
+
# ==== Attributes
|
100
|
+
#
|
101
|
+
# * +state+ - Two letter state abbreviation
|
102
|
+
# * +id+ - Numeric id of a school. This GreatSchools ID is included in
|
103
|
+
# other listing requests like +GreatSchools::School#browse+
|
104
|
+
# and +GreatSchools::School#nearby+
|
105
|
+
def profile(state, id)
|
106
|
+
response = GreatSchools::API.get("schools/#{state.upcase}/#{id}")
|
107
|
+
|
108
|
+
new(response)
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns a list of schools based on a search string.
|
112
|
+
#
|
113
|
+
# ==== Attributes
|
114
|
+
#
|
115
|
+
# * +state+ - Two letter state abbreviation
|
116
|
+
# * +query+ - Search query string
|
117
|
+
#
|
118
|
+
# ==== Options
|
119
|
+
#
|
120
|
+
# * +:level+ - Level of school you wish to appear in the list:
|
121
|
+
# 'elementary-schools', 'middle-schools', or 'high-schools'
|
122
|
+
# * +:sort+ - This call by default sorts the results by relevance. You
|
123
|
+
# can sort the results alphabetically by using 'alpha'.
|
124
|
+
# * +:limit+ - Maximum number of schools to return. This defaults to
|
125
|
+
# 200 and must be at least 1.
|
126
|
+
# --
|
127
|
+
# TODO: handle validations
|
128
|
+
# ++
|
129
|
+
def search(state, query, options = {})
|
130
|
+
options[:levelCode] = options.delete(:level)
|
131
|
+
options[:q] = query
|
132
|
+
options[:state] = state
|
133
|
+
options.slice!(:state, :q, :levelCode, :sort, :limit)
|
134
|
+
|
135
|
+
response = GreatSchools::API.get('search/schools', options)
|
136
|
+
|
137
|
+
Array.wrap(response).map { |school| new(school) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# Returns census and profile data for the school.
|
142
|
+
def census
|
143
|
+
GreatSchools::Census.for_school(state, id)
|
144
|
+
end
|
145
|
+
|
146
|
+
# Set an array of +GreatSchools::Review+.
|
147
|
+
#
|
148
|
+
# ==== Attributes
|
149
|
+
#
|
150
|
+
# * +params+ - a +Hash+ or +Array+ of +GreatSchools::Review+ attributes.
|
151
|
+
def parent_reviews=(params)
|
152
|
+
@parent_reviews = []
|
153
|
+
|
154
|
+
params = params['review'] if params.is_a?(Hash) && params.key?('review')
|
155
|
+
|
156
|
+
Array.wrap(params).each do |hash|
|
157
|
+
@parent_reviews << GreatSchools::Review.new(hash)
|
158
|
+
end
|
159
|
+
|
160
|
+
@parent_reviews
|
161
|
+
end
|
162
|
+
|
163
|
+
# Returns a list of the most recent reviews for the school.
|
164
|
+
#
|
165
|
+
# ==== Options
|
166
|
+
#
|
167
|
+
# * +:limit+ - Maximum number of reviews to return. This defaults to 5.
|
168
|
+
def reviews(options = {})
|
169
|
+
GreatSchools::Review.for_school(state, id, options.slice(:limit))
|
170
|
+
end
|
171
|
+
|
172
|
+
# Returns test and rank data for the school.
|
173
|
+
def score
|
174
|
+
GreatSchools::Score.for_school(state, id)
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module GreatSchools # :nodoc:
|
2
|
+
# = GreatSchools Score
|
3
|
+
class Score < Model
|
4
|
+
attr_accessor :school_name, :rank, :tests
|
5
|
+
|
6
|
+
class << self # Class methods
|
7
|
+
# Returns test and rank data for a specific school.
|
8
|
+
#
|
9
|
+
# ==== Attributes
|
10
|
+
#
|
11
|
+
# * +state+ - Two letter state abbreviation
|
12
|
+
# * +id+ - Numeric id of a school. This GreatSchools ID is included in
|
13
|
+
# other listing requests like +GreatSchools::School#browse+
|
14
|
+
# and +GreatSchools::School#nearby+
|
15
|
+
def for_school(state, id)
|
16
|
+
response = GreatSchools::API.get("school/tests/#{state.upcase}/#{id}")
|
17
|
+
|
18
|
+
new(response)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Set the +GreatSchools::Rank+.
|
23
|
+
#
|
24
|
+
# ==== Attributes
|
25
|
+
#
|
26
|
+
# * +params+ - a +Hash+ of +GreatSchools::Rank+ attributes.
|
27
|
+
def rank=(params)
|
28
|
+
@rank = GreatSchools::Rank.new(params)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Set an array of +GreatSchools::Test+.
|
32
|
+
#
|
33
|
+
# ==== Attributes
|
34
|
+
#
|
35
|
+
# * +params+ - a +Hash+ or +Array+ of +GreatSchools::Test+ attributes.
|
36
|
+
def tests=(params)
|
37
|
+
@tests = []
|
38
|
+
|
39
|
+
Array.wrap(params).each do |hash|
|
40
|
+
@tests << GreatSchools::Test.new(hash)
|
41
|
+
end
|
42
|
+
|
43
|
+
@tests
|
44
|
+
end
|
45
|
+
alias_method :test=, :tests=
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GreatSchools # :nodoc:
|
2
|
+
# = GreatSchools Test
|
3
|
+
class Test < Model
|
4
|
+
attr_accessor :name, :id, :description, :abbreviation, :scale, :level_code, :results
|
5
|
+
|
6
|
+
# Set an array of +GreatSchools::Result+.
|
7
|
+
#
|
8
|
+
# ==== Attributes
|
9
|
+
#
|
10
|
+
# * +params+ - a +Hash+ or +Array+ of +GreatSchools::Result+ attributes.
|
11
|
+
def results=(params)
|
12
|
+
@results = []
|
13
|
+
|
14
|
+
Array.wrap(params).each do |hash|
|
15
|
+
@results << GreatSchools::Result.new(hash)
|
16
|
+
end
|
17
|
+
|
18
|
+
@results
|
19
|
+
end
|
20
|
+
alias_method :test_result=, :results=
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module GreatSchools # :nodoc:
|
2
|
+
class Version # :nodoc:
|
3
|
+
MAJOR = 0 # version when you make incompatible API changes
|
4
|
+
MINOR = 1 # version when you add functionality in a backwards-compatible manner
|
5
|
+
PATCH = 0 # version when you make backwards-compatible bug fixes
|
6
|
+
|
7
|
+
class << self # Class methods
|
8
|
+
# MAJOR.MINOR.PATCH per Semantic Versioning 2.0.0
|
9
|
+
def to_s
|
10
|
+
"#{MAJOR}.#{MINOR}.#{PATCH}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
2
|
+
<districts>
|
3
|
+
<district>
|
4
|
+
<name>San Francisco Unified School District</name>
|
5
|
+
<ncesCode>0634410</ncesCode>
|
6
|
+
<districtRating>6</districtRating>
|
7
|
+
<address>555 Franklin St., San Francisco, CA 94102</address>
|
8
|
+
<phone>(415) 241-6000</phone>
|
9
|
+
<fax>(415) 241-6012</fax>
|
10
|
+
<website>http://www.sfusd.k12.ca.us</website>
|
11
|
+
<gradeRange>K-12 & ungraded</gradeRange>
|
12
|
+
<totalSchools>121</totalSchools>
|
13
|
+
<elementarySchools>84</elementarySchools>
|
14
|
+
<middleSchools>36</middleSchools>
|
15
|
+
<highSchools>34</highSchools>
|
16
|
+
<publicSchools>113</publicSchools>
|
17
|
+
<charterSchools>8</charterSchools>
|
18
|
+
</district>
|
19
|
+
<district>
|
20
|
+
<name>San Francisco County Office of Education</name>
|
21
|
+
<ncesCode>0691111</ncesCode>
|
22
|
+
<districtRating>1</districtRating>
|
23
|
+
<address>555 Franklin St., San Francisco, CA 94102</address>
|
24
|
+
<phone>(415) 241-6000</phone>
|
25
|
+
<fax>(415) 241-6012</fax>
|
26
|
+
<website>http://www.sfusd.k12.ca.us</website>
|
27
|
+
<gradeRange>K-12</gradeRange>
|
28
|
+
<totalSchools>4</totalSchools>
|
29
|
+
<elementarySchools>2</elementarySchools>
|
30
|
+
<middleSchools>3</middleSchools>
|
31
|
+
<highSchools>4</highSchools>
|
32
|
+
<publicSchools>4</publicSchools>
|
33
|
+
<charterSchools>0</charterSchools>
|
34
|
+
</district>
|
35
|
+
</districts>
|
@@ -0,0 +1,46 @@
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
2
|
+
<schools>
|
3
|
+
<school>
|
4
|
+
<gsId>13978</gsId>
|
5
|
+
<name>Alder Creek Middle School</name>
|
6
|
+
<type>public</type>
|
7
|
+
<gradeRange>6-8</gradeRange>
|
8
|
+
<enrollment>598</enrollment>
|
9
|
+
<city>Truckee</city>
|
10
|
+
<state>CA</state>
|
11
|
+
<districtId>509</districtId>
|
12
|
+
<district>Tahoe-Truckee Joint Unified School District</district>
|
13
|
+
<districtNCESId>0638770</districtNCESId>
|
14
|
+
<address>10931 Alder Dr., Truckee, CA 96161</address>
|
15
|
+
<phone>(530) 582-2750</phone>
|
16
|
+
<fax>(530) 582-7640</fax>
|
17
|
+
<website>http://www.ttusd.org</website>
|
18
|
+
<ncesId>063877011005</ncesId>
|
19
|
+
<lat>39.3454</lat>
|
20
|
+
<lon>-120.1735</lon>
|
21
|
+
<overviewLink>http://www.greatschools.org/modperl/browse_school/ca/13978?s_cid=gsapi</overviewLink>
|
22
|
+
<ratingsLink>http://www.greatschools.org/school/rating.page?state=CA&id=13978&s_cid=gsapi</ratingsLink>
|
23
|
+
<reviewsLink>http://www.greatschools.org/school/parentReviews.page?state=CA&id=13978&s_cid=gsapi</reviewsLink>
|
24
|
+
</school>
|
25
|
+
<school>
|
26
|
+
<gsId>11039</gsId>
|
27
|
+
<name>Cold Stream Alternative School</name>
|
28
|
+
<type>public</type>
|
29
|
+
<gradeRange>9-12</gradeRange>
|
30
|
+
<enrollment>47</enrollment>
|
31
|
+
<city>Truckee</city>
|
32
|
+
<state>CA</state>
|
33
|
+
<districtId>509</districtId>
|
34
|
+
<district>Tahoe-Truckee Joint Unified School District</district>
|
35
|
+
<districtNCESId>0638770</districtNCESId>
|
36
|
+
<address>11661 Donner Pass Rd., Truckee, CA 96161</address>
|
37
|
+
<phone>(530) 582-2640</phone>
|
38
|
+
<fax>(530) 582-7640</fax>
|
39
|
+
<ncesId>063877006175</ncesId>
|
40
|
+
<lat>39.3259</lat>
|
41
|
+
<lon>-120.2147</lon>
|
42
|
+
<overviewLink>http://www.greatschools.org/modperl/browse_school/ca/11039?s_cid=gsapi</overviewLink>
|
43
|
+
<ratingsLink>http://www.greatschools.org/school/rating.page?state=CA&id=11039&s_cid=gsapi</ratingsLink>
|
44
|
+
<reviewsLink>http://www.greatschools.org/school/parentReviews.page?state=CA&id=11039&s_cid=gsapi</reviewsLink>
|
45
|
+
</school>
|
46
|
+
</schools>
|