From 47d1b808b8dfdba130ba6673fb890236beb57a9b Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 20:04:15 -0400 Subject: [PATCH 01/17] Replace MongoDB with Postgres This commit outlines all the changes that were necessary to make the app work with Postgres and ActiveRecord. --- .gitignore | 3 + Gemfile | 15 +- Gemfile.lock | 39 +-- README.md | 51 ++-- app/api/api.rb | 15 +- app/api/entities.rb | 17 +- app/api/ohana.rb | 82 +----- app/models/address.rb | 27 +- app/models/api_application.rb | 15 +- app/models/category.rb | 20 +- app/models/contact.rb | 22 +- app/models/fax.rb | 18 ++ app/models/location.rb | 262 ++++++----------- app/models/mail_address.rb | 23 +- app/models/organization.rb | 27 +- app/models/phone.rb | 22 ++ app/models/schedule.rb | 27 -- app/models/service.rb | 40 +-- app/models/user.rb | 39 +-- app/validators/hash_validator.rb | 32 -- config/application.rb | 2 +- config/initializers/devise.rb | 2 +- config/initializers/garner.rb | 7 - config/initializers/inflections.rb | 12 +- config/initializers/secret_token.rb | 2 +- config/mongoid.yml | 96 ------ config/routes.rb | 1 + data/sample_data.json | 14 +- db/migrate/20140328034023_create_addresses.rb | 14 + .../20140328034531_create_organizations.rb | 12 + db/migrate/20140328034754_create_locations.rb | 26 ++ db/migrate/20140328035528_create_users.rb | 42 +++ .../20140328041648_create_api_applications.rb | 15 + db/migrate/20140328041859_create_contacts.rb | 16 + db/migrate/20140328042108_create_faxes.rb | 12 + .../20140328042218_create_mail_addresses.rb | 15 + db/migrate/20140328042359_create_phones.rb | 14 + db/migrate/20140328043104_create_services.rb | 22 ++ .../20140328044447_create_categories.rb | 12 + ...20140328052427_create_friendly_id_slugs.rb | 18 ++ ...140402222453_create_categories_services.rb | 10 + ...20140404220233_add_ancestry_to_category.rb | 6 + db/schema.rb | 199 +++++++++++++ lib/tasks/setup_db.rake | 33 +-- script/drop | 7 - script/reset | 13 + script/setup_db | 13 +- script/setup_heroku | 6 +- script/setup_prod_db | 11 +- script/test | 8 + spec/api/categories_spec.rb | 15 +- spec/api/link_headers_spec.rb | 2 +- spec/api/locations_spec.rb | 276 +++++++++++------- spec/api/organizations_spec.rb | 12 +- spec/api/search_spec.rb | 43 +-- spec/api/services_spec.rb | 13 +- spec/factories/addresses.rb | 31 ++ spec/factories/categories.rb | 8 + spec/factories/contacts.rb | 6 - spec/factories/faxes.rb | 12 + spec/factories/locations.rb | 75 +---- spec/factories/mail_addresses.rb | 6 + spec/factories/phones.rb | 15 + spec/factories/services.rb | 4 - spec/factories/users.rb | 16 +- spec/models/address_spec.rb | 4 +- spec/models/api_application_spec.rb | 16 +- spec/models/category_spec.rb | 12 + spec/models/fax_spec.rb | 31 ++ spec/models/location_spec.rb | 30 +- spec/models/phone_spec.rb | 51 ++++ spec/models/service_spec.rb | 59 ++++ spec/models/user_spec.rb | 25 +- spec/spec_helper.rb | 9 +- spec/support/features/session_helpers.rb | 7 + spec/support/mongoid.rb | 3 - 76 files changed, 1290 insertions(+), 947 deletions(-) create mode 100644 app/models/fax.rb create mode 100644 app/models/phone.rb delete mode 100644 app/models/schedule.rb delete mode 100644 app/validators/hash_validator.rb delete mode 100644 config/mongoid.yml create mode 100644 db/migrate/20140328034023_create_addresses.rb create mode 100644 db/migrate/20140328034531_create_organizations.rb create mode 100644 db/migrate/20140328034754_create_locations.rb create mode 100644 db/migrate/20140328035528_create_users.rb create mode 100644 db/migrate/20140328041648_create_api_applications.rb create mode 100644 db/migrate/20140328041859_create_contacts.rb create mode 100644 db/migrate/20140328042108_create_faxes.rb create mode 100644 db/migrate/20140328042218_create_mail_addresses.rb create mode 100644 db/migrate/20140328042359_create_phones.rb create mode 100644 db/migrate/20140328043104_create_services.rb create mode 100644 db/migrate/20140328044447_create_categories.rb create mode 100644 db/migrate/20140328052427_create_friendly_id_slugs.rb create mode 100644 db/migrate/20140402222453_create_categories_services.rb create mode 100644 db/migrate/20140404220233_add_ancestry_to_category.rb create mode 100644 db/schema.rb delete mode 100755 script/drop create mode 100755 script/reset mode change 100755 => 100644 script/setup_prod_db create mode 100755 script/test create mode 100644 spec/factories/addresses.rb create mode 100644 spec/factories/categories.rb create mode 100644 spec/factories/faxes.rb create mode 100644 spec/factories/phones.rb create mode 100644 spec/models/category_spec.rb create mode 100644 spec/models/fax_spec.rb create mode 100644 spec/models/phone_spec.rb create mode 100644 spec/models/service_spec.rb delete mode 100644 spec/support/mongoid.rb diff --git a/.gitignore b/.gitignore index 7ace89f76..0554c2cfd 100644 --- a/.gitignore +++ b/.gitignore @@ -46,3 +46,6 @@ dump.rdb script/backup_mongo script/import_to_staging + +# Zeus +.zeus.sock \ No newline at end of file diff --git a/Gemfile b/Gemfile index df94b774c..fd8c8b14e 100644 --- a/Gemfile +++ b/Gemfile @@ -3,6 +3,8 @@ source 'https://rubygems.org' ruby '2.1.1' gem 'rails', '3.2.17' +gem "pg" + group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' @@ -24,20 +26,15 @@ end # Test coverage gem 'coveralls', require: false -# MongoDB ORM -gem "mongoid", ">= 3.1.2" - group :test, :development do - # Testing with Rspec gem "rspec-rails", ">= 2.12.2" gem "factory_girl_rails", ">= 4.2.0" end group :test do - # Testing with Rspec and Mongoid gem "database_cleaner", ">= 1.0.0.RC1" - gem "mongoid-rspec", ">= 1.7.0" gem "capybara" + gem 'shoulda-matchers' end group :development do @@ -98,8 +95,6 @@ gem "figaro" gem "tire", :git => "git://github.com/monfresh/tire.git", :ref => "2d174e792a" # Nested categories for OpenEligibility -gem "glebtv-mongoid_nested_set" - -gem 'mongoid_time_field' +gem "ancestry" -gem 'mongoid_slug' +gem "friendly_id", "~> 4.0.10" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index f392b8e1a..dff9a8c4e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -67,6 +67,8 @@ GEM activesupport (3.2.17) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) + ancestry (2.0.0) + activerecord (>= 3.0.0) ansi (1.4.3) arel (3.0.3) arrayfields (4.9.2) @@ -155,8 +157,8 @@ GEM flog (4.2.0) ruby_parser (~> 3.1, > 3.1.0) sexp_processor (~> 4.4) - glebtv-mongoid_nested_set (0.4.2) - mongoid (>= 3.1.0, < 4.1) + friendly_id (4.0.10.1) + activerecord (>= 3.0, < 4.0) grape (0.6.1) activesupport builder @@ -219,21 +221,6 @@ GEM metric_fu-Saikuro (1.1.3) mime-types (1.25.1) mini_portile (0.5.2) - mongoid (3.1.6) - activemodel (~> 3.2) - moped (~> 1.4) - origin (~> 1.0) - tzinfo (~> 0.3.29) - mongoid-rspec (1.11.0) - mongoid (~> 3.1.6) - rake - rspec (>= 2.14) - mongoid_slug (3.2.0) - mongoid (> 3.0) - stringex (~> 2.0) - mongoid_time_field (0.3.2) - mongoid (>= 3.0.0) - moped (1.5.2) multi_json (1.9.2) multi_xml (0.5.5) newrelic-grape (1.3.1) @@ -242,13 +229,13 @@ GEM newrelic_rpm (3.7.3.204) nokogiri (1.6.1) mini_portile (~> 0.5.0) - origin (1.1.0) orm_adapter (0.5.0) parallel (1.0.0) passenger (4.0.40) daemon_controller (>= 1.2.0) rack rake (>= 0.8.1) + pg (0.17.1) polyglot (0.3.4) pry (0.9.12.6) coderay (~> 1.0) @@ -312,10 +299,6 @@ GEM mime-types (>= 1.16) roodi (3.3.1) ruby_parser (~> 3.2, >= 3.2.2) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) rspec-core (2.14.8) rspec-expectations (2.14.5) diff-lcs (>= 1.1.3, < 2.0) @@ -340,6 +323,8 @@ GEM sass (>= 3.1.10) tilt (~> 1.3) sexp_processor (4.4.2) + shoulda-matchers (2.5.0) + activesupport (>= 3.0.0) simplecov (0.8.2) docile (~> 1.1.0) multi_json @@ -351,7 +336,6 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - stringex (2.1.2) swagger-ui_rails (0.1.7) term-ansicolor (1.2.2) tins (~> 0.8) @@ -383,6 +367,7 @@ PLATFORMS ruby DEPENDENCIES + ancestry attribute_normalizer better_errors (>= 0.7.2) binding_of_caller (>= 0.7.1) @@ -396,8 +381,8 @@ DEPENDENCIES enumerize factory_girl_rails (>= 4.2.0) figaro + friendly_id (~> 4.0.10) geocoder! - glebtv-mongoid_nested_set grape grape-entity grape-swagger! @@ -407,13 +392,10 @@ DEPENDENCIES kgio memcachier metric_fu - mongoid (>= 3.1.2) - mongoid-rspec (>= 1.7.0) - mongoid_slug - mongoid_time_field newrelic-grape newrelic_rpm passenger + pg quiet_assets (>= 1.0.2) rack-cors rack-timeout @@ -422,6 +404,7 @@ DEPENDENCIES redis rspec-rails (>= 2.12.2) sass-rails (~> 3.2.3) + shoulda-matchers swagger-ui_rails tire! uglifier (>= 1.0.3) diff --git a/README.md b/README.md index b72fdd1d3..7ee61a956 100644 --- a/README.md +++ b/README.md @@ -12,16 +12,18 @@ We encourage third-party developers to build additional applications on top of t ## Current Status We are happy to announce that this project has been awarded a [grant from the Knight Foundation](http://www.knightfoundation.org/grants/201447979/), which means we get to keep working on it in 2014! Our primary goals this year are: simplifying the installation process, streamlining the code, reducing dependencies, and preparing the project for broader installation by a variety of organizations and governments. -One of the major changes will be replacing MongoDB and Elasticsearch with Postgres. This work will probably begin between mid-March and mid-April. The main reason is to reduce dependencies, but another important reason is that we want to upgrade the app to Rails 4, but Mongoid currently doesn't support Rails 4, and there's no specific date yet as to when that will happen. +One of the major changes (that is now complete as of early April 2014) is the replacement of MongoDB with Postgres. The main reason for that change was to reduce dependencies, but another important reason is that we want to upgrade the app to Rails 4, but Mongoid currently doesn't support Rails 4, and there's no specific date yet as to when that will happen. + +The next changes underway in April are: upgrading to Rails 4, and replacing Elasticsearch with the full-text search capabilities in Postgres. Because the project will be undergoing these major changes, we don't recommend using it for a production app just yet, but please feel free to try it out and provide feedback! ## Data Schema -If you would like to try out the current version of the project that uses MongoDB, please read the Wiki article about [Populating the Mongo DB from a JSON file](https://github.com/codeforamerica/ohana-api/wiki/Populating-the-Mongo-database-from-a-JSON-file). That article documents the current schema and data dictionary, but please note that this will be in flux as we are working with various interested parties to define a [Human Services Data Specification](https://github.com/codeforamerica/OpenReferral). +If you would like to try out the current version of the project that uses Postgres, please read the Wiki article about [Populating the Postgres DB from a JSON file](https://github.com/codeforamerica/ohana-api/wiki/Populating-the-Postgres-database-from-a-JSON-file). That article documents the current schema and data dictionary, but please note that this will be in flux as we are working with various interested parties to define a [Human Services Data Specification](https://github.com/codeforamerica/OpenReferral). ## Taxonomy -We are currently using the [Open Eligibility](http://openeligibility.org) taxonomy to assign Services to [Categories](https://github.com/codeforamerica/ohana-api/blob/master/app/models/category.rb). -Ohana API only accepts the categories defined by Open Eligibility. +By default, this project uses the [Open Eligibility](http://openeligibility.org) taxonomy to assign Services to [Categories](https://github.com/codeforamerica/ohana-api/blob/master/app/models/category.rb). +If you would like to use your own taxonomy, feel free to update this rake task to [create your own hierarchy or tree structure](https://github.com/codeforamerica/ohana-api/blob/master/lib/tasks/oe.rake). Then run `rake create_categories`. The easiest way to assign categories to a service is to use the [Ohana API Admin](https://github.com/codeforamerica/ohana-api-admin/blob/master/app/controllers/hsa_controller.rb#L183-187) interface. Here's a screenshot: @@ -49,7 +51,7 @@ You can also try it from the Rails console, mimicking how the API would do it wh * Ruby version 2.1.1 * Rails version 3.2.17 -* MongoDB with the Mongoid ORM +* Postgres * Redis * ElasticSearch * API framework: Grape @@ -69,23 +71,16 @@ Please note that the instructions below have only been tested on OS X. If you ar **Windows**: Try [RailsInstaller](http://railsinstaller.org), along with some of these [tutorials](https://www.google.com/search?q=install+rails+on+windows) if you get stuck. -#### MongoDB +#### PostgreSQL **OS X** -On OS X, the easiest way to install MongoDB (or almost any development tool) is with Homebrew: - - brew update - brew install mongodb - -Follow the Homebrew instructions for configuring MongoDB and starting it automatically every time you restart your computer. Otherwise, you can launch MongoDB manually in a separate Terminal tab or window with this command: +On OS X, the easiest way to install PostgreSQL is with [Postgres.app](http://postgresapp.com/) - mongod - -[MongoDB installation instructions using MacPorts](https://github.com/codeforamerica/ohana-api/wiki/Installing-MongoDB-with-MacPorts-on-OS-X) are available on the wiki. +If that doesn't work, try this [tutorial](http://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/). **Other** -See the Downloads page on mongodb.org for steps to install on other systems: [http://www.mongodb.org/downloads](http://www.mongodb.org/downloads) +See the Downloads page on postgresql.org for steps to install on other systems: [http://www.postgresql.org/download/](http://www.postgresql.org/download/) #### Redis @@ -159,11 +154,12 @@ To go the next page (the page parameter works for all API responses): http://localhost:8080/api/locations?page=2 +Note that the sample dataset has less than 30 locations, so the second page will be empty. + Search for organizations by keyword and/or location: http://localhost:8080/api/search?keyword=food http://localhost:8080/api/search?keyword=counseling&location=94403 - http://localhost:8080/api/search?keyword=market&location=san mateo http://localhost:8080/api/search?location=redwood city, ca Search for organizations by languages spoken at the location: @@ -240,12 +236,10 @@ We recommend these tools to interact with APIs: [HTTPie](https://github.com/jkbr/httpie) command line utility for making interactions with web services from the command line more human friendly. -### Resetting the app -If you want to wipe out the local test DB and start from scratch: - - script/drop - script/bootstrap +### Resetting the DB +If you want to wipe out the local test DB and reset it with the sample data, run this command: + script/reset ### User authentication and emails The app allows developers to sign up for an account via the home page (http://localhost:8080), but all email addresses need to be verified first. In development, the app sends email via Gmail. If you want to try this email process on your local machine, you need to configure your Gmail username and password by creating a file called `application.yml` in the config folder, and entering your info like so: @@ -257,20 +251,23 @@ The app allows developers to sign up for an account via the home page (http://lo ### Test the app -Run tests locally with this simple command: +First, run this command to make sure your local test database is up to date: + + script/test + +Then run tests locally with this simple command: rspec -For faster tests: +For faster tests (optional): gem install zeus zeus start #in a separate Terminal window or tab zeus rspec spec -To see the actual tests, browse through the [spec](https://github.com/codeforamerica/ohana-api/tree/master/spec) directory. +Read more about [Zeus](https://github.com/burke/zeus). -### Drop the database -If you ever want to start from scratch, run `script/drop`, then `script/bootstrap` to set everything up again. Do this on your local machine only, not in production! +To see the actual tests, browse through the [spec](https://github.com/codeforamerica/ohana-api/tree/master/spec) directory. ## Contributing diff --git a/app/api/api.rb b/app/api/api.rb index 8317041d9..d8f6028f0 100644 --- a/app/api/api.rb +++ b/app/api/api.rb @@ -12,7 +12,6 @@ class Root < Grape::API #Garner::Mixins::Rack # Garner.configure do |config| - # config.mongoid_identity_fields = [:_id] # config.cache = ActiveSupport::Cache::DalliStore.new(ENV["MEMCACHIER_SERVERS"], { :compress => true }) # end @@ -29,16 +28,24 @@ def valid_api_token? end - rescue_from Mongoid::Errors::DocumentNotFound do + rescue_from ActiveRecord::RecordNotFound do rack_response({ "error" => "Not Found", "message" => "The requested resource could not be found." }.to_json, 404) end - rescue_from Mongoid::Errors::Validations do |e| + rescue_from ActiveRecord::RecordInvalid do |e| + if e.record.errors.first.first == :kind + message = "Please enter a valid value for Kind" + elsif e.record.errors.first.first == :accessibility + message = "Please enter a valid value for Accessibility" + else + message = e.record.errors.first.last + end + rack_response({ - "message" => e.message + "message" => message }.to_json, 400) end diff --git a/app/api/entities.rb b/app/api/entities.rb index 3a3b8b40c..1e0c7e8a3 100644 --- a/app/api/entities.rb +++ b/app/api/entities.rb @@ -8,26 +8,28 @@ class Location < Grape::Entity kind.text end + # format_with(:slug_text) do |slugs| + # slugs.map(&:slug) if slugs.present? + # end + expose :id, :unless => lambda { |o,_| o.id.blank? } expose :accessibility, :format_with => :accessibility_text, :unless => lambda { |o,_| o.accessibility.blank? } expose :address, :using => Address::Entity, :unless => lambda { |o,_| o.address.blank? } - expose :admins, :unless => lambda { |o,_| o.admins.blank? } - expose :ask_for, :unless => lambda { |o,_| o.ask_for.blank? } + expose :admin_emails, :unless => lambda { |o,_| o.admin_emails.blank? } expose :contacts, :using => Contact::Entity, :unless => lambda { |o,_| o.contacts.blank? } expose :coordinates, :unless => lambda { |o,_| o.coordinates.blank? } expose :description, :unless => lambda { |o,_| o.description.blank? } expose :emails, :unless => lambda { |o,_| o.emails.blank? } - expose :faxes, :unless => lambda { |o,_| o.faxes.blank? } + expose :faxes, :using => Fax::Entity, :unless => lambda { |o,_| o.faxes.blank? } expose :hours, :unless => lambda { |o,_| o.hours.blank? } expose :kind, :format_with => :kind_text, :unless => lambda { |o,_| o.kind.blank? } expose :languages, :unless => lambda { |o,_| o.languages.blank? } expose :mail_address, :using => MailAddress::Entity, :unless => lambda { |o,_| o.mail_address.blank? } expose :name, :unless => lambda { |o,_| o.name.blank? } - expose :payments, :unless => lambda { |o,_| o.payments.blank? } - expose :phones, :unless => lambda { |o,_| o.phones.blank? } - expose :products, :unless => lambda { |o,_| o.products.blank? } + expose :phones, :using => Phone::Entity, :unless => lambda { |o,_| o.phones.blank? } expose :short_desc, :unless => lambda { |o,_| o.short_desc.blank? } - expose :slugs, :unless => lambda { |o,_| o.slugs.blank? } + expose :slug, :unless => lambda { |o,_| o.slug.blank? } + #expose :slugs, :format_with => :slug_text, :unless => lambda { |o,_| o.slugs.blank? } expose :transportation, :unless => lambda { |o,_| o.transportation.blank? } expose :updated_at expose :urls, :unless => lambda { |o,_| o.urls.blank? } @@ -36,5 +38,4 @@ class Location < Grape::Entity expose :services, :using => Service::Entity, :unless => lambda { |o,_| o.services.blank? } expose :organization, :using => Organization::Entity end - end diff --git a/app/api/ohana.rb b/app/api/ohana.rb index e463f66ab..d84a7caba 100644 --- a/app/api/ohana.rb +++ b/app/api/ohana.rb @@ -14,7 +14,7 @@ class API < Grape::API { "organizations_url" => "#{ENV["API_BASE_URL"]}organizations{/organization_id}", "locations_url" => "#{ENV["API_BASE_URL"]}locations{/location_id}", - "general_search_url" => "#{ENV["API_BASE_URL"]}search{?keyword,location,radius,language,kind,category,market_match}", + "general_search_url" => "#{ENV["API_BASE_URL"]}search{?keyword,location,radius,language,kind,category}", "rate_limit_url" => "#{ENV["API_BASE_URL"]}rate_limit" } end @@ -41,17 +41,12 @@ class API < Grape::API <<-NOTE # Fetching a location - You can fetch a location either by its id or by one of its slugs. - The `slugs` field is an array containing all slugs for a particular - location over time. Most locations will only have one slug, but it's - possible that a few will have their name edited at some point. Since - the API keeps track of the slug history, those locations will have - multiple slugs. + You can fetch a location either by its id or by its slug. If using the API to display a location's details on a web page that will be crawled by search engines, we recommend setting the end of the canonical URL of the location's page to the - last slug in the array. + location's slug. Example: @@ -113,7 +108,7 @@ class API < Grape::API #garner.options(expires_in: 30.minutes) do location = Location.find(params[:locations_id]) nearby = Location.nearby(location, params) - set_link_header(nearby) if location.coordinates.present? + set_link_header(nearby) nearby #end end @@ -165,12 +160,7 @@ class API < Grape::API <<-NOTE # Fetching an organization - You can fetch an organization either by its id or by one of its slugs. - The `slugs` field is an array containing all slugs for a particular - organization over time. Most organizations will only have one slug, but it's - possible that a few will have their name edited at some point. Since - the API keeps track of the slug history, those organizations will have - multiple slugs. + You can fetch an organization either by its id or by its slug. Example: @@ -246,7 +236,7 @@ class API < Grape::API params[:service_areas] = [] if params[:service_areas].blank? service.update_attributes!(params) - service + present service, with: Service::Entity end segment '/:services_id' do @@ -266,7 +256,7 @@ class API < Grape::API # For example, "Prevent & Treat" becomes "prevent-and-treat". # If you want to see all 327 slugs, run this command from the # Rails console: - # Category.all.map(&:slugs).flatten + # Category.all.map(&:slug) cat_ids = [] params[:category_slugs].each do |cat_slug| cat = Category.find(cat_slug) @@ -276,7 +266,7 @@ class API < Grape::API # Set the service's category_ids to this new array of ids s.category_ids = cat_ids s.save - s + present s, with: Service::Entity end end end @@ -429,59 +419,6 @@ class API < Grape::API `#{ENV["API_BASE_URL"]}search?kind[]=Libaries&kind[]=Parks&sort=kind&order=desc` - ### market_match (Farmers' Markets only) - - Get a list of markets that participate in the [Market Match](http://www.pcfma.com/pcfma_marketmatch.php) program. - - Examples: - - `#{ENV["API_BASE_URL"]}search?kind=market&market_match=1` (to get participants) - - `#{ENV["API_BASE_URL"]}search?kind=market&market_match=0` (to get non-participants) - - ### products, payments (Farmers' Markets only) - These two additional parameters are available for farmers' markets - to filter the markets that only accept certain types of payment and - sell certain kinds of products. - - Examples: - - `#{ENV["API_BASE_URL"]}search?products=Baked Goods` - - `#{ENV["API_BASE_URL"]}search?products=baked goods` - - `#{ENV["API_BASE_URL"]}search?payments=SFMNP` - - `#{ENV["API_BASE_URL"]}search?payments=snap` - - `#{ENV["API_BASE_URL"]}search?payments=SNAP&products=vegetables` - - Possible values for `payments`: Credit, WIC, WICcash, SFMNP, SNAP - - Possible values for `products`: - - Baked Goods - Cheese - Crafts - Flowers - Eggs - Seafood - Herbs - Vegetables - Honey - Jams - Maple - Meat - Nursery - Nuts - Plants - Poultry - Prepared Food - Soap - Trees - Wine - - ## JSON response The search results JSON includes the location's parent organization info, as well as the location's services, so you can have all the @@ -516,9 +453,6 @@ class API < Grape::API optional :language, type: String, desc: "Languages other than English spoken at the location" optional :kind, type: Array, desc: "The type of organization, such as human services, farmers' markets" optional :category, type: String, desc: "The service category based on the OpenEligibility taxonomy" - optional :market_match, type: String, desc: "To filter farmers' markets that participate in Market Match" - optional :products, type: String, desc: "To filter farmers' markets that sell certain products" - optional :payments, type: String, desc: "To filter farmers' markets that accept certain payment types" optional :page, type: Integer, default: 1 end get do diff --git a/app/models/address.rb b/app/models/address.rb index 284ead51c..9f1b605c7 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -1,28 +1,23 @@ -class Address - #include RocketPants::Cacheable - include Mongoid::Document - include Grape::Entity::DSL - - embedded_in :location - #belongs_to :location - #validates_presence_of :location +class Address < ActiveRecord::Base - normalize_attributes :street, :city, :state, :zip + attr_accessible :city, :state, :street, :zip - field :street - field :city - field :state - field :zip + belongs_to :location, touch: true - validates_presence_of :street, :city, :state, :zip + #validates_presence_of :location + validates_presence_of :street, :city, :state, :zip, + message: lambda { |x,y| "#{y[:attribute]} can't be blank" } - validates_length_of :state, :maximum => 2, :minimum => 2 + validates_length_of :state, :maximum => 2, :minimum => 2, + message: "Please enter a valid 2-letter state abbreviation" - extend ValidatesFormattingOf::ModelAdditions validates_formatting_of :zip, using: :us_zip, allow_blank: true, message: "%{value} is not a valid ZIP code" + normalize_attributes :street, :city, :state, :zip + + include Grape::Entity::DSL entity do expose :street expose :city diff --git a/app/models/api_application.rb b/app/models/api_application.rb index 987e9639c..3e1aa6b64 100644 --- a/app/models/api_application.rb +++ b/app/models/api_application.rb @@ -1,17 +1,8 @@ -class ApiApplication - include Mongoid::Document +class ApiApplication < ActiveRecord::Base before_create :generate_api_token - # Mongoid field types are String be default, - # so we can omit the type declaration - field :name - field :main_url - field :callback_url - field :api_token - - embedded_in :user - #belongs_to :user + belongs_to :user attr_accessible :name, :main_url, :callback_url @@ -26,7 +17,7 @@ class ApiApplication def generate_api_token self.api_token = loop do random_token = SecureRandom.hex - break random_token unless User.where('api_applications.api_token' => random_token).exists? + break random_token unless self.class.exists?(api_token: random_token) end end end \ No newline at end of file diff --git a/app/models/category.rb b/app/models/category.rb index 15a45d0de..f8e190773 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -1,20 +1,22 @@ -class Category - include Mongoid::Document - include Mongoid::Slug - include Grape::Entity::DSL - acts_as_nested_set +class Category < ActiveRecord::Base + has_ancestry + + extend FriendlyId + friendly_id :name, use: [:slugged, :history] - slug :name, history: true + attr_accessible :name, :oe_id - has_and_belongs_to_many :services - #has_many :services + has_and_belongs_to_many :services, uniq: true + self.include_root_in_json = false + + include Grape::Entity::DSL entity do expose :id expose :depth expose :oe_id expose :name expose :parent_id - expose :slugs + expose :slug end end \ No newline at end of file diff --git a/app/models/contact.rb b/app/models/contact.rb index 068786a5f..2ea9029bf 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -1,23 +1,12 @@ -class Contact - #include RocketPants::Cacheable - include Mongoid::Document - include Grape::Entity::DSL +class Contact < ActiveRecord::Base + attr_accessible :email, :extension, :fax, :name, :phone, :title - #belongs_to :location - embedded_in :location + belongs_to :location, touch: true normalize_attributes :name, :title, :email, :fax, :phone, :extension - field :name - field :title - field :email - field :fax - field :phone - field :extension - - validates_presence_of :name, :title, message: "can't be blank for Contact" - - extend ValidatesFormattingOf::ModelAdditions + validates_presence_of :name, :title, + message: lambda { |x,y| "#{y[:attribute]} can't be blank for Contact" } validates_formatting_of :email, with: /.+@.+\..+/i, allow_blank: true, message: "%{value} is not a valid email" @@ -30,6 +19,7 @@ class Contact allow_blank: true, message: "%{value} is not a valid US fax number" + include Grape::Entity::DSL entity do expose :id expose :name, :unless => lambda { |o,_| o.name.blank? } diff --git a/app/models/fax.rb b/app/models/fax.rb new file mode 100644 index 000000000..41b28e1e8 --- /dev/null +++ b/app/models/fax.rb @@ -0,0 +1,18 @@ +class Fax < ActiveRecord::Base + belongs_to :location, touch: true + attr_accessible :number, :department + + validates_presence_of :number + validates_formatting_of :number, :using => :us_phone, + message: "%{value} is not a valid US fax number" + + normalize_attributes :number, :department + + include Grape::Entity::DSL + entity do + expose :id + expose :number, :unless => lambda { |o,_| o.number.blank? } + expose :department, :unless => lambda { |o,_| o.department.blank? } + end + +end diff --git a/app/models/location.rb b/app/models/location.rb index 00cea6e31..f0f37b321 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -1,95 +1,78 @@ -class Location - include Mongoid::Document - include Mongoid::Timestamps - include Mongoid::Slug - extend Enumerize - - paginates_per Rails.env.test? ? 1 : 30 - - attr_accessible :accessibility, :address, :admins, :ask_for, :contacts, - :description, :emails, :faxes, :hours, :kind, :languages, - :mail_address, :name, :phones, :short_desc, :transportation, - :urls, :contacts_attributes, :mail_address_attributes, - :address_attributes, :products, :payments, :market_match, - :organization_id - - # embedded_in :organization - belongs_to :organization - validates_presence_of :organization - - # embeds_many :services - has_many :services, dependent: :destroy - #accepts_nested_attributes_for :services - - #has_many :contacts, dependent: :destroy - embeds_many :contacts - accepts_nested_attributes_for :contacts +class Location < ActiveRecord::Base + extend FriendlyId + friendly_id :name, use: [:slugged, :history] - embeds_many :schedules - accepts_nested_attributes_for :schedules - - # has_one :address - # has_one :mail_address - embeds_one :address - embeds_one :mail_address - accepts_nested_attributes_for :mail_address, :reject_if => :all_blank - accepts_nested_attributes_for :address, :reject_if => :all_blank - - normalize_attributes :description, :hours, :kind, :name, - :short_desc, :transportation, :urls - - field :accessibility + extend Enumerize + serialize :accessibility, Array + # Don't change the terms here! You can change their display + # name in config/locales/en.yml enumerize :accessibility, in: [:cd, :deaf_interpreter, :disabled_parking, :elevator, :ramp, :restroom, :tape_braille, :tty, :wheelchair, - :wheelchair_van], multiple: true - - # List of emails that should have access to edit a location's info. - # Admins can be added to a location via the Admin GUI: - # https://github.com/codeforamerica/ohana-api-admin - field :admins, type: Array + :wheelchair_van], multiple: true, scope: true - field :ask_for, type: Array - field :coordinates, type: Array - field :description - field :emails, type: Array - field :faxes, type: Array - field :hours - - field :kind # Don't change the terms here! You can change their display # name in config/locales/en.yml enumerize :kind, in: [:arts, :clinics, :education, :entertainment, :farmers_markets, :government, :human_services, :libraries, :museums, - :other, :parks, :sports, :test] + :other, :parks, :sports, :test], scope: true + + # List of admin emails that should have access to edit a location's info. + # Admin emails can be added to a location via the Admin GUI: + # https://github.com/codeforamerica/ohana-api-admin + serialize :admin_emails, Array + + serialize :emails, Array - field :languages, type: Array + serialize :languages, Array # enumerize :languages, in: [:arabic, :cantonese, :french, :german, # :mandarin, :polish, :portuguese, :russian, :spanish, :tagalog, :urdu, # :vietnamese, - # ], multiple: true + # ], multiple: true, scope: true - field :name - slug :name, history: true + serialize :urls, Array - field :phones, type: Array + attr_accessible :accessibility, :address, :admin_emails, :contacts, + :description, :emails, :faxes, :hours, :kind, :languages, + :latitude, :longitude, :mail_address, :name, :phones, + :short_desc, :transportation, :urls, :address_attributes, + :contacts_attributes, :faxes_attributes, + :mail_address_attributes, :phones_attributes, + :services_attributes, :organization_id - field :short_desc - field :transportation + belongs_to :organization, touch: true - field :urls, type: Array + has_one :address, dependent: :destroy + accepts_nested_attributes_for :address, :reject_if => :all_blank - # farmers markets - field :market_match, type: Boolean - field :products, type: Array - field :payments, type: Array + has_many :contacts, dependent: :destroy + accepts_nested_attributes_for :contacts + + has_many :faxes, dependent: :destroy + accepts_nested_attributes_for :faxes + + has_one :mail_address, dependent: :destroy + accepts_nested_attributes_for :mail_address, :reject_if => :all_blank + + has_many :phones, dependent: :destroy + accepts_nested_attributes_for :phones + + has_many :services, dependent: :destroy + after_touch() { tire.update_index } + accepts_nested_attributes_for :services + + #has_many :schedules, dependent: :destroy + #accepts_nested_attributes_for :schedules + + normalize_attributes :description, :hours, :kind, :name, + :short_desc, :transportation, :urls # This is where you define all the fields that you want to be required # when uploading a dataset or creating a new entry via an admin interface. # You can separate the required fields with a comma. For example: # validates_presence_of :name, :hours, :phones - validates_presence_of :name - validates_presence_of :description + validates_presence_of :description, :organization, :name, + message: lambda { |x,y| "#{y[:attribute]} can't be blank" } validate :address_presence ## Uncomment the line below if you want to require a short description. @@ -120,47 +103,13 @@ class Location allow_blank: true, message: "%{value} is not a valid email" } } - validates :phones, hash: { - format: { with: /\A(\((\d{3})\)|\d{3})[ |\.|\-]?(\d{3})[ |\.|\-]?(\d{4})\z/, - allow_blank: true, - message: "%{value} is not a valid US phone number" } } - - # validates :faxes, hash: { - # format: { with: /\A(\((\d{3})\)|\d{3})[ |\.|\-]?(\d{3})[ |\.|\-]?(\d{4})\z/, - # allow_blank: true, - # message: "%{value} is not a valid US fax number" } } - - after_validation :reset_coordinates, if: :address_blank? - - validate :fax_format validate :format_of_admin_email - def fax_format - if faxes.is_a?(String) - errors[:base] << "Fax must be an array of hashes with number (required) and department (optional) attributes" - elsif faxes.is_a?(Array) - faxes.each do |fax| - if !fax.is_a?(Hash) - errors[:base] << "Fax must be a hash with number (required) and department (optional) attributes" - elsif fax.is_a?(Hash) - if fax["number"].blank? - errors[:base] << "Fax hash must have a number attribute" - else - regexp = /\A(\((\d{3})\)|\d{3})[ |\.|\-]?(\d{3})[ |\.|\-]?(\d{4})\z/ - if fax["number"].match(regexp).nil? - errors[:base] << "Please enter a valid US fax number" - end - end - end - end - end - end - def format_of_admin_email regexp = /.+@.+\..+/i - if admins.present? && - (!admins.is_a?(Array) || admins.detect { |a| a.match(regexp).nil? }) - errors[:base] << "Admins must be an array of valid email addresses" + if admin_emails.present? && + (!admin_emails.is_a?(Array) || admin_emails.detect { |a| a.match(regexp).nil? }) + errors[:base] << "admin_emails must be an array of valid email addresses" end end @@ -182,15 +131,21 @@ def full_physical_address end end + def coordinates + [longitude, latitude] if longitude.present? && latitude.present? + end + + after_validation :reset_coordinates, if: :address_blank? + def address_blank? - self.address.blank? + address.blank? end def reset_coordinates - self.coordinates = nil + self.latitude = nil + self.longitude = nil end - include Geocoder::Model::Mongoid geocoded_by :full_physical_address # can also be an IP address # Only call Google's geocoding service if the address has changed @@ -199,7 +154,7 @@ def reset_coordinates def needs_geocoding? if self.address.present? - self.address.changed? || self.coordinates.nil? + self.address.changed? || latitude.nil? || longitude.nil? end end @@ -210,9 +165,13 @@ def needs_geocoding? include Tire::Model::Search include Tire::Model::Callbacks + paginates_per Rails.env.test? ? 1 : 30 + # INDEX_NAME is defined in config/initializers/tire.rb index_name INDEX_NAME + self.include_root_in_json = false + # Defines the JSON output of search results. # Since search returns locations, we also want to include # the location's parent organization info, as well as the @@ -224,16 +183,24 @@ def to_indexed_json :methods => ['url'], :include => { :services => { :except => [:location_id, :created_at], - :methods => ['categories'] }, + :include => { :categories => { :except => + [:ancestry, :created_at, :updated_at], + :methods => ['depth'] } } + }, :organization => { :methods => ['url', 'locations_url'] }, - :address => { :except => [:_id] }, - :mail_address => { :except => [:_id] }, - :contacts => { :except => [] } - }) - hash.merge!("slugs" => _slugs) if _slugs.present? + :address => { :except => [:created_at, :updated_at] }, + :mail_address => { :except => [:created_at, :updated_at] }, + :contacts => { :except => [:created_at, :updated_at] }, + :faxes => { :except => [:created_at, :updated_at] }, + :phones => { :except => [:created_at, :updated_at] } + }, + :methods => ['coordinates', 'url'] + ) + #hash.merge!("slugs" => slugs.map(&:slug)) if slugs.present? + #hash[:organization].merge!("slugs" => organization.slugs.map(&:slug)) hash.merge!("accessibility" => accessibility.map(&:text)) hash.merge!("kind" => kind.text) if kind.present? - remove_nil_fields(hash,["organization","contacts","services"]) + remove_nil_fields(hash,[:organization,:contacts,:faxes,:phones,:services]) hash.to_json end @@ -244,7 +211,7 @@ def to_indexed_json # The fields passed are the associated models that contain # nil fields that we want to get rid of. # Each field is an Array of Hashes because it's a 1-N relationship: - # Location embeds_many :contacts and has_many :services. + # Location has_many :contacts and has_many :services. # Once each associated model is cleaned up, we removed nil fields # from the main hash too. # @@ -260,17 +227,6 @@ def remove_nil_fields(obj,fields=[]) end end obj.reject! { |_,v| v.blank? } - - # remove service_ids and category_ids fields to speed up response time. - # clients don't need them. - if obj["services"].present? - obj["services"].each do |s| - s.reject! { |k,_| k == "category_ids" } - if s["categories"].present? - s["categories"].each { |c| c.reject! { |k,_| k == "service_ids" } } - end - end - end end settings :number_of_shards => 1, @@ -312,14 +268,13 @@ def remove_nil_fields(obj,fields=[]) exact: { type: "string", index: "not_analyzed" } } indexes :description, analyzer: "snowball" - indexes :products, :index => :not_analyzed indexes :kind, type: "string", analyzer: "keyword" indexes :emails, type: "multi_field", fields: { exact: { type: "string", index: "not_analyzed" }, domain: { type: "string", index_analyzer: "email_analyzer", search_analyzer: "keyword" } } - indexes :admins, index: "not_analyzed" + indexes :admin_emails, index: "not_analyzed" indexes :urls, type: "string", index_analyzer: "url_analyzer", search_analyzer: "keyword" indexes :organization do @@ -416,7 +371,7 @@ def self.search(params={}) score_mode "total" end end - match [:admins, "emails.exact"], params[:email] if params[:email].present? + match [:admin_emails, "emails.exact"], params[:email] if params[:email].present? generic_domains = %w(gmail.com aol.com sbcglobal.net hotmail.com yahoo.com co.sanmateo.ca.us smcgov.org) if params[:domain].present? && generic_domains.include?(params[:domain]) @@ -436,11 +391,6 @@ def self.search(params={}) "Government", "Libraries", "Museums", "Other", "Parks", "Sports", "Test"] } } if params[:exclude] == "market_other" filter :not, { :term => { :kind => "Other" } } if params[:exclude] == "Other" - filter :exists, field: 'market_match' if params[:market_match] == "1" - filter :missing, field: 'market_match' if params[:market_match] == "0" - filter :term, :payments => params[:payments].downcase if params[:payments].present? - filter :term, :products => params[:products].titleize if params[:products].present? - filter :not, { :term => { :kind => "Test" } } if params[:keyword] != "maceo" filter :missing, field: 'services.categories' if params[:include] == "no_cats" filter :term, "services.categories.name.exact" => params[:category] if params[:category].present? filter :term, "organization.name.exact" => params[:org_name] if params[:org_name].present? @@ -501,8 +451,12 @@ def self.nearby(loc, params={}) end else # If location has no coordinates, the search above will raise - # an execption, so we return an empty array instead. - [] + # an exception, so we return an empty results list instead. + tire.search do + query do + string "sfdadf" + end + end end end @@ -529,34 +483,4 @@ def self.smc_service_areas 'Princeton-by-the-Sea','San Gregorio','Sky Londa','West Menlo Park' ] end - - # This allows you to display helpful error messages for attributes - # of embedded objects, like contacts and address. For example, - # the address model requires the state attribute to be exactly 2 characters. - # See this line in app/models/address.rb: - # validates_length_of :state, :maximum => 2, :minimum => 2 - # Without the custom validation below, if you try to create or update and - # address with a state that has less than 2 characters, the default - # error message will be "Address is invalid", which is not specific enough. - # By adding this custom validation, we can make the error message more - # helpful: "State is too short (minimum is 2 characters)". - after_validation :handle_post_validation - def handle_post_validation - unless self.errors[:contacts].blank? - self.contacts.each do |contact| - contact.errors.each { |attr,msg| self.errors.add(attr, msg) } - end - self.errors.delete(:contacts) - end - - unless self.errors[:address].blank? - address.errors.each { |attr,msg| self.errors.add(attr, msg) } - self.errors.delete(:address) - end - - unless self.errors[:mail_address].blank? - mail_address.errors.each { |attr,msg| self.errors.add(attr, msg) } - self.errors.delete(:mail_address) - end - end end diff --git a/app/models/mail_address.rb b/app/models/mail_address.rb index fa3d92b85..a5e3283b8 100644 --- a/app/models/mail_address.rb +++ b/app/models/mail_address.rb @@ -1,29 +1,22 @@ -class MailAddress - #include RocketPants::Cacheable - include Mongoid::Document - include Grape::Entity::DSL +class MailAddress < ActiveRecord::Base + attr_accessible :attention, :city, :state, :street, :zip - embedded_in :location - #belongs_to :location + belongs_to :location, touch: true #validates_presence_of :location normalize_attributes :street, :city, :state, :zip - field :attention - field :street - field :city - field :state - field :zip - - validates_presence_of :street, :city, :state, :zip + validates_presence_of :street, :city, :state, :zip, + message: lambda { |x,y| "#{y[:attribute]} can't be blank" } - validates_length_of :state, :maximum => 2, :minimum => 2 + validates_length_of :state, :maximum => 2, :minimum => 2, + message: "Please enter a valid 2-letter state abbreviation" - extend ValidatesFormattingOf::ModelAdditions validates_formatting_of :zip, using: :us_zip, allow_blank: true, message: "%{value} is not a valid ZIP code" + include Grape::Entity::DSL entity do expose :attention, :unless => lambda { |o,_| o.attention.blank? } expose :street diff --git a/app/models/organization.rb b/app/models/organization.rb index b67aab45c..f853c3ecc 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -1,26 +1,25 @@ -class Organization - include Mongoid::Document - include Mongoid::Timestamps - include Mongoid::Slug - include Grape::Entity::DSL +class Organization < ActiveRecord::Base + attr_accessible :name, :urls + + extend FriendlyId + friendly_id :name, use: [:slugged, :history] has_many :locations, dependent: :destroy - #embeds_many :locations #accepts_nested_attributes_for :locations normalize_attributes :name - field :name - slug :name, history: true - field :urls, type: Array + serialize :urls, Array validates_presence_of :name paginates_per Rails.env.test? ? 1 : 30 + self.include_root_in_json = false + after_save :refresh_tire_index def refresh_tire_index - self.locations.each { |loc| loc.update_index } + self.locations.each { |loc| loc.tire.update_index } end def url @@ -35,10 +34,16 @@ def root_url Rails.application.routes.url_helpers.root_url end + include Grape::Entity::DSL entity do + # format_with(:slug_text) do |slugs| + # slugs.map(&:slug) if slugs.present? + # end + expose :id expose :name - expose :slugs + #expose :slugs, :format_with => :slug_text + expose :slug expose :url expose :locations_url end diff --git a/app/models/phone.rb b/app/models/phone.rb new file mode 100644 index 000000000..f5462f889 --- /dev/null +++ b/app/models/phone.rb @@ -0,0 +1,22 @@ +class Phone < ActiveRecord::Base + belongs_to :location, touch: true + + normalize_attributes :department, :extension, :number, :vanity_number + + validates_presence_of :number + + validates_formatting_of :number, :using => :us_phone, + message: "%{value} is not a valid US phone number" + + attr_accessible :department, :extension, :number, :vanity_number + + include Grape::Entity::DSL + entity do + expose :id + expose :department, :unless => lambda { |o,_| o.department.blank? } + expose :extension, :unless => lambda { |o,_| o.extension.blank? } + expose :number, :unless => lambda { |o,_| o.number.blank? } + expose :vanity_number, :unless => lambda { |o,_| o.vanity_number.blank? } + end + +end diff --git a/app/models/schedule.rb b/app/models/schedule.rb deleted file mode 100644 index 4f106b716..000000000 --- a/app/models/schedule.rb +++ /dev/null @@ -1,27 +0,0 @@ -class Schedule - include Mongoid::Document - - field :day, :type => Integer - field :open, type: TimeField.new(format: 'HH:MM') - field :close, type: TimeField.new(format: 'HH:MM') - field :holiday, type: Time - - embedded_in :location - embedded_in :service - - # A days constant - DAYS = %w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday].freeze - - def day_name - DAYS[self.day] - end - - def create_hours(params={}) - start_time = params[:start_time] ? params[:start_time] : 6.hours - end_time = params[:end_time] ? params[:end_time] : 23.hours - increment = params[:increment] ? params[:increment] : 5.minutes - Array.new(1 + (end_time - start_time)/increment) do |i| - (Time.now.midnight + (i*increment) + start_time).strftime("%H:%M") - end - end -end \ No newline at end of file diff --git a/app/models/service.rb b/app/models/service.rb index 681ce7ae2..a67933bec 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -1,20 +1,12 @@ -class Service - #include RocketPants::Cacheable - include Mongoid::Document - include Mongoid::Timestamps - include Grape::Entity::DSL - - # embedded_in :location - belongs_to :location - validates_presence_of :location - - has_and_belongs_to_many :categories - #belongs_to :category +class Service < ActiveRecord::Base + belongs_to :location, touch: true - embeds_many :schedules - accepts_nested_attributes_for :schedules + has_and_belongs_to_many :categories, uniq: true #accepts_nested_attributes_for :categories + #has_many :schedules + #accepts_nested_attributes_for :schedules + attr_accessible :audience, :description, :eligibility, :fees, :funding_sources, :keywords, :how_to_apply, :name, :service_areas, :short_desc, :urls, :wait @@ -22,18 +14,10 @@ class Service normalize_attributes :audience, :description, :eligibility, :fees, :how_to_apply, :name, :short_desc, :wait - field :audience - field :description - field :eligibility - field :fees - field :funding_sources, type: Array - field :keywords, type: Array - field :how_to_apply - field :name - field :service_areas, type: Array, default: [] - field :short_desc - field :urls, type: Array - field :wait + serialize :funding_sources, Array + serialize :keywords, Array + serialize :service_areas, Array + serialize :urls, Array validates :urls, array: { format: { with: %r{\Ahttps?://([^\s:@]+:[^\s:@]*@)?[A-Za-z\d\-]+(\.[A-Za-z\d\-]+)+\.?(:\d{1,5})?([\/?]\S*)?\z}i, @@ -99,6 +83,9 @@ def valid_service_areas ] end + self.include_root_in_json = false + + include Grape::Entity::DSL entity do expose :id expose :audience, :unless => lambda { |o,_| o.audience.blank? } @@ -108,7 +95,6 @@ def valid_service_areas expose :funding_sources, :unless => lambda { |o,_| o.funding_sources.blank? } expose :keywords, :unless => lambda { |o,_| o.keywords.blank? } expose :categories, :using => Category::Entity, :unless => lambda { |o,_| o.categories.blank? } - #expose :categories, :unless => lambda { |o,_| o.categories.blank? } expose :how_to_apply, :unless => lambda { |o,_| o.how_to_apply.blank? } expose :name, :unless => lambda { |o,_| o.name.blank? } expose :service_areas, :unless => lambda { |o,_| o.service_areas.blank? } diff --git a/app/models/user.rb b/app/models/user.rb index 1959a0ccc..938fcca83 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1,5 +1,4 @@ -class User - include Mongoid::Document +class User < ActiveRecord::Base # Include default devise modules. Others available are: # :token_authenticatable, :confirmable, # :lockable, :timeoutable and :omniauthable @@ -14,40 +13,6 @@ class User attr_accessible :name, :email, :password, :password_confirmation, :remember_me - embeds_many :api_applications - #has_many :api_applications + has_many :api_applications, :dependent => :destroy accepts_nested_attributes_for :api_applications - - ## Database authenticatable - field :email, :type => String, :default => "" - field :encrypted_password, :type => String, :default => "" - field :name, :type => String, :default => "" - - ## Recoverable - field :reset_password_token, :type => String - field :reset_password_sent_at, :type => Time - - ## Rememberable - field :remember_created_at, :type => Time - - ## Trackable - field :sign_in_count, :type => Integer, :default => 0 - field :current_sign_in_at, :type => Time - field :last_sign_in_at, :type => Time - field :current_sign_in_ip, :type => String - field :last_sign_in_ip, :type => String - - ## Confirmable - field :confirmation_token, :type => String - field :confirmed_at, :type => Time - field :confirmation_sent_at, :type => Time - field :unconfirmed_email, :type => String # Only if using reconfirmable - - ## Lockable - # field :failed_attempts, :type => Integer, :default => 0 # Only if lock strategy is :failed_attempts - # field :unlock_token, :type => String # Only if unlock strategy is :email or :both - # field :locked_at, :type => Time - - ## Token authenticatable - # field :authentication_token, :type => String end diff --git a/app/validators/hash_validator.rb b/app/validators/hash_validator.rb deleted file mode 100644 index dc36d90f2..000000000 --- a/app/validators/hash_validator.rb +++ /dev/null @@ -1,32 +0,0 @@ -# This custom validator allows you to validate Mongoid Hash fields -# Based on app/validators/array_validator.rb -# See app/models/organization.rb for usage. Here's an example: -# validates :phones, hash: { -# format: { with: /some regex/, -# message: "Please enter a valid phone" } } - -class HashValidator < ActiveModel::EachValidator - def validate_each(record, attribute, values) - if values.present? - values.each do |hash| - value = hash["number"] - options.each do |key, args| - validator_options = { attributes: attribute } - validator_options.merge!(args) if args.is_a?(Hash) - - next if value.blank? && validator_options[:allow_blank] - - validator_class_name = "#{key.to_s.camelize}Validator" - validator_class = begin - validator_class_name.constantize - rescue NameError - "ActiveModel::Validations::#{validator_class_name}".constantize - end - - validator = validator_class.new(validator_options) - validator.validate_each(record, attribute, value) - end - end - end - end -end \ No newline at end of file diff --git a/config/application.rb b/config/application.rb index 6f1db8a88..3ab73b7ca 100644 --- a/config/application.rb +++ b/config/application.rb @@ -1,7 +1,7 @@ require File.expand_path('../boot', __FILE__) # Pick the frameworks you want: -# require "active_record/railtie" +require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" require "active_resource/railtie" diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 8585bbb32..07c7547d8 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -13,7 +13,7 @@ # Load and configure the ORM. Supports :active_record (default) and # :mongoid (bson_ext recommended) by default. Other ORMs may be # available as additional gems. - require 'devise/orm/mongoid' + require 'devise/orm/active_record' # ==> Configuration for any authentication mechanism # Configure which keys are used when authenticating a user. The default is diff --git a/config/initializers/garner.rb b/config/initializers/garner.rb index 7d86a3eae..2ce496184 100644 --- a/config/initializers/garner.rb +++ b/config/initializers/garner.rb @@ -1,13 +1,6 @@ # require "garner" -# require "garner/mixins/mongoid" # require "garner/mixins/rack" -# module Mongoid -# module Document -# include Garner::Mixins::Mongoid::Document -# end -# end - # # Monkey patch to cache headers until this is built into Garner # # https://github.com/artsy/garner/issues/54 # module Garner diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 5d8d9be23..0197f32e9 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -2,12 +2,12 @@ # Add new inflection rules using the following format # (all these examples are active by default): -# ActiveSupport::Inflector.inflections do |inflect| -# inflect.plural /^(ox)$/i, '\1en' -# inflect.singular /^(ox)en/i, '\1' -# inflect.irregular 'person', 'people' -# inflect.uncountable %w( fish sheep ) -# end +ActiveSupport::Inflector.inflections do |inflect| + #inflect.plural /^(ox)$/i, '\1en' + #inflect.singular /^(ox)en/i, '\1' + inflect.irregular 'fax', 'faxes' + #inflect.uncountable %w( fish sheep ) +end # # These inflection rules are supported but not enabled by default: # ActiveSupport::Inflector.inflections do |inflect| diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 6d499b7a3..4297e8014 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -10,4 +10,4 @@ To generate it, run "rake secret", then set it with "heroku config:set SECRET_TOKEN=the_token_you_generated"' end -OhanaApi::Application.config.secret_token = ENV['SECRET_TOKEN'] || '6bb1869cc43f25b000d02cf02245a55e330d67da37f97d2c6fb8cd12af3052bab3cc0f630938f531fa35b55976026458ac987e55c103600514bf4810a1945f96' +OhanaApi::Application.config.secret_token = ENV['SECRET_TOKEN'] || '7bb1869cc43f25b000d02cf02245a55e330d67da37f97d2c6fb8cd12af3052bab3cc0f630938f531fa35b55976026458ac987e55c103600514bf4810a1945f96' diff --git a/config/mongoid.yml b/config/mongoid.yml deleted file mode 100644 index b7bbb766c..000000000 --- a/config/mongoid.yml +++ /dev/null @@ -1,96 +0,0 @@ -production: - sessions: - default: - uri: <%= ENV['MONGOLAB_URI'] %> - options: - safe: true - skip_version_check: true - -staging: - sessions: - default: - uri: <%= ENV['MONGOLAB_URI'] %> - options: - safe: true - skip_version_check: true - -development: - # Configure available database sessions. (required) - sessions: - # Defines the default session. (required) - default: - # Defines the name of the default database that Mongoid can connect to. - # (required). - database: ohana_api_development - # Provides the hosts the default session can connect to. Must be an array - # of host:port pairs. (required) - hosts: - - localhost:27017 - options: - # Change whether the session persists in safe mode by default. - # (default: false) - # safe: false - - # Change the default consistency model to :eventual or :strong. - # :eventual will send reads to secondaries, :strong sends everything - # to master. (default: :eventual) - # consistency: :eventual - - # How many times Moped should attempt to retry an operation after - # failure. (default: 30) - # max_retries: 30 - - # The time in seconds that Moped should wait before retrying an - # operation on failure. (default: 1) - # retry_interval: 1 - # Configure Mongoid specific options. (optional) - options: - # Configuration for whether or not to allow access to fields that do - # not have a field definition on the model. (default: true) - # allow_dynamic_fields: true - - # Enable the identity map, needed for eager loading. (default: false) - # identity_map_enabled: true - - # Includes the root model name in json serialization. (default: false) - # include_root_in_json: false - - # Include the _type field in serializaion. (default: false) - # include_type_for_serialization: false - - # Preload all models in development, needed when models use - # inheritance. (default: false) - # preload_models: false - - # Protect id and type from mass assignment. (default: true) - # protect_sensitive_fields: true - - # Raise an error when performing a #find and the document is not found. - # (default: true) - # raise_not_found_error: true - - # Raise an error when defining a scope with the same name as an - # existing method. (default: false) - # scope_overwrite_exception: false - - # Skip the database version check, used when connecting to a db without - # admin access. (default: false) - # skip_version_check: false - - # User Active Support's time zone in conversions. (default: true) - # use_activesupport_time_zone: true - - # Ensure all times are UTC in the app side. (default: false) - # use_utc: false -test: - sessions: - default: - database: ohana_api_test - hosts: - - localhost:27017 - options: - consistency: :strong - # In the test environment we lower the retries and retry interval to - # low amounts for fast failures. - max_retries: 1 - retry_interval: 0 diff --git a/config/routes.rb b/config/routes.rb index 2b3496cea..d7ad52c44 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,6 +1,7 @@ OhanaApi::Application.routes.draw do devise_for :users + resources :api_applications, except: :show get "api_applications/:id" => "api_applications#edit" diff --git a/data/sample_data.json b/data/sample_data.json index c5cfb89ed..8d2a59af9 100644 --- a/data/sample_data.json +++ b/data/sample_data.json @@ -1,7 +1,7 @@ -{"name":"Peninsula Family Service","locs":[{"name":"Fair Oaks Adult Activity Center","contacts":[{"name":"Susan Houston","title":"Director of Older Adult Services"},{"name":" Christina Gonzalez","title":"Center Director"}],"description":"A walk-in center for older adults that provides social services, wellness, recreational, educational and creative activities including arts and crafts, computer classes and gardening classes. Coffee and healthy breakfasts are available daily. A hot lunch is served Tuesday-Friday for persons age 60 or over and spouse. Provides case management (including in-home assessments) and bilingual information and referral about community services to persons age 60 or over on questions of housing, employment, household help, recreation and social activities, home delivered meals, health and counseling services and services to shut-ins. Health insurance and legal counseling is available by appointment. Lectures on a variety of health and fitness topics are held monthly in both English and Spanish. Provides a variety of physical fitness opportunities, including a garden club, yoga, tai chi, soul line dance and aerobics classes geared toward older adults. Also provides free monthly blood pressure screenings, quarterly blood glucose monitoring and health screenings by a visiting nurse. Offers a Brown Bag Program in which low-income seniors can receive a bag of groceries each week for a membership fee of $10 a year. Offers Spanish lessons. Formerly known as Peninsula Family Service, Fair Oaks Intergenerational Center. Formerly known as the Fair Oaks Senior Center. Formerly known as Family Service Agency of San Mateo County, Fair Oaks Intergenerational Center.","short_desc":"A multipurpose senior citizens' center serving the Redwood City area.","address":{"street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address":{"attention":"Fair Oaks Intergenerational Center","street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"ask_for":["Christina Gonzalez"],"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["cgonzalez@peninsulafamilyservice.org"],"faxes":[{"number":"650 701-0856"}],"phones":[{"number":"650 780-7525","hours":"(Monday-Friday, 8:30-5)"}],"urls":["http://www.peninsulafamilyservice.org"],"servs":[{"audience":"Older adults age 55 or over, ethnic minorities and low-income persons","eligibility":"Age 55 or over for most programs, age 60 or over for lunch program","fees":"$2.50 suggested donation for lunch for age 60 or over, donations for other services appreciated. Cash and checks accepted.","how_to_apply":"Walk in or apply by phone.","service_areas":["Redwood City"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","English Language","RECREATION/LEISURE SERVICES","Arts and Crafts","Sports/Games/Exercise","Brown Bag Food Programs","Congregate Meals/Nutrition Sites","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["City","County","Donations","Fees","Fundraising"]}]},{"name":"Second Career Employment Program","contacts":[{"name":"Brenda Brown","title":"Director, Second Career Services"}],"description":"Provides training and job placement to eligible people age 55 or over who meet certain income qualifications. An income of 125% of poverty level or less is required for subsidized employment and training. (No income requirements for job matchup program.) If a person seems likely to be qualified after a preliminary phone call or visit, he or she must complete an application at this office. Staff will locate appropriate placements for applicants, provide orientation and on-the-job training and assist with finding a job outside the program. Any county resident, regardless of income, age 55 or over has access to the program, job developers and the job bank. Also provides referrals to classroom training. Formerly known as Family Service Agency of San Mateo County, Senior Employment Services.","short_desc":"Provides training and job placement to eligible persons age 55 or over. All persons age 55 or over have access to information in the program's job bank.","address":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address":{"attention":"PFS Second Career Services","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"ask_for":["Brenda Brown"],"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Chinese (Mandarin)","Filipino (Tagalog)","Italian","Spanish"],"emails":["bbrown@peninsulafamilyservice.org"],"faxes":[{"number":"650 403-4302"}],"phones":[{"number":"650 403-4300","department":" Ext. 4385","hours":"(Monday-Friday, 9-5)"}],"urls":["http://www.peninsulafamilyservice.org"],"servs":[{"audience":"Residents of San Mateo County age 55 or over","eligibility":"Age 55 or over, county resident and willing and able to work. Income requirements vary according to program","fees":"None. Donations requested of clients who can afford it.","how_to_apply":"Apply by phone for an appointment.","service_areas":["San Mateo County"],"keywords":["EMPLOYMENT/TRAINING SERVICES","Job Development","Job Information/Placement/Referral","Job Training","Job Training Formats","Job Search/Placement","Older Adults"],"wait":"Varies.","funding_sources":["County","Federal","State"]}]},{"name":"Senior Peer Counseling","contacts":[{"name":"Howard Lader, LCSW","title":"Manager, Senior Peer Counseling"}],"description":"Offers supportive counseling services to San Mateo County residents age 55 or over. Volunteer counselors are selected and professionally trained to help their peers face the challenges and concerns of growing older. Training provides topics that include understanding depression, cultural competence, sexuality, community resources, and other subjects that are relevant to working with older adults. After completion of training, weekly supervision groups are provided for the volunteer senior peer counselors, as well as quarterly in-service training seminars. Peer counseling services are provided in English, Spanish, Chinese (Cantonese and Mandarin), Tagalog, and to the lesbian, gay, bisexual, transgender older adult community. Formerly known as Family Service Agency of San Mateo County, Senior Peer Counseling.","short_desc":"Offers supportive counseling services to older persons.","address":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address":{"attention":"PFS Senior Peer Counseling","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"ask_for":["Howard Lader at 403-4300 ext. 4322 will take referrals"],"hours":"Any time that is convenient for the client and peer counselor","transportation":"Service is provided in person's home or at a mutually agreed upon location.","accessibility":[],"languages":["Chinese (Cantonese)","Chinese (Mandarin)","Filipino (Tagalog)","Spanish"],"emails":null,"faxes":[{"number":"650 403-4303"}],"phones":[{"number":"650 403-4300","department":"English"}],"urls":null,"servs":[{"audience":"Older adults age 55 or over who can benefit from counseling","eligibility":"Resident of San Mateo County age 55 or over","fees":"None.","how_to_apply":"Phone for information (403-4300 Ext. 4322).","service_areas":["San Mateo County"],"keywords":["Geriatric Counseling","Older Adults","Gay, Lesbian, Bisexual, Transgender Individuals"],"wait":"Varies.","funding_sources":["County","Donations","Grants"]}]},{"name":"Family Visitation Center","contacts":[{"name":"Kimberly Pesavento","title":"Director of Visitation"}],"description":"Provides supervised visitation services and a neutral site for parents to carry out supervised exchanges of children in a safe manner. Therapeutic visitation and other counseling services available. Kids in the Middle is an education class for separating or divorcing parents. The goal is to enable parents to focus on their children's needs while the family is going through separation, divorce or other transition. The class explores the psychological aspects of divorce and its impact on children, builds effective communication techniques and points out areas in which outside help may be indicated. The fee is $50 for working parents, $15 for unemployed people. Classes are scheduled on Saturdays and Sundays and held at various sites throughout the county. Call 650-403-4300 ext. 4500 to register. Formerly known as Family Service Agency of San Mateo County, Family Visitation Center.","short_desc":"Provides supervised visitation services and a neutral site for parents in extremely hostile divorces to carry out supervised exchanges of children.","address":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address":{"attention":"PFS Family Visitation Center","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"ask_for":["Intake"],"hours":"Monday, 10-6; Tuesday-Friday, 10-8; Saturday, Sunday, 9:30-5:30","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 4 blocks.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":["kpesavento@peninsulafamilyservice.org"],"faxes":[{"number":"650 403-4303"}],"phones":[{"number":"650 403-4300","extensions":["4500"],"hours":"(Tuesday, Wednesday, 10-6; Thursday, Friday, 10-8; Saturday, Sunday, 9:30-5:30)"}],"urls":["http://www.peninsulafamilyservice.org"],"servs":[{"audience":"Parents, children, families with problems of custody disputes, domestic violence or substance abuse, families going through a separation or divorce","eligibility":"None","fees":"Vary according to income ($5-$90). Cash, checks and credit cards accepted.","how_to_apply":"Apply by phone.","service_areas":["San Mateo County"],"keywords":["INDIVIDUAL AND FAMILY DEVELOPMENT SERVICES","Growth and Adjustment","LEGAL AND CRIMINAL JUSTICE SERVICES","Mediation","Parental Visitation Monitoring","Divorce Counseling","Youth"],"wait":"No wait.","funding_sources":["County","Donations","Grants"]}]},{"name":"Economic Self - Sufficiency Program","contacts":[{"name":"Joe Bloom","title":"Financial Empowerment Programs Program Director"}],"description":"Provides fixed 8% short term loans to eligible applicants for the purchase of a reliable, used autmobile. Loans are up to $6,000 over 2 1/2 years (30 months). Funds must go towards the entire purchase of the automobile. Peninsula Family Service originates loans and collaborates with commercial partner banks to service the loans, helping clients build credit histories. Formerly known as Family Service Agency of San Mateo County, Ways to Work Family Loan Program.","short_desc":"Makes small loans to working families.","address":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address":{"attention":"Economic Self - Sufficiency Program","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"ask_for":["Loan Coordinator"],"hours":null,"transportation":"SAMTRANS stops within 1 block. CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Hindi","Spanish"],"emails":["waystowork@peninsulafamilyservice.org"],"faxes":[{"number":"650 403-4303"}],"phones":[{"number":"650 403-4300","extensions":["4100"]}],"urls":["http://www.peninsulafamilyservice.org"],"servs":[{"audience":"Target group: Low-income working families with children transitioning from welfare to work and poor or who do not have access to conventional credit","eligibility":"Eligibility: Low-income family with legal custody of a minor child or an involved parent of a dependent minor child. Must reside and/or work in San Mateo County. Must be working and have verifiable income and ability to pay off loan. No bankruptcies in the past two years and unable to qualify for other funding sources. Loans approved by loan committee.","fees":"$60 application fee. Cash or checks accepted.","how_to_apply":"Phone for information.","service_areas":["San Mateo County"],"keywords":["COMMUNITY SERVICES","Speakers","Automobile Loans"],"wait":null,"funding_sources":["County","Grants","State"]}]}]} -{"name":"Peninsula Volunteers","locs":[{"name":"Little House","contacts":[{"name":"Peter Olson","title":"Little House Director"},{"name":" Bart Charlow","title":"Executive Director, Peninsula Volunteers"}],"description":"A multipurpose center offering a wide variety of recreational, education and cultural activities. Lifelong learning courses cover topics such as music, art, languages, etc., are hosted at this location. Little House offers a large variety of classes including arts and crafts, jewelry, languages, current events, lapidary, woodwork, painting, and fitness courses (yoga, strength straining, tai chi). There are monthly art and cultural lectures, movie showings, and a computer center. Recreation activities include mah jong, pinochle, ballroom dancing, bridge, trips and tours. Partners with the Sequoia Adult Education Program. The Alzheimer's Cafe, open the third Tuesday of every month from 2:00 - 4:00 pm, is a place that brings together people liviing with dementia, their families, and their caregivers. Free and no registration is needed. The Little House Community Service Desk offers information and referrals regarding social service issues, such as housing, food, transportation, health insurance counseling, and estate planning. Massage, podiatry, and acupuncture are available by appointment. Lunch is served Monday-Friday, 11:30 am-1:00 pm. Prices vary according to selection.","short_desc":"A multipurpose senior citizens' center.","address":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address":{"attention":"Little House","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"ask_for":["Peter Olson"],"hours":"Monday-Thursday, 8 am-9 pm; Friday, 8-5","transportation":"SAMTRANS stops within 3 blocks, RediWheels and Menlo Park Shuttle stop at door.","accessibility":["disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["polson@peninsulavolunteers.org"],"faxes":[{"number":"650 326-9547"}],"phones":[{"number":"650 326-2025","hours":"(Monday-Thursday, 8 am-9 pm; Friday, 8-5))"}],"urls":["http://www.penvol.org/littlehouse"],"servs":[{"audience":"Any age","eligibility":"None","fees":"$55 per year membership dues. Classes have fees. Discounts are available for members. Cash, checks and credit cards accepted.","how_to_apply":"Walk in or apply by phone for membership application.","service_areas":["San Mateo County","Santa Clara County"],"keywords":["ADULT PROTECTION AND CARE SERVICES","In-Home Supportive","Meal Sites/Home-delivered Meals","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","Adult","HEALTH SERVICES","Education/Information","Family Support","Individual/Group Counseling","Screening/Immunization","RECREATION/LEISURE SERVICES","Sports/Games/Exercise","Community Adult Schools","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["Fees","Fundraising","Grants","Membership dues"]}]},{"name":"Rosener House Adult Day Services","contacts":[{"name":"Bart Charlow","title":"Executive Director, Peninsula Volunteers"},{"name":" Barbara Kalt","title":"Director"}],"description":"Rosener House is a day center for older adults who may be unable to live independently but do not require 24-hour nursing care, may be isolated and in need of a planned activity program, may need assistance with activities of daily living or are living in a family situation where the caregiver needs respite from giving full-time care. Assists elderly persons to continue to live with family or alone rather than moving to a skilled nursing facility. Activities are scheduled Monday-Friday, 10 am-2:30 pm, and participants may come two to five days per week. The facility is open from 8 am to 5:30 pm for participants who need to remain all day. Small group late afternoon activities are held from 3-5:30 pm. The program provides a noon meal including special diets as required. Services offered include social and therapeutic recreational activities, individual and family counseling and occupational, physical and speech therapy. A registered nurse is available daily. The Dementia and Alzheimer's Services Program provides specialized activities in a supportive environment for participants with Alzheimer's disease and other dementias. Holds a weekly support group for caregivers. An early memory loss class for independent adults, \"Minds in Motion\" meets weekly at Rosener House on Wednesday mornings. Call for more information.","short_desc":"A day center for adults age 50 or over.","address":{"street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"mail_address":{"attention":"Rosener House","street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"ask_for":["Florence Marchick (Social Worker)"],"hours":"Monday-Friday, 8-5:30","transportation":"Transportation can be arranged via Redi-Wheels or Outreach.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish","Filipino (Tagalog)","Vietnamese"],"emails":["bkalt@peninsulavolunteers.org","fmarchick@peninsulavolunteers.org"],"faxes":[{"number":"650 322-4067"}],"phones":[{"number":"650 322-0126","hours":"(Monday-Friday, 8-5:30)"}],"urls":["http://www.penvol.org/rosenerhouse"],"servs":[{"audience":"Older adults who have memory or sensory loss, mobility limitations and may be lonely and in need of socialization","eligibility":"Age 18 or over","fees":"$85 per day. Vary according to income for those unable to pay full fee. Cash, checks, credit cards, private insurance and vouchers accepted.","how_to_apply":"Apply by phone or be referred by a doctor, social worker or other professional. All prospective participants are interviewed individually before starting the program. A recent physical examination is required, including a TB test.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Los Altos","Los Altos Hills","Menlo Park","Mountain View","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Sunnyvale","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Adult Day Health Care","Dementia Management","Adult Day Programs","Older Adults"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"Meals on Wheels - South County","contacts":[{"name":"Marilyn Baker-Venturini","title":"Director"},{"name":" Graciela Hernandez","title":"Assistant Manager"},{"name":" Julie Avelino","title":"Assessment Specialist"}],"description":"Delivers a hot meal to the home of persons age 60 or over who are primarily homebound and unable to prepare their own meals, and have no one to prepare meals. Also, delivers a hot meal to the home of disabled individuals ages 18-59. Meals are delivered between 9 am-1:30 pm, Monday-Friday. Special diets are accommodated: low fat, low sodium, and low sugar.","short_desc":"Will deliver a hot meal to the home of persons age 60 or over who are homebound and unable to prepare their own meals. Can provide special diets.","address":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address":{"attention":"Meals on Wheels - South County","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"ask_for":["Julie Arelino"],"hours":"Delivery times: Monday-Friday, 9-1:30","transportation":"Not necessary for service.","accessibility":null,"languages":["Spanish"],"emails":["mbaker-venturini@peninsulavolunteers.org"],"faxes":[{"number":"650 326-9547"}],"phones":[{"number":"650 323-2022","hours":"(Monday-Friday, 8-2)"}],"urls":["http://www.peninsulavolunteers.org"],"servs":[{"audience":"Senior citizens age 60 or over, disabled individuals age 18-59","eligibility":"Homebound person unable to cook or shop","fees":"Suggested donation of $4.25 per meal for seniors 60 or over. Mandatory charge of $2 per meal for disabled individuals ages 18-59.","how_to_apply":"Apply by phone.","service_areas":["Atherton","Belmont","East Palo Alto","Menlo Park","Portola Valley","Redwood City","San Carlos","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","HEALTH SERVICES","Nutrition","Home Delivered Meals","Older Adults","Disabilities Issues"],"wait":"No wait.","funding_sources":["County","Donations"]}]}]} -{"name":"Redwood City Public Library","locs":[{"name":"Fair Oaks Branch","contacts":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Divisions Manager"}],"description":"Provides general reading material, including bilingual, multi-cultural books, CDs and cassettes, bilingual and Spanish language reference services. School, class and other group visits may be arranged by appointment. The library is a member of the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address":{"street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address":{"attention":"Fair Oaks Branch","street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"ask_for":[],"hours":"Monday-Thursday, 10-7; Friday, 10-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","wheelchair"],"languages":["Spanish"],"emails":null,"faxes":[{"number":"650 569-3371"}],"phones":[{"number":"650 780-7261","hours":"(Monday-Thursday, 10-7; Friday, 10-5)"}],"urls":null,"servs":[{"audience":"Ethnic minorities, especially Spanish speaking","eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of residency in California required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City","County"]}]},{"name":"Main Library","contacts":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Division Manager"}],"description":"Provides general reading and media materials, literacy and homework assistance, and programs for all ages. Provides public computers, wireless connectivity, a children's room, teen center, and a local history collection. The library is a member of the Peninsula Library System. The Fair Oaks Branch (650-780-7261) is located at 2510 Middlefield Road and is open Monday-Thursday, 10-7; Friday, 10-5. The Schaberg Branch (650-780-7010) is located at 2140 Euclid Avenue and is open Tuesday-Thursday, 1-6; Saturday, 10-3. The Redwood Shores Branch (650-780-5740) is located at 399 Marine Parkway and is open Monday-Thursday, 10-8; Saturday, 10-5; Sunday 12-5.","short_desc":"Provides general reference and reading materials to adults, teenagers and children.","address":{"street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address":{"attention":"Main Library","street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"ask_for":["Serena Gregorio (Administration)","Kathy Endaya (Literacy Services)","Maria Kramer (Downtown/Youth Services)"],"hours":"Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5","transportation":"SAMTRANS stops within 1 block; CALTRAIN stops within 1 block.","accessibility":["elevator","tape_braille","ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish"],"emails":["rclinfo@redwoodcity.org"],"faxes":[{"number":"650 780-7069"}],"phones":[{"number":"650 780-7018","department":"Circulation","hours":"(Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5)"}],"urls":["http://www.redwoodcity.org/library"],"servs":[{"audience":null,"eligibility":"Resident of California to obtain a card","fees":"None.","how_to_apply":"Walk in. Proof of California residency to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Schaberg Branch","contacts":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Elizabeth Meeks","title":"Branch Manager"}],"description":"Provides general reading materials, including large-type books, DVD's and CDs, books on CD and some Spanish language materials to children. Offers children's programs and a Summer Reading Club. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address":{"street":"2140 Euclid Avenue.","city":"Redwood City","state":"CA","zip":"94061"},"mail_address":{"attention":"Schaberg Branch","street":"2140 Euclid Avenue","city":"Redwood City","state":"CA","zip":"94061"},"ask_for":[],"hours":"Tuesday-Thursday, 1-6, Saturday, 10-3","transportation":"SAMTRANS stops within 1 block.","accessibility":["ramp"],"languages":null,"emails":null,"faxes":[{"number":"650 365-3738"}],"phones":[{"number":"650 780-7010","hours":"(Tuesday-Thursday, 1-6, Saturday, 10-3)"}],"urls":null,"servs":[{"audience":null,"eligibility":"Resident of California to obtain a library card for borrowing materials","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Project Read","contacts":[{"name":"Kathy Endaya","title":"Director"}],"description":"Offers an intergenerational literacy program for youth and English-speaking adults seeking to improver literacy skills. Adult services include: adult one-to-one tutoring to improve basic skills in reading, writing and critical thinking; Families for Literacy (FFL), a home-based family literacy program for parents who want to be able to read to their young children; and small group/English as a Second Language (ESL). Youth services include: Youth Tutoring, Families in Partnership (FIP); Teen-to-Elementary Student Tutoring, Kids in Partnership (KIP); and computer-aided literacy. Redwood City Friends of Literacy is a fundraising board that helps to support and to fund Redwood City's Project Read. Call for more information about each service.","short_desc":"Offers an intergenerational literacy program for adults and youth seeking to improver literacy skills.","address":{"street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"mail_address":{"attention":"Project Read","street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"ask_for":[],"hours":"Monday-Thursday, 10-8:30; Friday, 10-5","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","restroom","disabled_parking"],"languages":null,"emails":["rclread@redwoodcity.org"],"faxes":[{"number":"650 780-7004"}],"phones":[{"number":"650 780-7077","hours":"(Monday-Thursday, 9:30 am-9 pm; Friday, 9:30-5)"}],"urls":["http://www.projectreadredwoodcity.org"],"servs":[{"audience":"Adults, parents, children in 1st-12th grades in the Redwood City school districta","eligibility":"English-speaking adult reading at or below 7th grade level or child in 1st-12th grade in the Redwood City school districts","fees":"None.","how_to_apply":"Walk in or apply by phone, email or webpage registration.","service_areas":["Redwood City"],"keywords":["EDUCATION SERVICES","Adult","Alternative","Literacy","Literacy Programs","Libraries","Public Libraries","Youth"],"wait":"Depends on availability of tutors for small groups and one-on-one.","funding_sources":["City","Donations","Federal","Grants","State"]}]},{"name":"Redwood Shores Branch","contacts":[{"name":"Dave Genesy","title":"Library Director"}],"description":"Provides general reading materials, including large-type books, videos, music cassettes and CDs, and books on tape. Offers children's programs and a Summer Reading Club. Meeting room is available to nonprofit groups. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address":{"street":"399 Marine Parkway.","city":"Redwood City","state":"CA","zip":"94065"},"mail_address":{"attention":"Redwood Shores Branch","street":"399 Marine Parkway","city":"Redwood City","state":"CA","zip":"94065"},"ask_for":[],"hours":null,"transportation":null,"accessibility":null,"languages":null,"emails":null,"faxes":null,"phones":[{"number":"650 780-5740"}],"urls":["http://www.redwoodcity.org/library"],"servs":[{"audience":null,"eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]}]} -{"name":"Salvation Army","locs":[{"name":"Redwood City Corps","contacts":[{"name":"Andres Espinoza","title":"Captain, Commanding Officer"}],"description":"Provides food, clothing, bus tokens and shelter to individuals and families in times of crisis from the Redwood City Corps office and community centers throughout the county. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Counseling and translation services (English/Spanish) are available either on a walk-in basis or by appointment. Rental assistance with available funds. Another office (described separately) is located at 409 South Spruce Avenue, South San Francisco (650-266-4591).","short_desc":"Provides a variety of emergency services to low-income persons. Also sponsors recreational and educational activities.","address":{"street":"660 Veterans Blvd.","city":"Redwood City","state":"CA","zip":"94063"},"mail_address":{"attention":"Salvation Army","street":"P.O. Box 1147","city":"Redwood City","state":"CA","zip":"94064"},"ask_for":["Ivonne Pierre"],"hours":null,"transportation":"SAMTRANS stops nearby.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes":[{"number":"650 364-1712"}],"phones":[{"number":"650 368-4643","hours":"(Monday, Tuesday, Wednesday, Friday, 9-12, 1-4; Thursday, 1-4)"}],"urls":["http://www.tsagoldenstate.org"],"servs":[{"audience":"Individuals or families with low or no income and/or trying to obtain public assistance","eligibility":"None for most services. For emergency assistance, must have low or no income and be willing to apply for public assistance","fees":"None.","how_to_apply":"Call for appointment. Referral from human service professional preferred for emergency assistance.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Foster City","Menlo Park","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Woodside"],"keywords":["COMMUNITY SERVICES","Interpretation/Translation","EMERGENCY SERVICES","Shelter/Refuge","FINANCIAL ASSISTANCE SERVICES","Utilities","MENTAL HEALTH SERVICES","Individual/Group Counseling","Food Pantries","Homeless Shelter","Rental Deposit Assistance","Utility Service Payment Assistance"],"wait":"Up to 20 minutes.","funding_sources":["Donations","Grants"]}]},{"name":"Adult Rehabilitation Center","contacts":[{"name":"Jack Phillips","title":"Administrator"}],"description":"Provides a long-term (6-12 month) residential rehabilitation program for men and women with substance abuse and other problems. Residents receive individual counseling, and drug and alcohol education. The spiritual side of recovery is addressed through chapel services and Bible study as well as 12-step programs. Nicotine cessation is a part of the program. Residents must be physically able to work, seeking treatment for substance abuse, sober long enough to pass urine drug screen before entering and agreeable to participating in weekly 12-step programs such as Alcoholics Anonymous or Narcotics Anonymous. Pinehurst Lodge is a separate facility for women only. Transition houses for men and women graduates also available.","short_desc":"Long-term (6-12 month) residential treatment program for men/women age 21-60.","address":{"street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"mail_address":{"attention":"Adult Rehabilitation Center","street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"ask_for":["Intake coordinator"],"hours":"Monday-Friday, 8-4","transportation":"MUNI - 26 Valencia, Mission Street lines.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes":[{"number":"415 285-1391"}],"phones":[{"number":"415 643-8000","hours":"(Monday-Friday, 8-3)"}],"urls":null,"servs":[{"audience":"Adult alcoholic/drug addictive men and women with social and spiritual problems","eligibility":"Age 21-60, detoxed, physically able and willing to participate in a work therapy program","fees":"None.","how_to_apply":"Walk in or through other agency referral.","service_areas":["Alameda County","Contra Costa County","Marin County","San Francisco County","San Mateo County","Santa Clara County","Northern California"],"keywords":["ALCOHOLISM SERVICES","Residential Care","DRUG ABUSE SERVICES"],"wait":"Varies according to available beds for men and women. Women have a longer wait due to small number of beds statewide.","funding_sources":["Donations","Sales"]}]},{"name":"Sunnyvale Corps","contacts":[{"name":"James Lee","title":"Commanding Officer"}],"description":"Provides emergency assistance including food and clothing for persons in immediate need. Provides PG&E assistance through REACH program. Youth programs offer tutoring, music and troops. Information on related resources is available. Also provides rental assistance when funds are available.","short_desc":"Provides emergency assistance to persons in immediate need and offers after school activities and summer day camp program.","address":{"street":"1161 South Bernardo","city":"Sunnyvale","state":"CA","zip":"94087"},"mail_address":{"attention":"Salvation Army","street":"P.O. Box 61868","city":"Sunnyvale","state":"CA","zip":"94088"},"ask_for":[],"hours":"Monday-Friday, 9-4","transportation":"VTA stops within 4 blocks.","accessibility":[],"languages":["Korean"],"emails":["william_nichols@usw.salvationarmy.org"],"faxes":[{"number":"408 720-8075"}],"phones":[{"number":"408 720-0420","hours":"(Monday-Friday, 9-4)"}],"urls":null,"servs":[{"audience":null,"eligibility":"None for emergency assistance","fees":"None for emergency services. Vary for after school activities. Cash and checks accepted.","how_to_apply":"Walk in. Written application, identification required for emergency assistance.","service_areas":["Los Altos","Mountain View","Sunnyvale"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","CHILD PROTECTION AND CARE SERVICES","Day Care","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","RECREATION/LEISURE SERVICES","Camping","Emergency Food","Clothing","Utility Assistance","Youth Development"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"South San Francisco Citadel Corps","contacts":[{"name":"Kenneth Gibson","title":"Major"}],"description":"Provides emergency food, clothing and furniture vouchers to low-income families in times of crisis. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Offers a Saturday morning Homeless Feeding Program at 10:30, as well as Sunday services and spiritual counseling. Provides Christmas toys and Back-to-School clothes and supplies. Offers case management, advocacy and referrals to other agencies.","short_desc":"Provides emergency food and clothing and furniture vouchers to low-income families in times of crisis.","address":{"street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"mail_address":{"attention":"Salvation Army","street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"ask_for":["Family Services Caseworker "],"hours":"Monday-Thursday, 9-4:30","transportation":"SAMTRANS stops within 1 block, BART stops within 3 blocks.","accessibility":["wheelchair"],"languages":null,"emails":null,"faxes":[{"number":"650 266-2594"},{"number":"650 266-4594"}],"phones":[{"number":"650 266-4591","hours":"(Monday-Thursday, 9-4:30)"}],"urls":["http://www.tsagoldenstate.org"],"servs":[{"audience":null,"eligibility":"Low-income families","fees":"None.","how_to_apply":"Call for information.","service_areas":["Brisbane","Colma","Daly City","Millbrae","Pacifica","San Bruno","South San Francisco"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","Emergency Food","Food Pantries","Furniture","Clothing","Utility Assistance","School Supplies","Case/Care Management","Holiday Programs","Pastoral Counseling","Low Income"],"wait":null,"funding_sources":["Donations"]}]}]} -{"name":"Samaritan House","locs":[{"name":"Redwood City Free Medical Clinic","contacts":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical care to those in need. Offers basic medical exams for adults and tuberculosis screening. Assists the individual to access other services in the community. By appointment only, Project Smile provides a free dental exam, dental cleaning and oral hygiene instruction for children, age 3-12, of Samaritan House clients.","short_desc":"Provides free medical care to those in need.","address":{"street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"mail_address":{"attention":"Redwood City Free Medical Clinic","street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"ask_for":null,"hours":"Monday-Friday, 9-12, 2-5","transportation":"SAMTRANS stops within 2 blocks.","accessibility":["restroom","wheelchair"],"languages":["Spanish"],"emails":["gracie@samaritanhouse.com"],"faxes":[{"number":"650 839-1457"}],"phones":[{"number":"650 839-1447","hours":"(Monday-Friday, 8:30-12, 1:30-5)"}],"urls":["http://www.samaritanhouse.com"],"servs":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment. Medical visits are by appointment only.","service_areas":["Atherton","East Palo Alto","Menlo Park","Redwood City","San Carlos"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]},{"name":"San Mateo Free Medical Clinic","contacts":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","short_desc":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","address":{"street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address":{"attention":"San Mateo Free Medical/Dental","street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"ask_for":null,"hours":"Monday-Friday, 9-12, 1-4","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","wheelchair"],"languages":["Spanish"],"emails":["smcmed@samaritanhouse.com"],"faxes":[{"number":"650 578-0440"}],"phones":[{"number":"650 578-0400","hours":"(Monday-Friday, 9-12, 1-4)"}],"urls":["http://www.samaritanhouse.com"],"servs":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment (650-347-3648).","service_areas":["Belmont","Burlingame","Foster City","Millbrae","San Carlos","San Mateo"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]}]} -{"name":"Location with no phone", "locs":[{"accessibility" : [], "ask_for" : null, "coordinates" : null, "description" : "no phone", "emails" : [], "faxes" : null, "hours" : null, "kind" : "test", "languages" : null, "mail_address" : { "attention" : "", "street" : "puma", "city" : "fairfax", "state" : "VA", "zip" : "22031" }, "name" : "Location with no phone", "phones" : null, "short_desc" : "no phone", "transportation" : null, "urls" : null, "servs":[{"audience":""}] } ] } -{"name":"Admin Test Org", "locs":[{"accessibility" : [ "elevator", "restroom" ], "address" : { "city" : "fairfax", "state" : "va", "street" : "bozo", "zip" : "12345" }, "ask_for" : null, "contacts" : [ { "name" : "Moncef", "title" : "Director" } ], "coordinates" : [ -73.9395687, 42.8142432 ], "description" : "This is a description", "emails" : [ "eml@example.org" ], "faxes" : [ { "number" : "2025551212", "department" : "CalFresh" } ], "hours" : "Monday-Friday 10am-5pm", "kind" : "test", "languages" : null, "name" : "Admin Test Location", "phones" : [ { "number" : "7035551212", "vanity_number" : "703555-ABCD", "extension" : "x1223", "department" : "CalFresh" } ], "short_desc" : "This is a short description", "transportation" : "SAMTRANS stops within 1/2 mile.", "urls" : [ "http://codeforamerica.org" ], "servs":[{"service_areas":["San Mateo County"]}] }] } +{"name":"Peninsula Family Service","locations":[{"name":"Fair Oaks Adult Activity Center","contacts_attributes":[{"name":"Susan Houston","title":"Director of Older Adult Services"},{"name":" Christina Gonzalez","title":"Center Director"}],"description":"A walk-in center for older adults that provides social services, wellness, recreational, educational and creative activities including arts and crafts, computer classes and gardening classes. Coffee and healthy breakfasts are available daily. A hot lunch is served Tuesday-Friday for persons age 60 or over and spouse. Provides case management (including in-home assessments) and bilingual information and referral about community services to persons age 60 or over on questions of housing, employment, household help, recreation and social activities, home delivered meals, health and counseling services and services to shut-ins. Health insurance and legal counseling is available by appointment. Lectures on a variety of health and fitness topics are held monthly in both English and Spanish. Provides a variety of physical fitness opportunities, including a garden club, yoga, tai chi, soul line dance and aerobics classes geared toward older adults. Also provides free monthly blood pressure screenings, quarterly blood glucose monitoring and health screenings by a visiting nurse. Offers a Brown Bag Program in which low-income seniors can receive a bag of groceries each week for a membership fee of $10 a year. Offers Spanish lessons. Formerly known as Peninsula Family Service, Fair Oaks Intergenerational Center. Formerly known as the Fair Oaks Senior Center. Formerly known as Family Service Agency of San Mateo County, Fair Oaks Intergenerational Center.","short_desc":"A multipurpose senior citizens' center serving the Redwood City area.","address_attributes":{"street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Fair Oaks Intergenerational Center","street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["cgonzalez@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 701-0856"}],"phones_attributes":[{"number":"650 780-7525","hours":"(Monday-Friday, 8:30-5)"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Older adults age 55 or over, ethnic minorities and low-income persons","eligibility":"Age 55 or over for most programs, age 60 or over for lunch program","fees":"$2.50 suggested donation for lunch for age 60 or over, donations for other services appreciated. Cash and checks accepted.","how_to_apply":"Walk in or apply by phone.","service_areas":["Redwood City"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","English Language","RECREATION/LEISURE SERVICES","Arts and Crafts","Sports/Games/Exercise","Brown Bag Food Programs","Congregate Meals/Nutrition Sites","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["City","County","Donations","Fees","Fundraising"]}]},{"name":"Second Career Employment Program","contacts_attributes":[{"name":"Brenda Brown","title":"Director, Second Career Services"}],"description":"Provides training and job placement to eligible people age 55 or over who meet certain income qualifications. An income of 125% of poverty level or less is required for subsidized employment and training. (No income requirements for job matchup program.) If a person seems likely to be qualified after a preliminary phone call or visit, he or she must complete an application at this office. Staff will locate appropriate placements for applicants, provide orientation and on-the-job training and assist with finding a job outside the program. Any county resident, regardless of income, age 55 or over has access to the program, job developers and the job bank. Also provides referrals to classroom training. Formerly known as Family Service Agency of San Mateo County, Senior Employment Services.","short_desc":"Provides training and job placement to eligible persons age 55 or over. All persons age 55 or over have access to information in the program's job bank.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"PFS Second Career Services","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Chinese (Mandarin)","Filipino (Tagalog)","Italian","Spanish"],"emails":["bbrown@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4302"}],"phones_attributes":[{"number":"650 403-4300","department":" Ext. 4385","hours":"(Monday-Friday, 9-5)"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Residents of San Mateo County age 55 or over","eligibility":"Age 55 or over, county resident and willing and able to work. Income requirements vary according to program","fees":"None. Donations requested of clients who can afford it.","how_to_apply":"Apply by phone for an appointment.","service_areas":["San Mateo County"],"keywords":["EMPLOYMENT/TRAINING SERVICES","Job Development","Job Information/Placement/Referral","Job Training","Job Training Formats","Job Search/Placement","Older Adults"],"wait":"Varies.","funding_sources":["County","Federal","State"]}]},{"name":"Senior Peer Counseling","contacts_attributes":[{"name":"Howard Lader, LCSW","title":"Manager, Senior Peer Counseling"}],"description":"Offers supportive counseling services to San Mateo County residents age 55 or over. Volunteer counselors are selected and professionally trained to help their peers face the challenges and concerns of growing older. Training provides topics that include understanding depression, cultural competence, sexuality, community resources, and other subjects that are relevant to working with older adults. After completion of training, weekly supervision groups are provided for the volunteer senior peer counselors, as well as quarterly in-service training seminars. Peer counseling services are provided in English, Spanish, Chinese (Cantonese and Mandarin), Tagalog, and to the lesbian, gay, bisexual, transgender older adult community. Formerly known as Family Service Agency of San Mateo County, Senior Peer Counseling.","short_desc":"Offers supportive counseling services to older persons.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address_attributes":{"attention":"PFS Senior Peer Counseling","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"hours":"Any time that is convenient for the client and peer counselor","transportation":"Service is provided in person's home or at a mutually agreed upon location.","accessibility":[],"languages":["Chinese (Cantonese)","Chinese (Mandarin)","Filipino (Tagalog)","Spanish"],"emails":null,"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","department":"English"}],"urls":null,"services_attributes":[{"audience":"Older adults age 55 or over who can benefit from counseling","eligibility":"Resident of San Mateo County age 55 or over","fees":"None.","how_to_apply":"Phone for information (403-4300 Ext. 4322).","service_areas":["San Mateo County"],"keywords":["Geriatric Counseling","Older Adults","Gay, Lesbian, Bisexual, Transgender Individuals"],"wait":"Varies.","funding_sources":["County","Donations","Grants"]}]},{"name":"Family Visitation Center","contacts_attributes":[{"name":"Kimberly Pesavento","title":"Director of Visitation"}],"description":"Provides supervised visitation services and a neutral site for parents to carry out supervised exchanges of children in a safe manner. Therapeutic visitation and other counseling services available. Kids in the Middle is an education class for separating or divorcing parents. The goal is to enable parents to focus on their children's needs while the family is going through separation, divorce or other transition. The class explores the psychological aspects of divorce and its impact on children, builds effective communication techniques and points out areas in which outside help may be indicated. The fee is $50 for working parents, $15 for unemployed people. Classes are scheduled on Saturdays and Sundays and held at various sites throughout the county. Call 650-403-4300 ext. 4500 to register. Formerly known as Family Service Agency of San Mateo County, Family Visitation Center.","short_desc":"Provides supervised visitation services and a neutral site for parents in extremely hostile divorces to carry out supervised exchanges of children.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"PFS Family Visitation Center","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":"Monday, 10-6; Tuesday-Friday, 10-8; Saturday, Sunday, 9:30-5:30","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 4 blocks.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":["kpesavento@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","extension":"4500","hours":"(Tuesday, Wednesday, 10-6; Thursday, Friday, 10-8; Saturday, Sunday, 9:30-5:30)"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Parents, children, families with problems of custody disputes, domestic violence or substance abuse, families going through a separation or divorce","eligibility":"None","fees":"Vary according to income ($5-$90). Cash, checks and credit cards accepted.","how_to_apply":"Apply by phone.","service_areas":["San Mateo County"],"keywords":["INDIVIDUAL AND FAMILY DEVELOPMENT SERVICES","Growth and Adjustment","LEGAL AND CRIMINAL JUSTICE SERVICES","Mediation","Parental Visitation Monitoring","Divorce Counseling","Youth"],"wait":"No wait.","funding_sources":["County","Donations","Grants"]}]},{"name":"Economic Self - Sufficiency Program","contacts_attributes":[{"name":"Joe Bloom","title":"Financial Empowerment Programs Program Director"}],"description":"Provides fixed 8% short term loans to eligible applicants for the purchase of a reliable, used autmobile. Loans are up to $6,000 over 2 1/2 years (30 months). Funds must go towards the entire purchase of the automobile. Peninsula Family Service originates loans and collaborates with commercial partner banks to service the loans, helping clients build credit histories. Formerly known as Family Service Agency of San Mateo County, Ways to Work Family Loan Program.","short_desc":"Makes small loans to working families.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"Economic Self - Sufficiency Program","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":null,"transportation":"SAMTRANS stops within 1 block. CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Hindi","Spanish"],"emails":["waystowork@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","extension":"4100"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Target group: Low-income working families with children transitioning from welfare to work and poor or who do not have access to conventional credit","eligibility":"Eligibility: Low-income family with legal custody of a minor child or an involved parent of a dependent minor child. Must reside and/or work in San Mateo County. Must be working and have verifiable income and ability to pay off loan. No bankruptcies in the past two years and unable to qualify for other funding sources. Loans approved by loan committee.","fees":"$60 application fee. Cash or checks accepted.","how_to_apply":"Phone for information.","service_areas":["San Mateo County"],"keywords":["COMMUNITY SERVICES","Speakers","Automobile Loans"],"wait":null,"funding_sources":["County","Grants","State"]}]}]} +{"name":"Peninsula Volunteers","locations":[{"name":"Little House","contacts_attributes":[{"name":"Peter Olson","title":"Little House Director"},{"name":" Bart Charlow","title":"Executive Director, Peninsula Volunteers"}],"description":"A multipurpose center offering a wide variety of recreational, education and cultural activities. Lifelong learning courses cover topics such as music, art, languages, etc., are hosted at this location. Little House offers a large variety of classes including arts and crafts, jewelry, languages, current events, lapidary, woodwork, painting, and fitness courses (yoga, strength straining, tai chi). There are monthly art and cultural lectures, movie showings, and a computer center. Recreation activities include mah jong, pinochle, ballroom dancing, bridge, trips and tours. Partners with the Sequoia Adult Education Program. The Alzheimer's Cafe, open the third Tuesday of every month from 2:00 - 4:00 pm, is a place that brings together people liviing with dementia, their families, and their caregivers. Free and no registration is needed. The Little House Community Service Desk offers information and referrals regarding social service issues, such as housing, food, transportation, health insurance counseling, and estate planning. Massage, podiatry, and acupuncture are available by appointment. Lunch is served Monday-Friday, 11:30 am-1:00 pm. Prices vary according to selection.","short_desc":"A multipurpose senior citizens' center.","address_attributes":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address_attributes":{"attention":"Little House","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"hours":"Monday-Thursday, 8 am-9 pm; Friday, 8-5","transportation":"SAMTRANS stops within 3 blocks, RediWheels and Menlo Park Shuttle stop at door.","accessibility":["disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["polson@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 326-9547"}],"phones_attributes":[{"number":"650 326-2025","hours":"(Monday-Thursday, 8 am-9 pm; Friday, 8-5))"}],"urls":["http://www.penvol.org/littlehouse"],"services_attributes":[{"audience":"Any age","eligibility":"None","fees":"$55 per year membership dues. Classes have fees. Discounts are available for members. Cash, checks and credit cards accepted.","how_to_apply":"Walk in or apply by phone for membership application.","service_areas":["San Mateo County","Santa Clara County"],"keywords":["ADULT PROTECTION AND CARE SERVICES","In-Home Supportive","Meal Sites/Home-delivered Meals","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","Adult","HEALTH SERVICES","Education/Information","Family Support","Individual/Group Counseling","Screening/Immunization","RECREATION/LEISURE SERVICES","Sports/Games/Exercise","Community Adult Schools","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["Fees","Fundraising","Grants","Membership dues"]}]},{"name":"Rosener House Adult Day Services","contacts_attributes":[{"name":"Bart Charlow","title":"Executive Director, Peninsula Volunteers"},{"name":" Barbara Kalt","title":"Director"}],"description":"Rosener House is a day center for older adults who may be unable to live independently but do not require 24-hour nursing care, may be isolated and in need of a planned activity program, may need assistance with activities of daily living or are living in a family situation where the caregiver needs respite from giving full-time care. Assists elderly persons to continue to live with family or alone rather than moving to a skilled nursing facility. Activities are scheduled Monday-Friday, 10 am-2:30 pm, and participants may come two to five days per week. The facility is open from 8 am to 5:30 pm for participants who need to remain all day. Small group late afternoon activities are held from 3-5:30 pm. The program provides a noon meal including special diets as required. Services offered include social and therapeutic recreational activities, individual and family counseling and occupational, physical and speech therapy. A registered nurse is available daily. The Dementia and Alzheimer's Services Program provides specialized activities in a supportive environment for participants with Alzheimer's disease and other dementias. Holds a weekly support group for caregivers. An early memory loss class for independent adults, \"Minds in Motion\" meets weekly at Rosener House on Wednesday mornings. Call for more information.","short_desc":"A day center for adults age 50 or over.","address_attributes":{"street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"mail_address_attributes":{"attention":"Rosener House","street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"hours":"Monday-Friday, 8-5:30","transportation":"Transportation can be arranged via Redi-Wheels or Outreach.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish","Filipino (Tagalog)","Vietnamese"],"emails":["bkalt@peninsulavolunteers.org","fmarchick@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 322-4067"}],"phones_attributes":[{"number":"650 322-0126","hours":"(Monday-Friday, 8-5:30)"}],"urls":["http://www.penvol.org/rosenerhouse"],"services_attributes":[{"audience":"Older adults who have memory or sensory loss, mobility limitations and may be lonely and in need of socialization","eligibility":"Age 18 or over","fees":"$85 per day. Vary according to income for those unable to pay full fee. Cash, checks, credit cards, private insurance and vouchers accepted.","how_to_apply":"Apply by phone or be referred by a doctor, social worker or other professional. All prospective participants are interviewed individually before starting the program. A recent physical examination is required, including a TB test.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Los Altos","Los Altos Hills","Menlo Park","Mountain View","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Sunnyvale","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Adult Day Health Care","Dementia Management","Adult Day Programs","Older Adults"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"Meals on Wheels - South County","contacts_attributes":[{"name":"Marilyn Baker-Venturini","title":"Director"},{"name":" Graciela Hernandez","title":"Assistant Manager"},{"name":" Julie Avelino","title":"Assessment Specialist"}],"description":"Delivers a hot meal to the home of persons age 60 or over who are primarily homebound and unable to prepare their own meals, and have no one to prepare meals. Also, delivers a hot meal to the home of disabled individuals ages 18-59. Meals are delivered between 9 am-1:30 pm, Monday-Friday. Special diets are accommodated: low fat, low sodium, and low sugar.","short_desc":"Will deliver a hot meal to the home of persons age 60 or over who are homebound and unable to prepare their own meals. Can provide special diets.","address_attributes":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address_attributes":{"attention":"Meals on Wheels - South County","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"hours":"Delivery times: Monday-Friday, 9-1:30","transportation":"Not necessary for service.","accessibility":null,"languages":["Spanish"],"emails":["mbaker-venturini@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 326-9547"}],"phones_attributes":[{"number":"650 323-2022","hours":"(Monday-Friday, 8-2)"}],"urls":["http://www.peninsulavolunteers.org"],"services_attributes":[{"audience":"Senior citizens age 60 or over, disabled individuals age 18-59","eligibility":"Homebound person unable to cook or shop","fees":"Suggested donation of $4.25 per meal for seniors 60 or over. Mandatory charge of $2 per meal for disabled individuals ages 18-59.","how_to_apply":"Apply by phone.","service_areas":["Atherton","Belmont","East Palo Alto","Menlo Park","Portola Valley","Redwood City","San Carlos","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","HEALTH SERVICES","Nutrition","Home Delivered Meals","Older Adults","Disabilities Issues"],"wait":"No wait.","funding_sources":["County","Donations"]}]}]} +{"name":"Redwood City Public Library","locations":[{"name":"Fair Oaks Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Divisions Manager"}],"description":"Provides general reading material, including bilingual, multi-cultural books, CDs and cassettes, bilingual and Spanish language reference services. School, class and other group visits may be arranged by appointment. The library is a member of the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Fair Oaks Branch","street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-7; Friday, 10-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"650 569-3371"}],"phones_attributes":[{"number":"650 780-7261","hours":"(Monday-Thursday, 10-7; Friday, 10-5)"}],"urls":null,"services_attributes":[{"audience":"Ethnic minorities, especially Spanish speaking","eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of residency in California required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City","County"]}]},{"name":"Main Library","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Division Manager"}],"description":"Provides general reading and media materials, literacy and homework assistance, and programs for all ages. Provides public computers, wireless connectivity, a children's room, teen center, and a local history collection. The library is a member of the Peninsula Library System. The Fair Oaks Branch (650-780-7261) is located at 2510 Middlefield Road and is open Monday-Thursday, 10-7; Friday, 10-5. The Schaberg Branch (650-780-7010) is located at 2140 Euclid Avenue and is open Tuesday-Thursday, 1-6; Saturday, 10-3. The Redwood Shores Branch (650-780-5740) is located at 399 Marine Parkway and is open Monday-Thursday, 10-8; Saturday, 10-5; Sunday 12-5.","short_desc":"Provides general reference and reading materials to adults, teenagers and children.","address_attributes":{"street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Main Library","street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5","transportation":"SAMTRANS stops within 1 block; CALTRAIN stops within 1 block.","accessibility":["elevator","tape_braille","ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish"],"emails":["rclinfo@redwoodcity.org"],"faxes_attributes":[{"number":"650 780-7069"}],"phones_attributes":[{"number":"650 780-7018","department":"Circulation","hours":"(Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5)"}],"urls":["http://www.redwoodcity.org/library"],"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a card","fees":"None.","how_to_apply":"Walk in. Proof of California residency to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Schaberg Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Elizabeth Meeks","title":"Branch Manager"}],"description":"Provides general reading materials, including large-type books, DVD's and CDs, books on CD and some Spanish language materials to children. Offers children's programs and a Summer Reading Club. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"2140 Euclid Avenue.","city":"Redwood City","state":"CA","zip":"94061"},"mail_address_attributes":{"attention":"Schaberg Branch","street":"2140 Euclid Avenue","city":"Redwood City","state":"CA","zip":"94061"},"hours":"Tuesday-Thursday, 1-6, Saturday, 10-3","transportation":"SAMTRANS stops within 1 block.","accessibility":["ramp"],"languages":null,"emails":null,"faxes_attributes":[{"number":"650 365-3738"}],"phones_attributes":[{"number":"650 780-7010","hours":"(Tuesday-Thursday, 1-6, Saturday, 10-3)"}],"urls":null,"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a library card for borrowing materials","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Project Read","contacts_attributes":[{"name":"Kathy Endaya","title":"Director"}],"description":"Offers an intergenerational literacy program for youth and English-speaking adults seeking to improver literacy skills. Adult services include: adult one-to-one tutoring to improve basic skills in reading, writing and critical thinking; Families for Literacy (FFL), a home-based family literacy program for parents who want to be able to read to their young children; and small group/English as a Second Language (ESL). Youth services include: Youth Tutoring, Families in Partnership (FIP); Teen-to-Elementary Student Tutoring, Kids in Partnership (KIP); and computer-aided literacy. Redwood City Friends of Literacy is a fundraising board that helps to support and to fund Redwood City's Project Read. Call for more information about each service.","short_desc":"Offers an intergenerational literacy program for adults and youth seeking to improver literacy skills.","address_attributes":{"street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Project Read","street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-8:30; Friday, 10-5","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","restroom","disabled_parking"],"languages":null,"emails":["rclread@redwoodcity.org"],"faxes_attributes":[{"number":"650 780-7004"}],"phones_attributes":[{"number":"650 780-7077","hours":"(Monday-Thursday, 9:30 am-9 pm; Friday, 9:30-5)"}],"urls":["http://www.projectreadredwoodcity.org"],"services_attributes":[{"audience":"Adults, parents, children in 1st-12th grades in the Redwood City school districta","eligibility":"English-speaking adult reading at or below 7th grade level or child in 1st-12th grade in the Redwood City school districts","fees":"None.","how_to_apply":"Walk in or apply by phone, email or webpage registration.","service_areas":["Redwood City"],"keywords":["EDUCATION SERVICES","Adult","Alternative","Literacy","Literacy Programs","Libraries","Public Libraries","Youth"],"wait":"Depends on availability of tutors for small groups and one-on-one.","funding_sources":["City","Donations","Federal","Grants","State"]}]},{"name":"Redwood Shores Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"}],"description":"Provides general reading materials, including large-type books, videos, music cassettes and CDs, and books on tape. Offers children's programs and a Summer Reading Club. Meeting room is available to nonprofit groups. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"399 Marine Parkway.","city":"Redwood City","state":"CA","zip":"94065"},"mail_address_attributes":{"attention":"Redwood Shores Branch","street":"399 Marine Parkway","city":"Redwood City","state":"CA","zip":"94065"},"hours":null,"transportation":null,"accessibility":null,"languages":null,"emails":null,"phones_attributes":[{"number":"650 780-5740"}],"urls":["http://www.redwoodcity.org/library"],"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]}]} +{"name":"Salvation Army","locations":[{"name":"Redwood City Corps","contacts_attributes":[{"name":"Andres Espinoza","title":"Captain, Commanding Officer"}],"description":"Provides food, clothing, bus tokens and shelter to individuals and families in times of crisis from the Redwood City Corps office and community centers throughout the county. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Counseling and translation services (English/Spanish) are available either on a walk-in basis or by appointment. Rental assistance with available funds. Another office (described separately) is located at 409 South Spruce Avenue, South San Francisco (650-266-4591).","short_desc":"Provides a variety of emergency services to low-income persons. Also sponsors recreational and educational activities.","address_attributes":{"street":"660 Veterans Blvd.","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Salvation Army","street":"P.O. Box 1147","city":"Redwood City","state":"CA","zip":"94064"},"hours":null,"transportation":"SAMTRANS stops nearby.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"650 364-1712"}],"phones_attributes":[{"number":"650 368-4643","hours":"(Monday, Tuesday, Wednesday, Friday, 9-12, 1-4; Thursday, 1-4)"}],"urls":["http://www.tsagoldenstate.org"],"services_attributes":[{"audience":"Individuals or families with low or no income and/or trying to obtain public assistance","eligibility":"None for most services. For emergency assistance, must have low or no income and be willing to apply for public assistance","fees":"None.","how_to_apply":"Call for appointment. Referral from human service professional preferred for emergency assistance.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Foster City","Menlo Park","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Woodside"],"keywords":["COMMUNITY SERVICES","Interpretation/Translation","EMERGENCY SERVICES","Shelter/Refuge","FINANCIAL ASSISTANCE SERVICES","Utilities","MENTAL HEALTH SERVICES","Individual/Group Counseling","Food Pantries","Homeless Shelter","Rental Deposit Assistance","Utility Service Payment Assistance"],"wait":"Up to 20 minutes.","funding_sources":["Donations","Grants"]}]},{"name":"Adult Rehabilitation Center","contacts_attributes":[{"name":"Jack Phillips","title":"Administrator"}],"description":"Provides a long-term (6-12 month) residential rehabilitation program for men and women with substance abuse and other problems. Residents receive individual counseling, and drug and alcohol education. The spiritual side of recovery is address_attributesed through chapel services and Bible study as well as 12-step programs. Nicotine cessation is a part of the program. Residents must be physically able to work, seeking treatment for substance abuse, sober long enough to pass urine drug screen before entering and agreeable to participating in weekly 12-step programs such as Alcoholics Anonymous or Narcotics Anonymous. Pinehurst Lodge is a separate facility for women only. Transition houses for men and women graduates also available.","short_desc":"Long-term (6-12 month) residential treatment program for men/women age 21-60.","address_attributes":{"street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"mail_address_attributes":{"attention":"Adult Rehabilitation Center","street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"hours":"Monday-Friday, 8-4","transportation":"MUNI - 26 Valencia, Mission Street lines.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"415 285-1391"}],"phones_attributes":[{"number":"415 643-8000","hours":"(Monday-Friday, 8-3)"}],"urls":null,"services_attributes":[{"audience":"Adult alcoholic/drug addictive men and women with social and spiritual problems","eligibility":"Age 21-60, detoxed, physically able and willing to participate in a work therapy program","fees":"None.","how_to_apply":"Walk in or through other agency referral.","service_areas":["Alameda County","Contra Costa County","Marin County","San Francisco County","San Mateo County","Santa Clara County","Northern California"],"keywords":["ALCOHOLISM SERVICES","Residential Care","DRUG ABUSE SERVICES"],"wait":"Varies according to available beds for men and women. Women have a longer wait due to small number of beds statewide.","funding_sources":["Donations","Sales"]}]},{"name":"Sunnyvale Corps","contacts_attributes":[{"name":"James Lee","title":"Commanding Officer"}],"description":"Provides emergency assistance including food and clothing for persons in immediate need. Provides PG&E assistance through REACH program. Youth programs offer tutoring, music and troops. Information on related resources is available. Also provides rental assistance when funds are available.","short_desc":"Provides emergency assistance to persons in immediate need and offers after school activities and summer day camp program.","address_attributes":{"street":"1161 South Bernardo","city":"Sunnyvale","state":"CA","zip":"94087"},"mail_address_attributes":{"attention":"Salvation Army","street":"P.O. Box 61868","city":"Sunnyvale","state":"CA","zip":"94088"},"hours":"Monday-Friday, 9-4","transportation":"VTA stops within 4 blocks.","accessibility":[],"languages":["Korean"],"emails":["william_nichols@usw.salvationarmy.org"],"faxes_attributes":[{"number":"408 720-8075"}],"phones_attributes":[{"number":"408 720-0420","hours":"(Monday-Friday, 9-4)"}],"urls":null,"services_attributes":[{"audience":null,"eligibility":"None for emergency assistance","fees":"None for emergency services. Vary for after school activities. Cash and checks accepted.","how_to_apply":"Walk in. Written application, identification required for emergency assistance.","service_areas":["Los Altos","Mountain View","Sunnyvale"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","CHILD PROTECTION AND CARE SERVICES","Day Care","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","RECREATION/LEISURE SERVICES","Camping","Emergency Food","Clothing","Utility Assistance","Youth Development"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"South San Francisco Citadel Corps","contacts_attributes":[{"name":"Kenneth Gibson","title":"Major"}],"description":"Provides emergency food, clothing and furniture vouchers to low-income families in times of crisis. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Offers a Saturday morning Homeless Feeding Program at 10:30, as well as Sunday services and spiritual counseling. Provides Christmas toys and Back-to-School clothes and supplies. Offers case management, advocacy and referrals to other agencies.","short_desc":"Provides emergency food and clothing and furniture vouchers to low-income families in times of crisis.","address_attributes":{"street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"mail_address_attributes":{"attention":"Salvation Army","street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"hours":"Monday-Thursday, 9-4:30","transportation":"SAMTRANS stops within 1 block, BART stops within 3 blocks.","accessibility":["wheelchair"],"languages":null,"emails":null,"faxes_attributes":[{"number":"650 266-2594"},{"number":"650 266-4594"}],"phones_attributes":[{"number":"650 266-4591","hours":"(Monday-Thursday, 9-4:30)"}],"urls":["http://www.tsagoldenstate.org"],"services_attributes":[{"audience":null,"eligibility":"Low-income families","fees":"None.","how_to_apply":"Call for information.","service_areas":["Brisbane","Colma","Daly City","Millbrae","Pacifica","San Bruno","South San Francisco"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","Emergency Food","Food Pantries","Furniture","Clothing","Utility Assistance","School Supplies","Case/Care Management","Holiday Programs","Pastoral Counseling","Low Income"],"wait":null,"funding_sources":["Donations"]}]}]} +{"name":"Samaritan House","locations":[{"name":"Redwood City Free Medical Clinic","contacts_attributes":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical care to those in need. Offers basic medical exams for adults and tuberculosis screening. Assists the individual to access other services in the community. By appointment only, Project Smile provides a free dental exam, dental cleaning and oral hygiene instruction for children, age 3-12, of Samaritan House clients.","short_desc":"Provides free medical care to those in need.","address_attributes":{"street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Redwood City Free Medical Clinic","street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Friday, 9-12, 2-5","transportation":"SAMTRANS stops within 2 blocks.","accessibility":["restroom","wheelchair"],"languages":["Spanish"],"emails":["gracie@samaritanhouse.com"],"faxes_attributes":[{"number":"650 839-1457"}],"phones_attributes":[{"number":"650 839-1447","hours":"(Monday-Friday, 8:30-12, 1:30-5)"}],"urls":["http://www.samaritanhouse.com"],"services_attributes":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment. Medical visits are by appointment only.","service_areas":["Atherton","East Palo Alto","Menlo Park","Redwood City","San Carlos"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]},{"name":"San Mateo Free Medical Clinic","contacts_attributes":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","short_desc":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","address_attributes":{"street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address_attributes":{"attention":"San Mateo Free Medical/Dental","street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"hours":"Monday-Friday, 9-12, 1-4","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","wheelchair"],"languages":["Spanish"],"emails":["smcmed@samaritanhouse.com"],"faxes_attributes":[{"number":"650 578-0440"}],"phones_attributes":[{"number":"650 578-0400","hours":"(Monday-Friday, 9-12, 1-4)"}],"urls":["http://www.samaritanhouse.com"],"services_attributes":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment (650-347-3648).","service_areas":["Belmont","Burlingame","Foster City","Millbrae","San Carlos","San Mateo"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]}]} +{"name":"Location with no phone", "locations":[{"accessibility" : [], "description" : "no phone", "emails" : [], "faxes_attributes" : [], "hours" : null, "kind" : "test", "languages" : null, "mail_address_attributes" : { "attention" : "", "street" : "puma", "city" : "fairfax", "state" : "VA", "zip" : "22031" }, "name" : "Location with no phone", "phones_attributes" : [], "short_desc" : "no phone", "transportation" : null, "urls" : null, "services_attributes":[{"audience":""}] } ] } +{"name":"Admin Test Org", "locations":[{"accessibility" : [ "elevator", "restroom" ], "address_attributes" : { "city" : "fairfax", "state" : "va", "street" : "bozo", "zip" : "12345" }, "contacts_attributes" : [ { "name" : "Moncef", "title" : "Director" } ], "latitude" : 42.8142432, "longitude": -73.9395687, "description" : "This is a description", "emails" : [ "eml@example.org" ], "faxes_attributes" : [ { "number" : "2025551212", "department" : "CalFresh" } ], "hours" : "Monday-Friday 10am-5pm", "kind" : "test", "languages" : null, "name" : "Admin Test Location", "phones_attributes" : [ { "number" : "7035551212", "vanity_number" : "703555-ABCD", "extension" : "x1223", "department" : "CalFresh" } ], "short_desc" : "This is a short description", "transportation" : "SAMTRANS stops within 1/2 mile.", "urls" : [ "http://codeforamerica.org" ], "services_attributes":[{"service_areas":["San Mateo County"]}] }] } diff --git a/db/migrate/20140328034023_create_addresses.rb b/db/migrate/20140328034023_create_addresses.rb new file mode 100644 index 000000000..a3b6b9523 --- /dev/null +++ b/db/migrate/20140328034023_create_addresses.rb @@ -0,0 +1,14 @@ +class CreateAddresses < ActiveRecord::Migration + def change + create_table :addresses do |t| + t.belongs_to :location + t.text :street + t.text :city + t.text :state + t.text :zip + + t.timestamps + end + add_index :addresses, :location_id + end +end diff --git a/db/migrate/20140328034531_create_organizations.rb b/db/migrate/20140328034531_create_organizations.rb new file mode 100644 index 000000000..9871060fe --- /dev/null +++ b/db/migrate/20140328034531_create_organizations.rb @@ -0,0 +1,12 @@ +class CreateOrganizations < ActiveRecord::Migration + def change + create_table :organizations do |t| + t.text :name + t.text :urls + t.text :slug + + t.timestamps + end + add_index :organizations, :slug, unique: true + end +end diff --git a/db/migrate/20140328034754_create_locations.rb b/db/migrate/20140328034754_create_locations.rb new file mode 100644 index 000000000..1bc62c265 --- /dev/null +++ b/db/migrate/20140328034754_create_locations.rb @@ -0,0 +1,26 @@ +class CreateLocations < ActiveRecord::Migration + def change + create_table :locations do |t| + t.belongs_to :organization + t.text :accessibility + t.text :admin_emails + t.text :description + t.text :emails + t.text :hours + t.text :kind + t.float :latitude + t.float :longitude + t.text :languages + t.text :name + t.text :short_desc + t.text :transportation + t.text :urls + t.text :slug + + t.timestamps + end + add_index :locations, :slug, unique: true + add_index :locations, :organization_id + add_index :locations, [:latitude, :longitude] + end +end diff --git a/db/migrate/20140328035528_create_users.rb b/db/migrate/20140328035528_create_users.rb new file mode 100644 index 000000000..e5d58b273 --- /dev/null +++ b/db/migrate/20140328035528_create_users.rb @@ -0,0 +1,42 @@ +class CreateUsers < ActiveRecord::Migration + def change + create_table :users do |t| + ## Database authenticatable + t.string :email, null: false, default: "" + t.string :encrypted_password, null: false, default: "" + t.string :name, null: false, default: "" + + ## Recoverable + t.string :reset_password_token + t.datetime :reset_password_sent_at + + ## Rememberable + t.datetime :remember_created_at + + ## Trackable + t.integer :sign_in_count, default: 0, null: false + t.datetime :current_sign_in_at + t.datetime :last_sign_in_at + t.string :current_sign_in_ip + t.string :last_sign_in_ip + + ## Confirmable + t.string :confirmation_token + t.datetime :confirmed_at + t.datetime :confirmation_sent_at + t.string :unconfirmed_email # Only if using reconfirmable + + ## Lockable + # t.integer :failed_attempts, default: 0, null: false # Only if lock strategy is :failed_attempts + # t.string :unlock_token # Only if unlock strategy is :email or :both + # t.datetime :locked_at + + t.timestamps + end + + add_index :users, :email, unique: true + add_index :users, :reset_password_token, unique: true + add_index :users, :confirmation_token, unique: true + # add_index :users, :unlock_token, unique: true + end +end diff --git a/db/migrate/20140328041648_create_api_applications.rb b/db/migrate/20140328041648_create_api_applications.rb new file mode 100644 index 000000000..501b655fb --- /dev/null +++ b/db/migrate/20140328041648_create_api_applications.rb @@ -0,0 +1,15 @@ +class CreateApiApplications < ActiveRecord::Migration + def change + create_table :api_applications do |t| + t.belongs_to :user + t.text :name + t.text :main_url + t.text :callback_url + t.text :api_token + + t.timestamps + end + add_index :api_applications, :user_id + add_index :api_applications, :api_token, unique: true + end +end diff --git a/db/migrate/20140328041859_create_contacts.rb b/db/migrate/20140328041859_create_contacts.rb new file mode 100644 index 000000000..e4f4e37d5 --- /dev/null +++ b/db/migrate/20140328041859_create_contacts.rb @@ -0,0 +1,16 @@ +class CreateContacts < ActiveRecord::Migration + def change + create_table :contacts do |t| + t.belongs_to :location + t.text :name + t.text :title + t.text :email + t.text :fax + t.text :phone + t.text :extension + + t.timestamps + end + add_index :contacts, :location_id + end +end diff --git a/db/migrate/20140328042108_create_faxes.rb b/db/migrate/20140328042108_create_faxes.rb new file mode 100644 index 000000000..df3aeff1c --- /dev/null +++ b/db/migrate/20140328042108_create_faxes.rb @@ -0,0 +1,12 @@ +class CreateFaxes < ActiveRecord::Migration + def change + create_table :faxes do |t| + t.belongs_to :location + t.text :number + t.text :department + + t.timestamps + end + add_index :faxes, :location_id + end +end diff --git a/db/migrate/20140328042218_create_mail_addresses.rb b/db/migrate/20140328042218_create_mail_addresses.rb new file mode 100644 index 000000000..2efa93f0f --- /dev/null +++ b/db/migrate/20140328042218_create_mail_addresses.rb @@ -0,0 +1,15 @@ +class CreateMailAddresses < ActiveRecord::Migration + def change + create_table :mail_addresses do |t| + t.belongs_to :location + t.text :attention + t.text :street + t.text :city + t.text :state + t.text :zip + + t.timestamps + end + add_index :mail_addresses, :location_id + end +end diff --git a/db/migrate/20140328042359_create_phones.rb b/db/migrate/20140328042359_create_phones.rb new file mode 100644 index 000000000..66aa766b7 --- /dev/null +++ b/db/migrate/20140328042359_create_phones.rb @@ -0,0 +1,14 @@ +class CreatePhones < ActiveRecord::Migration + def change + create_table :phones do |t| + t.belongs_to :location + t.text :number + t.text :department + t.text :extension + t.text :vanity_number + + t.timestamps + end + add_index :phones, :location_id + end +end diff --git a/db/migrate/20140328043104_create_services.rb b/db/migrate/20140328043104_create_services.rb new file mode 100644 index 000000000..3d37353d9 --- /dev/null +++ b/db/migrate/20140328043104_create_services.rb @@ -0,0 +1,22 @@ +class CreateServices < ActiveRecord::Migration + def change + create_table :services do |t| + t.belongs_to :location + t.text :audience + t.text :description + t.text :eligibility + t.text :fees + t.text :how_to_apply + t.text :name + t.text :short_desc + t.text :urls + t.text :wait + t.text :funding_sources + t.text :service_areas + t.text :keywords + + t.timestamps + end + add_index :services, :location_id + end +end diff --git a/db/migrate/20140328044447_create_categories.rb b/db/migrate/20140328044447_create_categories.rb new file mode 100644 index 000000000..d186fd448 --- /dev/null +++ b/db/migrate/20140328044447_create_categories.rb @@ -0,0 +1,12 @@ +class CreateCategories < ActiveRecord::Migration + def change + create_table :categories do |t| + t.text :name + t.text :oe_id + t.text :slug + + t.timestamps + end + add_index :categories, :slug, unique: true + end +end diff --git a/db/migrate/20140328052427_create_friendly_id_slugs.rb b/db/migrate/20140328052427_create_friendly_id_slugs.rb new file mode 100644 index 000000000..bb80e48d9 --- /dev/null +++ b/db/migrate/20140328052427_create_friendly_id_slugs.rb @@ -0,0 +1,18 @@ +class CreateFriendlyIdSlugs < ActiveRecord::Migration + + def self.up + create_table :friendly_id_slugs do |t| + t.string :slug, :null => false + t.integer :sluggable_id, :null => false + t.string :sluggable_type, :limit => 40 + t.datetime :created_at + end + add_index :friendly_id_slugs, :sluggable_id + add_index :friendly_id_slugs, [:slug, :sluggable_type], :unique => true + add_index :friendly_id_slugs, :sluggable_type + end + + def self.down + drop_table :friendly_id_slugs + end +end diff --git a/db/migrate/20140402222453_create_categories_services.rb b/db/migrate/20140402222453_create_categories_services.rb new file mode 100644 index 000000000..2b853ca77 --- /dev/null +++ b/db/migrate/20140402222453_create_categories_services.rb @@ -0,0 +1,10 @@ +class CreateCategoriesServices < ActiveRecord::Migration + def change + create_table :categories_services, id: false do |t| + t.belongs_to :category, null: false + t.belongs_to :service, null: false + end + add_index(:categories_services, [:service_id, :category_id], unique: true) + add_index(:categories_services, [:category_id, :service_id], unique: true) + end +end diff --git a/db/migrate/20140404220233_add_ancestry_to_category.rb b/db/migrate/20140404220233_add_ancestry_to_category.rb new file mode 100644 index 000000000..943479a94 --- /dev/null +++ b/db/migrate/20140404220233_add_ancestry_to_category.rb @@ -0,0 +1,6 @@ +class AddAncestryToCategory < ActiveRecord::Migration + def change + add_column :categories, :ancestry, :string + add_index :categories, :ancestry + end +end diff --git a/db/schema.rb b/db/schema.rb new file mode 100644 index 000000000..aa9fbabe4 --- /dev/null +++ b/db/schema.rb @@ -0,0 +1,199 @@ +# encoding: UTF-8 +# This file is auto-generated from the current state of the database. Instead +# of editing this file, please use the migrations feature of Active Record to +# incrementally modify your database, and then regenerate this schema definition. +# +# Note that this schema.rb definition is the authoritative source for your +# database schema. If you need to create the application database on another +# system, you should be using db:schema:load, not running all the migrations +# from scratch. The latter is a flawed and unsustainable approach (the more migrations +# you'll amass, the slower it'll run and the greater likelihood for issues). +# +# It's strongly recommended to check this file into your version control system. + +ActiveRecord::Schema.define(:version => 20140404220233) do + + create_table "addresses", :force => true do |t| + t.integer "location_id" + t.text "street" + t.text "city" + t.text "state" + t.text "zip" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "addresses", ["location_id"], :name => "index_addresses_on_location_id" + + create_table "api_applications", :force => true do |t| + t.integer "user_id" + t.text "name" + t.text "main_url" + t.text "callback_url" + t.text "api_token" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "api_applications", ["api_token"], :name => "index_api_applications_on_api_token", :unique => true + add_index "api_applications", ["user_id"], :name => "index_api_applications_on_user_id" + + create_table "categories", :force => true do |t| + t.text "name" + t.text "oe_id" + t.text "slug" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "ancestry" + end + + add_index "categories", ["ancestry"], :name => "index_categories_on_ancestry" + add_index "categories", ["slug"], :name => "index_categories_on_slug", :unique => true + + create_table "categories_services", :id => false, :force => true do |t| + t.integer "category_id", :null => false + t.integer "service_id", :null => false + end + + add_index "categories_services", ["category_id", "service_id"], :name => "index_categories_services_on_category_id_and_service_id", :unique => true + add_index "categories_services", ["service_id", "category_id"], :name => "index_categories_services_on_service_id_and_category_id", :unique => true + + create_table "contacts", :force => true do |t| + t.integer "location_id" + t.text "name" + t.text "title" + t.text "email" + t.text "fax" + t.text "phone" + t.text "extension" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "contacts", ["location_id"], :name => "index_contacts_on_location_id" + + create_table "faxes", :force => true do |t| + t.integer "location_id" + t.text "number" + t.text "department" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "faxes", ["location_id"], :name => "index_faxes_on_location_id" + + create_table "friendly_id_slugs", :force => true do |t| + t.string "slug", :null => false + t.integer "sluggable_id", :null => false + t.string "sluggable_type", :limit => 40 + t.datetime "created_at" + end + + add_index "friendly_id_slugs", ["slug", "sluggable_type"], :name => "index_friendly_id_slugs_on_slug_and_sluggable_type", :unique => true + add_index "friendly_id_slugs", ["sluggable_id"], :name => "index_friendly_id_slugs_on_sluggable_id" + add_index "friendly_id_slugs", ["sluggable_type"], :name => "index_friendly_id_slugs_on_sluggable_type" + + create_table "locations", :force => true do |t| + t.integer "organization_id" + t.text "accessibility" + t.text "admin_emails" + t.text "description" + t.text "emails" + t.text "hours" + t.text "kind" + t.float "latitude" + t.float "longitude" + t.text "languages" + t.text "name" + t.text "short_desc" + t.text "transportation" + t.text "urls" + t.text "slug" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "locations", ["latitude", "longitude"], :name => "index_locations_on_latitude_and_longitude" + add_index "locations", ["organization_id"], :name => "index_locations_on_organization_id" + add_index "locations", ["slug"], :name => "index_locations_on_slug", :unique => true + + create_table "mail_addresses", :force => true do |t| + t.integer "location_id" + t.text "attention" + t.text "street" + t.text "city" + t.text "state" + t.text "zip" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "mail_addresses", ["location_id"], :name => "index_mail_addresses_on_location_id" + + create_table "organizations", :force => true do |t| + t.text "name" + t.text "urls" + t.text "slug" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "organizations", ["slug"], :name => "index_organizations_on_slug", :unique => true + + create_table "phones", :force => true do |t| + t.integer "location_id" + t.text "number" + t.text "department" + t.text "extension" + t.text "vanity_number" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "phones", ["location_id"], :name => "index_phones_on_location_id" + + create_table "services", :force => true do |t| + t.integer "location_id" + t.text "audience" + t.text "description" + t.text "eligibility" + t.text "fees" + t.text "how_to_apply" + t.text "name" + t.text "short_desc" + t.text "urls" + t.text "wait" + t.text "funding_sources" + t.text "service_areas" + t.text "keywords" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "services", ["location_id"], :name => "index_services_on_location_id" + + create_table "users", :force => true do |t| + t.string "email", :default => "", :null => false + t.string "encrypted_password", :default => "", :null => false + t.string "name", :default => "", :null => false + t.string "reset_password_token" + t.datetime "reset_password_sent_at" + t.datetime "remember_created_at" + t.integer "sign_in_count", :default => 0, :null => false + t.datetime "current_sign_in_at" + t.datetime "last_sign_in_at" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" + t.string "confirmation_token" + t.datetime "confirmed_at" + t.datetime "confirmation_sent_at" + t.string "unconfirmed_email" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "users", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true + add_index "users", ["email"], :name => "index_users_on_email", :unique => true + add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + +end diff --git a/lib/tasks/setup_db.rake b/lib/tasks/setup_db.rake index 647adda3f..149120b21 100644 --- a/lib/tasks/setup_db.rake +++ b/lib/tasks/setup_db.rake @@ -1,40 +1,23 @@ require 'json' task :setup_db => [ - :load_cip_data, + :load_data, :create_categories] -task :load_cip_data => :environment do - puts "===> Populating the DB with San Mateo County, CA data." - puts "===> Hang tight, this will take a few seconds..." - +task :load_data => :environment do file = "data/sample_data.json" + puts "===> Populating the #{Rails.env} DB with #{file}..." + puts "===> Hang tight, this will take a few seconds..." + File.open(file).each do |line| data_item = JSON.parse(line) - org = Organization.create!(data_item) + org = Organization.create!(data_item.except("locations")) - locs = data_item["locs"] + locs = data_item["locations"] locs.each do |location| org.locations.create!(location) end - db_locs = org.locations # the newly created locations - - # Use .zip to pair each location hash from the json file to the - # newly-created location. - # This results in: [ [db_loc1, json_loc1], [db_loc2, json_loc2] ] - pairs = db_locs.zip(locs) - pairs.each do |pair| - - services = pair[1]["servs"] - if services.present? - services.each do |service| - pair[0].services.create!(service) - end - end - end end - puts "===> Cleaning up the DB..." - Organization.all.unset('locs') - Location.all.unset('servs') + puts "===> Done populating the DB with #{file}." end \ No newline at end of file diff --git a/script/drop b/script/drop deleted file mode 100755 index 4f85783ff..000000000 --- a/script/drop +++ /dev/null @@ -1,7 +0,0 @@ -#!/bin/sh - -set -e - -echo "===> Dropping all collections from the database..." -echo "===> Run 'script/bootstrap' to set everything up again." -rake db:drop \ No newline at end of file diff --git a/script/reset b/script/reset new file mode 100755 index 000000000..7ae45b8cc --- /dev/null +++ b/script/reset @@ -0,0 +1,13 @@ +#!/bin/sh + +set -e + +echo "===> Resetting the database..." +rake db:reset +echo "===> Done resetting the DB. Now populating it again..." +rake setup_db +echo "===> Done populating the DB. Now preparing the test database..." +rake db:test:load +echo "===> Creating the Elasticsearch index..." +script/tire --quiet +echo "===> All done!" \ No newline at end of file diff --git a/script/setup_db b/script/setup_db index 1709b5fa4..b120fc05a 100755 --- a/script/setup_db +++ b/script/setup_db @@ -2,11 +2,16 @@ set -e -echo "===> Setting up the DB." -rake setup_db +echo "===> Creating ohana-api_development database..." +rake db:create +echo "===> Loading the DB schema..." +rake db:schema:load + +echo "===> Creating ohana-api_test database..." +rake db:test:load -echo "===> Creating the DB indexes..." -rake db:mongoid:create_indexes --quiet +echo "===> Populating up the DB..." +rake setup_db echo "===> Creating the Elasticsearch index..." script/tire --quiet \ No newline at end of file diff --git a/script/setup_heroku b/script/setup_heroku index 23bdc38fc..5f0b2d843 100755 --- a/script/setup_heroku +++ b/script/setup_heroku @@ -28,8 +28,8 @@ then echo "Getting ready to install add-ons for $herokuApp" - echo "Installing MongoLab" - heroku addons:add mongolab --app $herokuApp + echo "Installing Postgres" + heroku addons:add heroku-postgresql --app $herokuApp echo "Installing Redis To Go" heroku addons:add redistogo --app $herokuApp @@ -48,7 +48,7 @@ then echo "Pushing code to Heroku now. This will take a few minutes..." git push heroku master - heroku run script/setup_db --app $herokuApp + heroku run script/setup_prod_db --app $herokuApp else echo "Please add your Heroku app name to the end of the command." echo "Usage: setup_heroku your_app_name" diff --git a/script/setup_prod_db b/script/setup_prod_db old mode 100755 new mode 100644 index 751aa0395..9d8e6c397 --- a/script/setup_prod_db +++ b/script/setup_prod_db @@ -2,8 +2,13 @@ set -e -echo "===> Creating the DB indexes..." -rake db:mongoid:create_indexes --quiet +echo "===> Creating ohana-api_production database..." +rake db:create +echo "===> Loading the DB schema..." +rake db:schema:load + +echo "===> Populating up the DB..." +rake setup_db echo "===> Creating the Elasticsearch index..." -script/tire --quiet +script/tire --quiet \ No newline at end of file diff --git a/script/test b/script/test new file mode 100755 index 000000000..82d287967 --- /dev/null +++ b/script/test @@ -0,0 +1,8 @@ +#!/bin/sh + +set -e + +echo "===> Updating the test DB." +rake db:migrate +rake db:test:prepare +rake db:test:load diff --git a/spec/api/categories_spec.rb b/spec/api/categories_spec.rb index a18b5f1b6..8a54c48ae 100644 --- a/spec/api/categories_spec.rb +++ b/spec/api/categories_spec.rb @@ -16,12 +16,12 @@ it "returns the category's children" do represented = [{ - "id" => "#{@food_child.id}", + "id" => @food_child.id, "depth" => @food_child.depth, "oe_id" => "101-01", "name" => "Emergency Food", - "parent_id" => "#{@food_child.parent_id}", - "slugs" => ["emergency-food"], + "parent_id" => @food_child.parent_id, + "slug" => "emergency-food", }] json.should == represented end @@ -58,17 +58,18 @@ describe "GET /api/categories" do before :each do @food = Category.create!(:name => "Food", :oe_id => "101") + @food.update_attributes!(name: "Emergency Food") get "/api/categories" end - it "displays the category's slugs" do + it "displays the category's latest slug" do represented = [{ - "id" => "#{@food.id}", + "id" => @food.id, "depth" => 0, "oe_id" => "101", - "name" => "Food", + "name" => "Emergency Food", "parent_id" => nil, - "slugs" => ["food"], + "slug" => "emergency-food", }] json.should == represented end diff --git a/spec/api/link_headers_spec.rb b/spec/api/link_headers_spec.rb index dcda3f16c..6a3915054 100644 --- a/spec/api/link_headers_spec.rb +++ b/spec/api/link_headers_spec.rb @@ -7,7 +7,7 @@ context "when on page 1 of 2" do before(:each) do - orgs = create_list(:location, 2) + locs = create_list(:location, 2) get 'api/search?keyword=parent' end diff --git a/spec/api/locations_spec.rb b/spec/api/locations_spec.rb index d38560525..93185a19c 100644 --- a/spec/api/locations_spec.rb +++ b/spec/api/locations_spec.rb @@ -4,6 +4,7 @@ describe "Location Requests" do include DefaultUserAgent + include Features::SessionHelpers describe "GET /api/locations" do xit "returns an empty array when no locations exist" do @@ -36,32 +37,127 @@ to eq("http://example.com/api/organizations/#{loc1.organization.id}") end - it "returns the correct info about the locations" do + it "displays address when present" do create(:location) get "/api/locations" json.first["address"]["street"].should == "1800 Easton Drive" end - it "doesn't include test data" do - create(:location) - create(:far_loc) + it "displays mail_address when present" do + loc = create(:location) + loc.create_mail_address!(attributes_for(:mail_address)) + loc.index.refresh + get "/api/locations" + json.first["mail_address"]["street"].should == "1 davis dr" + end + + it "displays contacts when present" do + loc = create(:location) + loc.contacts.create!(attributes_for(:contact)) + loc.index.refresh + get "/api/locations" + json.first["contacts"].first["title"].should == "CTO" + end + + it "displays faxes when present" do + loc = create(:location) + loc.faxes.create!(attributes_for(:fax)) + loc.index.refresh + get "/api/locations" + json.first["faxes"].first["number"].should == "703-555-1212" + end + + it "displays phones when present" do + loc = create(:location) + loc.phones.create!(attributes_for(:phone)) + loc.index.refresh get "/api/locations" - headers["X-Total-Count"].should == "1" - expect(json.first["name"]).to eq "VRS Services" + json.first["phones"].first["extension"].should == "x2000" + end + + context 'with nil fields' do + + before(:each) do + @loc = create(:loc_with_nil_fields) + end + + it 'does not return nil fields within Location' do + get "api/locations" + location_keys = json.first.keys + missing_keys = %w(accessibility admin_emails contacts emails faxes + hours languages mail_address phones transportation urls services) + missing_keys.each do |key| + location_keys.should_not include(key) + end + end + + it 'does not return nil fields within Contacts' do + attrs = attributes_for(:contact) + @loc.contacts.create!(attrs) + @loc.index.refresh + get "api/locations" + contact_keys = json.first["contacts"].first.keys + ["phone fax", "email"].each do |key| + contact_keys.should_not include(key) + end + end + + it 'does not return nil fields within Faxes' do + @loc.faxes.create!(attributes_for(:fax_with_no_dept)) + @loc.index.refresh + get "api/locations" + fax_keys = json.first["faxes"].first.keys + fax_keys.should_not include("department") + end + + it 'does not return nil fields within Phones' do + @loc.phones.create!(attributes_for(:phone_with_missing_fields)) + @loc.index.refresh + get "api/locations" + phone_keys = json.first["phones"].first.keys + ["extension", "vanity_number"].each do |key| + phone_keys.should_not include(key) + end + end + + it 'does not return nil fields within Organization' do + get "api/locations" + org_keys = json.first["organization"].keys + org_keys.should_not include("urls") + end + + it 'does not return nil fields within Services' do + attrs = attributes_for(:service) + @loc.services.create!(attrs) + @loc.index.refresh + get "api/locations" + service_keys = json.first["services"].first.keys + ["audience", "eligibility", "fees"].each do |key| + service_keys.should_not include(key) + end + end + end + + context "when location has no physical address" do + it 'does not return nil coordinates' do + create(:no_address) + get "api/locations" + location_keys = json.first.keys + location_keys.should_not include("coordinates") + end end end describe "GET /api/locations/:id" do context 'with valid data' do before :each do - service = create(:service) - @location = service.location + create_service get "/api/locations/#{@location.id}" end it "returns a status by id" do represented = { - "id" => "#{@location.id}", + "id" => @location.id, "accessibility"=>["Information on tape or in Braille", "Disabled Parking"], "address" => { "street" => @location.address.street, @@ -73,26 +169,21 @@ "description" => @location.description, "kind"=>"Other", "name" => @location.name, - "phones" => [{ - "number" => "650 851-1210", - "department" => "Information", - "phone_hours" => "(Monday-Friday, 9-12, 1-5)" - }], "short_desc" => "short description", - "slugs" => ["vrs-services"], + "slug" => "vrs-services", "updated_at" => @location.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z"), "url" => "http://example.com/api/locations/#{@location.id}", "services" => [{ - "id" => "#{@location.services.first.id}", + "id" => @location.services.reload.first.id, "description" => @location.services.first.description, "keywords" => @location.services.first.keywords, "name" => @location.services.first.name, "updated_at" => @location.services.first.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z") }], "organization" => { - "id" => "#{@location.organization.id}", + "id" => @location.organization.id, "name"=> "Parent Agency", - "slugs" => @location.organization.slugs, + "slug" => "parent-agency", "url" => "http://example.com/api/organizations/#{@location.organization.id}", "locations_url" => "http://example.com/api/organizations/#{@location.organization.id}/locations" } @@ -138,14 +229,6 @@ @loc = create(:loc_with_nil_fields) end - it 'does not return nil fields when visiting all locations' do - get "api/locations" - keys = json.first.keys - ["faxes", "fees", "email"].each do |key| - keys.should_not include(key) - end - end - it 'does not return nil fields when visiting one location' do get "api/locations/#{@loc.id}" keys = json.keys @@ -191,7 +274,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Kind is not included in the list" + json["message"].should include "Please enter a valid value for Kind" end it "validates the accessibility attribute" do @@ -199,11 +282,13 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Accessibility is invalid" + json["message"]. + should include "Please enter a valid value for Accessibility" end it "validates phone number" do - put "api/locations/#{@loc.id}", { :phones => [{ number: "703" }] }, + put "api/locations/#{@loc.id}", + { :phones_attributes => [{ number: "703" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) @@ -211,40 +296,17 @@ end it "validates fax number" do - put "api/locations/#{@loc.id}", { :faxes => [{ number: "703" }] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"].should include "Please enter a valid US fax number" - end - - it "validates fax number is a hash" do - put "api/locations/#{@loc.id}", { :faxes => ["703"] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"]. - should include "Fax must be a hash" - end - - it "validates fax number is an array" do - put "api/locations/#{@loc.id}", { :faxes => "703" }, + put "api/locations/#{@loc.id}", + { :faxes_attributes => [{ number: "703" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"]. - should include "Fax must be an array" + json["message"].should include "703 is not a valid US fax number" end - it "allows nil faxes attribute" do - put "api/locations/#{@loc.id}", { :faxes => nil }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(200) - end - it "allows empty array for faxes attribute" do - put "api/locations/#{@loc.id}", { :faxes => [] }, + it "allows empty array for faxes_attributes" do + put "api/locations/#{@loc.id}", { :faxes_attributes => [] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(200) @@ -259,62 +321,65 @@ it "validates contact phone" do put "api/locations/#{@loc.id}", - { :contacts => [{ name: "foo", title: "cfo", phone: "703" }] }, + { :contacts_attributes => [{ + name: "foo", title: "cfo", phone: "703" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Phone 703 is not a valid US phone number" + json["message"].should include "703 is not a valid US phone number" end it "validates contact fax" do put "api/locations/#{@loc.id}", - { :contacts => [{ name: "foo", title: "cfo", fax: "703" }] }, + { :contacts_attributes => [{ + name: "foo", title: "cfo", fax: "703" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Fax 703 is not a valid US fax number" + json["message"].should include "703 is not a valid US fax number" end it "validates contact email" do put "api/locations/#{@loc.id}", - { :contacts => [{ name: "foo", title: "cfo", email: "703" }] }, + { :contacts_attributes => [{ + name: "foo", title: "cfo", email: "703" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Email 703 is not a valid email" + json["message"].should include "703 is not a valid email" end it "validates admin email" do put "api/locations/#{@loc.id}", - { :admins => ["moncef-at-ohanapi.org"] }, + { :admin_emails => ["moncef-at-ohanapi.org"] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) json["message"]. - should include "Admins must be an array of valid email addresses" + should include "admin_emails must be an array of valid email addresses" end - it "validates admins is an array" do + it "validates admin_emails is an array" do put "api/locations/#{@loc.id}", - { :admins => "moncef@ohanapi.org" }, + { :admin_emails => "moncef@ohanapi.org" }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) json["message"]. - should include "Admins must be an array of valid email addresses" + should include "admin_emails must be an array of valid email addresses" end - it "allows empty admins array" do + it "allows empty admin_emails array" do put "api/locations/#{@loc.id}", - { :admins => [] }, + { :admin_emails => [] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(200) end - it "allows valid admins array" do + it "allows valid admin_emails array" do put "api/locations/#{@loc.id}", - { :admins => ["moncef@ohanapi.org"] }, + { :admin_emails => ["moncef@ohanapi.org"] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(200) @@ -322,7 +387,7 @@ it "requires contact name" do put "api/locations/#{@loc.id}", - { :contacts => [{ title: "cfo" }] }, + { :contacts_attributes => [{ title: "cfo" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) @@ -331,7 +396,7 @@ it "requires contact title" do put "api/locations/#{@loc.id}", - { :contacts => [{ name: "cfo" }] }, + { :contacts_attributes => [{ name: "cfo" }] }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) @@ -396,30 +461,35 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Urls badurl is not a valid URL" + json["message"].should include "badurl is not a valid URL" end it "validates location address state" do put "api/locations/#{@loc.id}", - { :address => {:state => "C" } }, + { :address_attributes => { + street: "123", city: "utopia", state: "C", zip: "12345" } + }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "State is too short (minimum is 2 characters)" + json["message"]. + should include "Please enter a valid 2-letter state abbreviation" end it "validates location address zip" do put "api/locations/#{@loc.id}", - { :address => {:zip => "1234" } }, + { :address_attributes => { + street: "123", city: "utopia", state: "CA", zip: "1234" } + }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Zip 1234 is not a valid ZIP code" + json["message"].should include "1234 is not a valid ZIP code" end it "requires location address street" do put "api/locations/#{@loc.id}", - { :address => { + { :address_attributes => { street: "", city: "utopia", state: "CA", zip: "12345" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -430,7 +500,7 @@ it "requires location address state" do put "api/locations/#{@loc.id}", - { :address => { + { :address_attributes => { street: "boo", city: "utopia", state: "", zip: "12345" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -441,7 +511,7 @@ it "requires location address city" do put "api/locations/#{@loc.id}", - { :address => { + { :address_attributes => { street: "funu", city: "", state: "CA", zip: "12345" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -452,7 +522,7 @@ it "requires location address zip" do put "api/locations/#{@loc.id}", - { :address => { + { :address_attributes => { street: "jam", city: "utopia", state: "CA", zip: "" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -463,25 +533,30 @@ it "validates location mail address state" do put "api/locations/#{@loc.id}", - { :mail_address => {:state => "C" } }, + { :mail_address_attributes => { + street: "123", city: "utopia", state: "C", zip: "12345" } + }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "State is too short (minimum is 2 characters)" + json["message"].should include "Please enter a valid 2-letter state abbreviation" end it "validates location mail address zip" do put "api/locations/#{@loc.id}", - { :mail_address => {:zip => "1234" } }, + { :mail_address_attributes => { + street: "123", city: "belmont", state: "CA", zip: "1234" + } + }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Zip 1234 is not a valid ZIP code" + json["message"].should include "1234 is not a valid ZIP code" end it "requires location mail_address street" do put "api/locations/#{@loc.id}", - { :mail_address => { + { :mail_address_attributes => { street: "", city: "utopia", state: "CA", zip: "12345" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -492,7 +567,7 @@ it "requires location mail_address state" do put "api/locations/#{@loc.id}", - { :mail_address => { + { :mail_address_attributes => { street: "boo", city: "utopia", state: "", zip: "12345" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -503,7 +578,7 @@ it "requires location mail_address city" do put "api/locations/#{@loc.id}", - { :mail_address => { + { :mail_address_attributes => { street: "funu", city: "", state: "CA", zip: "12345" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -514,7 +589,7 @@ it "requires location mail_address zip" do put "api/locations/#{@loc.id}", - { :mail_address => { + { :mail_address_attributes => { street: "jam", city: "utopia", state: "CA", zip: "" } }, { 'HTTP_X_API_TOKEN' => @token } @@ -531,13 +606,9 @@ json["message"].should include "A location must have at least one address type." end - it "doesn't geocode when address hasn't changed" do - @loc.coordinates = [] - @loc.save + xit "doesn't geocode when address hasn't changed" do put "api/locations/#{@loc.id}", { :kind => "entertainment" }, { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(@loc.coordinates).to eq([]) end it "geocodes when address has changed" do @@ -545,7 +616,7 @@ street: "1 davis drive", city: "belmont", state: "CA", zip: "94002" } coords = @loc.coordinates - put "api/locations/#{@loc.id}", { :address => address }, + put "api/locations/#{@loc.id}", { :address_attributes => address }, { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(@loc.coordinates).to_not eq(coords) @@ -555,7 +626,7 @@ put "api/locations/#{@loc.id}", { :address => nil, - :mail_address => { + :mail_address_attributes => { street: "1 davis drive", city: "belmont", state: "CA", zip: "94002" } @@ -591,7 +662,7 @@ :name => "new location", :description => "description", :short_desc => "short_desc", - :address => { + :address_attributes => { street: "main", city: "utopia", state: "CA", zip: "12345" }, :organization_id => org.id } @@ -649,9 +720,8 @@ describe "DELETE api/locations/:id" do before :each do - service = create(:service) - @service_id = service.id - @location = service.location + create_service + @service_id = @service.id @id = @location.id delete "api/locations/#{@id}", {}, { 'HTTP_X_API_TOKEN' => ENV["ADMIN_APP_TOKEN"] } @@ -664,7 +734,7 @@ it "deletes the service too" do expect { Service.find(@service_id) }. - to raise_error(Mongoid::Errors::DocumentNotFound) + to raise_error(ActiveRecord::RecordNotFound) end end diff --git a/spec/api/organizations_spec.rb b/spec/api/organizations_spec.rb index 57c0c0556..945b54f95 100644 --- a/spec/api/organizations_spec.rb +++ b/spec/api/organizations_spec.rb @@ -25,9 +25,9 @@ expect(response).to be_success expect(json.length).to eq(1) represented = [{ - "id" => "#{orgs.last.id}", + "id" => orgs.last.id, "name" => "#{orgs.last.name}", - "slugs" => orgs.last.slugs, + "slug" => orgs.last.slug, "url" => "http://example.com/api/organizations/#{orgs.last.id}", "locations_url" => "http://example.com/api/organizations/#{orgs.last.id}/locations" }] @@ -50,9 +50,9 @@ it "returns a status by id" do represented = { - "id" => "#{@org.id}", + "id" => @org.id, "name" => "#{@org.name}", - "slugs" => @org.slugs, + "slug" => "parent-agency", "url" => "http://example.com/api/organizations/#{@org.id}", "locations_url" => "http://example.com/api/organizations/#{@org.id}/locations" } @@ -128,7 +128,7 @@ name: "loc1", description: "training", short_desc: "short desc", - address: { + address_attributes: { street: "puma", city: "paris", state: "VA", @@ -140,7 +140,7 @@ name: "loc2", description: "training", short_desc: "short desc", - address: { + address_attributes: { street: "tiger", city: "paris", state: "VA", diff --git a/spec/api/search_spec.rb b/spec/api/search_spec.rb index f54f24d7a..52980c0c6 100644 --- a/spec/api/search_spec.rb +++ b/spec/api/search_spec.rb @@ -2,6 +2,7 @@ describe Ohana::API do include DefaultUserAgent + include Features::SessionHelpers describe "GET 'search'" do context "when none of the required parameters are present" do @@ -53,7 +54,7 @@ context 'with invalid radius' do before :each do - location = create(:farmers_market_loc) + location = create(:location) get "api/search?location=94403&radius=ads" end @@ -157,7 +158,7 @@ end it "finds the plural occurrence in service's keywords field" do - create(:service) + create_service get "api/search?keyword=pantry" keywords = json.first["services"].first["keywords"] keywords.should include "food pantries" @@ -184,7 +185,7 @@ end it "finds the plural occurrence in service's keywords field" do - create(:service) + create_service get "api/search?keyword=emergencies" keywords = json.first["services"].first["keywords"] keywords.should include "emergency" @@ -215,13 +216,8 @@ headers["X-Total-Count"].should == "1" end - it "filters out kind=other and kind=test" do + it "filters out kind=other" do get "api/search?exclude=Other" - headers["X-Total-Count"].should == "1" - end - - it "filters out kind=test" do - get "api/locations" headers["X-Total-Count"].should == "2" end @@ -248,33 +244,46 @@ create(:far_loc) create(:farmers_market_loc) cat = Category.create!(:name => "food") - FactoryGirl.create(:service_with_nil_fields, - :category_ids => ["#{cat.id}"]) + create_service + @service.category_ids = [cat.id] + @service.save + @location.index.refresh end it "boosts location whose services category name matches the query" do get "api/search?keyword=food" - headers["X-Total-Count"].should == "2" - json.first["name"].should == "Belmont Farmers Market with cat" + headers["X-Total-Count"].should == "3" + json.first["name"].should == "VRS Services" end end context "with category parameter" do before(:each) do create(:nearby_loc) - create(:location) + create(:farmers_market_loc) cat = Category.create!(:name => "Jobs") - FactoryGirl.create(:service_with_nil_fields, - :category_ids => ["#{cat.id}"]) + create_service + @service = @location.services.first + @service.category_ids = [cat.id] + @service.save + @location.index.refresh end + it "only returns locations whose category name matches the query" do get "api/search?category=Jobs" headers["X-Total-Count"].should == "1" - json.first["name"].should == "Belmont Farmers Market with cat" + json.first["name"].should == "VRS Services" end + it "only finds exact spelling matches for the category" do get "api/search?category=jobs" headers["X-Total-Count"].should == "0" end + + it "includes the depth attribute" do + get "api/search?category=Jobs" + expect(json.first["services"].first["categories"].first["depth"]). + to eq 0 + end end context "with org_name parameter" do diff --git a/spec/api/services_spec.rb b/spec/api/services_spec.rb index 463d48aa6..59601b4a5 100644 --- a/spec/api/services_spec.rb +++ b/spec/api/services_spec.rb @@ -4,10 +4,11 @@ describe "PUT Requests for Services" do include DefaultUserAgent + include Features::SessionHelpers describe "PUT /api/services/:id/" do before(:each) do - @service = create(:service) + create_service @token = ENV["ADMIN_APP_TOKEN"] end @@ -60,17 +61,17 @@ describe "Update a service without a valid token" do it "doesn't allow updating a service witout a valid token" do - service = create(:service) - put "api/services/#{service.id}", { :name => "new name" }, + create_service + put "api/services/#{@service.id}", { :name => "new name" }, { 'HTTP_X_API_TOKEN' => "invalid_token" } - service.reload + @service.reload expect(response.status).to eq(401) end end describe "PUT /api/services/:services_id/categories" do before(:each) do - @service = create(:service) + create_service @token = ENV["ADMIN_APP_TOKEN"] @food = Category.create!(:name => "Food", :oe_id => "101") end @@ -82,7 +83,7 @@ { 'HTTP_X_API_TOKEN' => @token } @service.reload expect(response).to be_success - json["category_ids"].first.should == "#{@food.id}" + json["categories"].first["name"].should == "Food" end end diff --git a/spec/factories/addresses.rb b/spec/factories/addresses.rb new file mode 100644 index 000000000..e7f9bd63d --- /dev/null +++ b/spec/factories/addresses.rb @@ -0,0 +1,31 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :address do + street "1800 Easton Drive" + city "Burlingame" + state "CA" + zip "94010" + end + + factory :near, class: Address do + street "250 Myrtle Road" + city "Burlingame" + state "CA" + zip "94010" + end + + factory :far, class: Address do + street "621 Magnolia Avenue" + city "Millbrae" + state "CA" + zip "94030" + end + + factory :far_west, class: Address do + street "8875 La Honda Road" + city "La Honda" + state "CA" + zip "94020" + end +end diff --git a/spec/factories/categories.rb b/spec/factories/categories.rb new file mode 100644 index 000000000..d75699e7e --- /dev/null +++ b/spec/factories/categories.rb @@ -0,0 +1,8 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :category do + name "Food" + oe_id "101" + end +end diff --git a/spec/factories/contacts.rb b/spec/factories/contacts.rb index 2df289c71..0911330e0 100644 --- a/spec/factories/contacts.rb +++ b/spec/factories/contacts.rb @@ -5,10 +5,4 @@ name "Moncef Belyamani" title "CTO" end - - factory :contact_with_nil_fields, class: Contact do - name "Moncef" - title "Chief Fun Officer" - email nil - end end \ No newline at end of file diff --git a/spec/factories/faxes.rb b/spec/factories/faxes.rb new file mode 100644 index 000000000..cd954e616 --- /dev/null +++ b/spec/factories/faxes.rb @@ -0,0 +1,12 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :fax do + number "703-555-1212" + department "Parks & Recreation" + end + + factory :fax_with_no_dept, class: Fax do + number "800-222-3333" + end +end diff --git a/spec/factories/locations.rb b/spec/factories/locations.rb index 72fc30a59..77f8e9e56 100644 --- a/spec/factories/locations.rb +++ b/spec/factories/locations.rb @@ -2,68 +2,27 @@ FactoryGirl.define do factory :location do - address { FactoryGirl.build(:address) } - #coordinates [-122.371448, 37.583849] - phones [{ number: "650 851-1210", - department: "Information", - phone_hours: "(Monday-Friday, 9-12, 1-5)" }] name "VRS Services" description "Provides jobs training" short_desc "short description" kind :other accessibility [:tape_braille, :disabled_parking] - association :organization + organization + address after(:create) { |loc| loc.index.refresh } factory :location_with_admin do - admins ["moncef@smcgov.org"] + admin_emails ["moncef@smcgov.org"] end end - factory :address do - street "1800 Easton Drive" - city "Burlingame" - state "CA" - zip "94010" - end - - factory :near, class: Address do - street "250 Myrtle Road" - city "Burlingame" - state "CA" - zip "94010" - end - - factory :far, class: Address do - street "621 Magnolia Avenue" - city "Millbrae" - state "CA" - zip "94030" - end - - factory :far_west, class: Address do - street "8875 La Honda Road" - city "La Honda" - state "CA" - zip "94020" - end - - factory :po_box, class: Address do - street "P.O Box 123" - city "La Honda" - state "CA" - zip "94020" - end - - factory :nearby_loc, class: Location do name "Library" description "great books about jobs" short_desc "short description" kind :human_services accessibility [:elevator] - address { FactoryGirl.build(:near) } - #coordinates [-122.362882, 37.588935] + association :address, factory: :near languages ["spanish", "Arabic"] association :organization, factory: :nearby_org after(:create) { |loc| loc.index.refresh } @@ -74,8 +33,8 @@ description "no coordinates" short_desc "short description" kind :test - mail_address { FactoryGirl.build(:po_box) } - association :organization + association :mail_address, factory: :po_box + organization after(:create) { |loc| loc.index.refresh } end @@ -83,13 +42,9 @@ name "Belmont Farmers Market" description "yummy food about jobs" short_desc "short description" - address { FactoryGirl.build(:far_west) } - #coordinates [-122.274369, 37.317983] - market_match true - payments ["Credit", "WIC", "SFMNP", "SNAP"] - products ["Cheese", "Flowers", "Eggs", "Seafood", "Herbs"] + association :address, factory: :far_west kind :farmers_markets - association :organization + organization after(:create) { |loc| loc.index.refresh } end @@ -97,11 +52,10 @@ name "Belmont Farmers Market" description "yummy food" short_desc "short description" - address { FactoryGirl.build(:far) } + association :address, factory: :far kind :test languages ["spanish", "Arabic"] - #coordinates [-122.3250474, 37.568272] - association :organization + organization after(:create) { |loc| loc.index.refresh } end @@ -109,12 +63,11 @@ name "Belmont Farmers Market with cat" description "yummy food" short_desc "short description" - faxes nil kind :farmers_markets - address { FactoryGirl.build(:address) } - contacts { [FactoryGirl.build(:contact_with_nil_fields)] } - coordinates [-122.3250474, 37.568272] - association :organization + address + organization + latitude 37.568272 + longitude -122.3250474 after(:create) { |loc| loc.index.refresh } end end \ No newline at end of file diff --git a/spec/factories/mail_addresses.rb b/spec/factories/mail_addresses.rb index 38905f7c3..f3b0c3e63 100644 --- a/spec/factories/mail_addresses.rb +++ b/spec/factories/mail_addresses.rb @@ -6,5 +6,11 @@ city "Belmont" state "CA" zip "90210" + + factory :po_box do + street "P.O Box 123" + city "La Honda" + zip "94020" + end end end \ No newline at end of file diff --git a/spec/factories/phones.rb b/spec/factories/phones.rb new file mode 100644 index 000000000..e8d04445b --- /dev/null +++ b/spec/factories/phones.rb @@ -0,0 +1,15 @@ +# Read about factories at https://github.com/thoughtbot/factory_girl + +FactoryGirl.define do + factory :phone do + number "650 851-1210" + department "Information" + extension "x2000" + vanity_number "800-FLY-AWAY" + end + + factory :phone_with_missing_fields, class: Phone do + number "650 851-1210" + department "Information" + end +end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 2cbe815d0..cc5d7f78e 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -3,8 +3,6 @@ name "Burlingame, Easton Branch" description "yoga classes" keywords ["library", "food pantries", "stood famps", "emergency"] - association :location - after(:create) { |s| s.location.index.refresh } end factory :service_with_nil_fields, class: Service do @@ -12,7 +10,5 @@ description "SNAP market" keywords ["health", "yoga"] fees nil - association :location, factory: :loc_with_nil_fields - after(:create) { |s| s.location.index.refresh } end end \ No newline at end of file diff --git a/spec/factories/users.rb b/spec/factories/users.rb index e4e817c57..4897fe9fb 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -8,18 +8,12 @@ password_confirmation 'mong01dtest' # required if the Devise Confirmable module is used confirmed_at Time.now - end - - factory :user_with_app, :class => :user do - name 'User with app' - email 'app@example.com' - password 'mong01dtest' - password_confirmation 'mong01dtest' - confirmed_at Time.now - api_applications [{ name: "first app", - main_url: "http://ohanapi", - callback_url: "http://callme" }] + factory :user_with_app do + after(:create) do |user| + create(:api_application, user: user) + end + end end factory :unconfirmed_user, :class => :user do diff --git a/spec/models/address_spec.rb b/spec/models/address_spec.rb index 415af4e9a..e439629b5 100644 --- a/spec/models/address_spec.rb +++ b/spec/models/address_spec.rb @@ -6,8 +6,10 @@ it { should be_valid } + it { should belong_to :location } + describe "invalid data" do - before(:each) do + before(:each) do @attrs = { street: "123", city: "belmont", state: "CA", zip: "90210" } end diff --git a/spec/models/api_application_spec.rb b/spec/models/api_application_spec.rb index 1c535aac9..1a5569491 100644 --- a/spec/models/api_application_spec.rb +++ b/spec/models/api_application_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' -# Uses the nifty mongoid-rspec matchers -# https://github.com/evansagge/mongoid-rspec +# Uses the nifty shoulda-matchers +# https://github.com/thoughtbot/shoulda-matchers describe ApiApplication do - it { should be_embedded_in :user } + it { should belong_to :user } it { should validate_presence_of(:name) } it { should validate_presence_of(:main_url) } @@ -13,8 +13,10 @@ it { should_not allow_mass_assignment_of(:api_token) } - it { should validate_format_of(:main_url).to_allow("http://localhost") - .not_to_allow("ohanapi.org") } - it { should validate_format_of(:callback_url).to_allow("https://localhost") - .not_to_allow("http://") } + it { should allow_value("http://localhost", "https://localhost"). + for(:main_url) } + + it { should_not allow_value("http://").for(:main_url) } + + it { should_not allow_value("http://").for(:callback_url) } end \ No newline at end of file diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb new file mode 100644 index 000000000..ce050d447 --- /dev/null +++ b/spec/models/category_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe Category do + subject { build(:category) } + + it { should be_valid } + + it { should have_and_belong_to_many(:services) } + + it { should allow_mass_assignment_of(:name) } + it { should allow_mass_assignment_of(:oe_id) } +end diff --git a/spec/models/fax_spec.rb b/spec/models/fax_spec.rb new file mode 100644 index 000000000..96feab46e --- /dev/null +++ b/spec/models/fax_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe Fax do + subject { build(:fax) } + + it { should be_valid } + + it { should belong_to(:location) } + + it { should allow_mass_assignment_of(:number) } + it { should allow_mass_assignment_of(:department) } + + it { should validate_presence_of(:number) } + + it { should normalize_attribute(:number). + from(" 800-555-1212 "). + to("800-555-1212") } + + it { should normalize_attribute(:department). + from(" Youth Development "). + to("Youth Development") } + + it { should allow_value("703-555-1212", "800.123.4567"). + for(:number) } + + it do + should_not allow_value("703-"). + for(:number). + with_message('703- is not a valid US fax number') + end +end diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 3ca987094..60786d9d2 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -6,6 +6,16 @@ it { should be_valid } + # Associations + it { should belong_to :organization } + it { should have_one :address } + it { should have_many :contacts } + it { should have_many :faxes } + it { should have_one :mail_address } + it { should have_many :phones } + it { should have_many :services } + + # Instance methods it { should respond_to(:full_address) } its(:full_address) { should == "#{subject.address.street}, " + "#{subject.address.city}, " + "#{subject.address.state} " + @@ -16,6 +26,7 @@ "#{subject.address.city}, " + "#{subject.address.state} " + "#{subject.address.zip}" } + # Attribute normalization it { should normalize_attribute(:urls). from(" http://www.codeforamerica.org "). to("http://www.codeforamerica.org") } @@ -57,13 +68,7 @@ end context "without an address" do - subject { build(:location, address: {}) } - it { should_not be_valid } - end - - context "with a non-US phone" do - subject { build(:location, - phones: [{ "number" => "33 6 65 08 51 12" }]) } + subject { build(:location, address: nil) } it { should_not be_valid } end @@ -76,6 +81,11 @@ subject { build(:location, emails: ["moncef.blahcom"]) } it { should_not be_valid } end + + context "admin email without @" do + subject { build(:location, admin_emails: ["moncef.blahcom"]) } + it { should_not be_valid } + end end describe "valid data" do @@ -94,12 +104,6 @@ it { should be_valid } end - context "with US phone containing dots" do - subject { build(:location, - phones: [{ "number" => "123.456.7890" }]) } - it { should be_valid } - end - context "email with trailing whitespace" do subject { build(:location, emails: ["moncef@blah.com "]) } it { should be_valid } diff --git a/spec/models/phone_spec.rb b/spec/models/phone_spec.rb new file mode 100644 index 000000000..04e6169d9 --- /dev/null +++ b/spec/models/phone_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe Phone do + + subject { build(:phone) } + + it { should be_valid } + + it { should belong_to :location } + + it { should allow_mass_assignment_of(:number) } + it { should allow_mass_assignment_of(:extension) } + it { should allow_mass_assignment_of(:department) } + it { should allow_mass_assignment_of(:vanity_number) } + + it { should normalize_attribute(:number). + from(" 703 555-1212 ").to("703 555-1212") } + + it { should normalize_attribute(:extension). + from(" x5104 ").to("x5104") } + + it { should normalize_attribute(:department). + from(" Intake ").to("Intake") } + + it { should normalize_attribute(:vanity_number). + from(" 800 YOU-NEED ").to("800 YOU-NEED") } + + describe "with invalid data" do + context "without a number" do + subject { build(:phone, number: nil)} + it { should_not be_valid } + end + + context "with an empty number" do + subject { build(:phone, number: "")} + it { should_not be_valid } + end + + context "with a non-US phone" do + subject { build(:phone, number: "33 6 65 08 51 12") } + it { should_not be_valid } + end + end + + describe "valid data" do + context "with US phone containing dots" do + subject { build(:phone, number: "123.456.7890") } + it { should be_valid } + end + end +end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb new file mode 100644 index 000000000..43bb442d4 --- /dev/null +++ b/spec/models/service_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' + +describe Service do + subject { build(:service) } + + it { should be_valid } + + it { should belong_to(:location) } + + it { should have_and_belong_to_many(:categories) } + + it { should allow_mass_assignment_of(:audience) } + it { should allow_mass_assignment_of(:description) } + it { should allow_mass_assignment_of(:eligibility) } + it { should allow_mass_assignment_of(:fees) } + it { should allow_mass_assignment_of(:funding_sources) } + it { should allow_mass_assignment_of(:keywords) } + it { should allow_mass_assignment_of(:how_to_apply) } + it { should allow_mass_assignment_of(:name) } + it { should allow_mass_assignment_of(:service_areas) } + it { should allow_mass_assignment_of(:short_desc) } + it { should allow_mass_assignment_of(:urls) } + it { should allow_mass_assignment_of(:wait) } + + it { should normalize_attribute(:audience). + from(" youth "). + to("youth") } + + it { should normalize_attribute(:description). + from(" Youth Development "). + to("Youth Development") } + + it { should normalize_attribute(:eligibility). + from(" Youth Development "). + to("Youth Development") } + + it { should normalize_attribute(:fees). + from(" Youth Development "). + to("Youth Development") } + + it { should normalize_attribute(:how_to_apply). + from(" Youth Development "). + to("Youth Development") } + + it { should normalize_attribute(:name). + from(" Youth Development "). + to("Youth Development") } + + it { should normalize_attribute(:short_desc). + from(" Youth Development "). + to("Youth Development") } + + it { should normalize_attribute(:wait). + from(" Youth Development "). + to("Youth Development") } + + it { should allow_value(["http://www.codeforamerica.org"]).for(:urls) } + +end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 746a663ed..1373e2a37 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -# Uses the nifty mongoid-rspec matchers -# https://github.com/evansagge/mongoid-rspec +# Uses the nifty shoulda-matchers +# https://github.com/thoughtbot/shoulda-matchers describe User do before(:each) do @@ -16,7 +16,7 @@ User.create!(@attr) end - it { should embed_many :api_applications } + it { should have_many :api_applications } it { should allow_mass_assignment_of(:name) } it { should allow_mass_assignment_of(:email) } @@ -24,24 +24,21 @@ it { should allow_mass_assignment_of(:password_confirmation) } it { should allow_mass_assignment_of(:remember_me) } - it { should have_field(:name).of_type(String).with_default_value_of("") } - it { should have_field(:encrypted_password).of_type(String) - .with_default_value_of("") } + it { should have_db_column(:name).of_type(:string).with_options(default: "") } + it { should have_db_column(:encrypted_password).of_type(:string). + with_options(default: "") } it { should validate_presence_of(:name) } it { should validate_presence_of(:email) } it { should validate_presence_of(:password) } - it { should validate_length_of(:password).greater_than(8) } + it { should ensure_length_of(:password).is_at_least(8) } - it { should validate_format_of(:email).to_allow("user@foo.com") - .not_to_allow("user@foo,com") } + it { should allow_value("user@foo.com", "THE_USER@foo.bar.org", + "first.last@foo.jp").for(:email) } - it { should validate_format_of(:email).to_allow("THE_USER@foo.bar.org") - .not_to_allow("user_at_foo.org") } - - it { should validate_format_of(:email).to_allow("first.last@foo.jp") - .not_to_allow("example.user@foo.") } + it { should_not allow_value("user@foo,com", "user_at_foo.org", + "example.user@foo.").for(:email) } it { should validate_uniqueness_of(:email) } diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7486234be..5e177873d 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -46,10 +46,11 @@ config.order = "random" config.before(:suite) do - # adding "[:mongoid]" is required when using ActiveAdmin. - # Otherwise, all tests will fail with the error - # ActiveRecord::ConnectionNotEstablished - DatabaseCleaner[:mongoid].strategy = :truncation + DatabaseCleaner.clean_with(:truncation) + end + + config.before(:each) do + DatabaseCleaner.strategy = :transaction end config.before(:each) do diff --git a/spec/support/features/session_helpers.rb b/spec/support/features/session_helpers.rb index 6d6784517..47fcfba7c 100644 --- a/spec/support/features/session_helpers.rb +++ b/spec/support/features/session_helpers.rb @@ -32,5 +32,12 @@ def visit_app(name, main_url) visit('/api_applications') click_link "#{name} (#{main_url})" end + + def create_service + @location = create(:location) + @service = @location.services.create!(attributes_for(:service)) + @location.reload + @location.index.refresh + end end end \ No newline at end of file diff --git a/spec/support/mongoid.rb b/spec/support/mongoid.rb deleted file mode 100644 index 6289c6930..000000000 --- a/spec/support/mongoid.rb +++ /dev/null @@ -1,3 +0,0 @@ -RSpec.configure do |config| - config.include Mongoid::Matchers -end From 3808b4ca47c8db2bb9d35e1c5de4e3dc7747a25d Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 20:35:08 -0400 Subject: [PATCH 02/17] Change permissions for setup_prod_db So it can run on Heroku. --- script/setup_prod_db | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 script/setup_prod_db diff --git a/script/setup_prod_db b/script/setup_prod_db old mode 100644 new mode 100755 From aa6e233819432022bf19d582db532e6c09e33e4b Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 22:39:06 -0400 Subject: [PATCH 03/17] Specify app when fetching SEARCHBOX_URL --- script/setup_heroku | 2 +- script/setup_prod_db | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/script/setup_heroku b/script/setup_heroku index 5f0b2d843..5bad243e5 100755 --- a/script/setup_heroku +++ b/script/setup_heroku @@ -41,7 +41,7 @@ then heroku addons:add searchbox --app $herokuApp echo "Setting ELASTICSEARCH_URL" - elasticsearchUrl=$(heroku config:get SEARCHBOX_URL) + elasticsearchUrl=$(heroku config:get SEARCHBOX_URL --app $herokuApp) heroku config:set ELASTICSEARCH_URL=$elasticsearchUrl --app $herokuApp echo "All done setting up env vars and add-ons." diff --git a/script/setup_prod_db b/script/setup_prod_db index 9d8e6c397..d32170d83 100755 --- a/script/setup_prod_db +++ b/script/setup_prod_db @@ -3,7 +3,7 @@ set -e echo "===> Creating ohana-api_production database..." -rake db:create +rake db:create:all echo "===> Loading the DB schema..." rake db:schema:load From 21644cf0abe2a4dd1d60556ee6285bafcb15b584 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 22:47:35 -0400 Subject: [PATCH 04/17] Remove redundant message about elasticsearch index --- script/setup_prod_db | 1 - 1 file changed, 1 deletion(-) diff --git a/script/setup_prod_db b/script/setup_prod_db index d32170d83..661deccad 100755 --- a/script/setup_prod_db +++ b/script/setup_prod_db @@ -10,5 +10,4 @@ rake db:schema:load echo "===> Populating up the DB..." rake setup_db -echo "===> Creating the Elasticsearch index..." script/tire --quiet \ No newline at end of file From 25d0b706a8053ceade459c802e343705e283bed4 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 23:06:14 -0400 Subject: [PATCH 05/17] Update travis to use Postgres --- .travis.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 1320b8a51..6506066c4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,13 +2,14 @@ before_install: gem update bundler bundler_args: --without assets:development:production language: ruby rvm: -- 2.1.1 + - 2.1.1 +addons: + postgresql: "9.3" services: -- mongodb -- redis-server -- elasticsearch + - redis-server + - elasticsearch before_script: -- rake db:mongoid:create_indexes + - psql -c 'create database ohana-api_test;' -U postgres notifications: campfire: rooms: From 4ce7a4738d407c801a10ad419c19ffc3818016a1 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 23:21:43 -0400 Subject: [PATCH 06/17] Remove hyphen from test database name Travis didn't like that. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 6506066c4..db7ae477d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ services: - redis-server - elasticsearch before_script: - - psql -c 'create database ohana-api_test;' -U postgres + - psql -c 'create database ohana_api_test;' -U postgres notifications: campfire: rooms: From b6cdc991e3238545d9f5961d3ad5bd96b6a30a65 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 23:57:54 -0400 Subject: [PATCH 07/17] Create a separate database config file for travis http://docs.travis-ci.com/user/using-postgresql/ --- .travis.yml | 1 + config/database.travis.yml | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 config/database.travis.yml diff --git a/.travis.yml b/.travis.yml index db7ae477d..d10953158 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ services: - redis-server - elasticsearch before_script: + - cp config/database.travis.yml config/database.yml - psql -c 'create database ohana_api_test;' -U postgres notifications: campfire: diff --git a/config/database.travis.yml b/config/database.travis.yml new file mode 100644 index 000000000..253a85334 --- /dev/null +++ b/config/database.travis.yml @@ -0,0 +1,4 @@ +test: + adapter: postgresql + database: travis_ci_test + username: postgres \ No newline at end of file From 822f8fbb31bac1c5ab00a871be1436a0fb366d93 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Sun, 6 Apr 2014 23:58:21 -0400 Subject: [PATCH 08/17] Remove redundant message from reset script --- script/reset | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/script/reset b/script/reset index 7ae45b8cc..574a53368 100755 --- a/script/reset +++ b/script/reset @@ -8,6 +8,6 @@ echo "===> Done resetting the DB. Now populating it again..." rake setup_db echo "===> Done populating the DB. Now preparing the test database..." rake db:test:load -echo "===> Creating the Elasticsearch index..." + script/tire --quiet echo "===> All done!" \ No newline at end of file From b77d5cc3613557e8902b55cddf43d8cb626a7287 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Mon, 7 Apr 2014 00:24:56 -0400 Subject: [PATCH 09/17] Remove psql line from travis --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d10953158..d76f4e2ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,7 +10,6 @@ services: - elasticsearch before_script: - cp config/database.travis.yml config/database.yml - - psql -c 'create database ohana_api_test;' -U postgres notifications: campfire: rooms: From eee8c737e406d8c5ca6f482f661afb94c2a55675 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Mon, 7 Apr 2014 00:51:28 -0400 Subject: [PATCH 10/17] Put bootstrap-sass in assets group Travis runs bundle without assets, but bootstrap-sass requires sass-rails, which is in assets. --- Gemfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index fd8c8b14e..0c26a24bd 100644 --- a/Gemfile +++ b/Gemfile @@ -9,11 +9,11 @@ group :assets do gem 'sass-rails', '~> 3.2.3' gem 'coffee-rails', '~> 3.2.1' gem 'uglifier', '>= 1.0.3' + gem 'bootstrap-sass' end # Front end gem 'jquery-rails' -gem 'bootstrap-sass' gem "haml-rails", ">= 0.4" # Server for deployment From b98ea8bd0a73ad64413243e7364248bf30d06be1 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Mon, 7 Apr 2014 01:03:01 -0400 Subject: [PATCH 11/17] Tell Travis to create test DB --- .travis.yml | 1 + config/database.travis.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index d76f4e2ee..704894358 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ services: - redis-server - elasticsearch before_script: + - psql -c 'create database ohana_api_test;' -U postgres - cp config/database.travis.yml config/database.yml notifications: campfire: diff --git a/config/database.travis.yml b/config/database.travis.yml index 253a85334..b64de343d 100644 --- a/config/database.travis.yml +++ b/config/database.travis.yml @@ -1,4 +1,4 @@ test: adapter: postgresql - database: travis_ci_test + database: ohana_api_test username: postgres \ No newline at end of file From d1e774a4041449f90378e3041ae7fa7ee57c6506 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Mon, 7 Apr 2014 01:13:50 -0400 Subject: [PATCH 12/17] Add DB rake tasks to travis --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 704894358..9b3d4bb0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,6 +11,8 @@ services: before_script: - psql -c 'create database ohana_api_test;' -U postgres - cp config/database.travis.yml config/database.yml + - rake db:schema:load + - rake db:test:load notifications: campfire: rooms: From 933eb62349f4144e77640e83b6cc69909a8faec5 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Thu, 10 Apr 2014 09:43:04 -0400 Subject: [PATCH 13/17] Upgrade Rails from 3.2.17 to 4.0.4 --- .gitignore | 7 +- Gemfile | 31 ++-- Gemfile.lock | 157 +++++++++--------- README.md | 31 ++-- Rakefile | 1 - app/api/api.rb | 3 +- app/assets/javascripts/application.js | 6 +- app/assets/stylesheets/application.css.scss | 8 +- app/controllers/application_controller.rb | 4 +- app/models/address.rb | 2 +- app/models/category.rb | 15 +- app/models/contact.rb | 3 +- app/models/location.rb | 29 +++- app/models/mail_address.rb | 2 +- app/models/organization.rb | 21 ++- app/models/service.rb | 4 +- bin/autospec | 16 ++ bin/bundle | 3 + bin/rails | 4 + bin/rake | 4 + bin/rspec | 16 ++ config.ru | 4 +- config/application.rb | 52 +----- config/boot.rb | 4 +- config/environment.rb | 4 +- config/environments/development.rb | 55 +++--- config/environments/production.rb | 73 ++++---- config/environments/staging.rb | 47 ++++-- config/environments/test.rb | 28 ++-- .../initializers/filter_parameter_logging.rb | 4 + config/initializers/friendly_id.rb | 88 ++++++++++ config/initializers/inflections.rb | 11 +- config/initializers/mime_types.rb | 1 - config/initializers/secret_token.rb | 14 +- config/initializers/session_store.rb | 5 - config/initializers/wrap_parameters.rb | 8 +- config/locales/en.yml | 22 ++- config/routes.rb | 6 +- db/schema.rb | 139 ++++++++-------- lib/tasks/oe.rake | 1 + public/404.html | 69 ++++++-- public/422.html | 69 ++++++-- public/500.html | 67 ++++++-- script/rails | 6 - spec/api/locations_spec.rb | 34 ++-- spec/api/organizations_spec.rb | 8 +- spec/api/search_spec.rb | 4 +- spec/controllers/status_controller_spec.rb | 8 +- spec/factories/categories.rb | 10 ++ spec/models/category_spec.rb | 38 +++++ spec/models/contact_spec.rb | 20 +-- spec/models/location_spec.rb | 34 ++++ spec/models/organization_spec.rb | 26 +++ spec/spec_helper.rb | 12 +- 54 files changed, 871 insertions(+), 467 deletions(-) create mode 100755 bin/autospec create mode 100755 bin/bundle create mode 100755 bin/rails create mode 100755 bin/rake create mode 100755 bin/rspec create mode 100644 config/initializers/filter_parameter_logging.rb create mode 100644 config/initializers/friendly_id.rb delete mode 100755 script/rails diff --git a/.gitignore b/.gitignore index 0554c2cfd..af69fbc44 100644 --- a/.gitignore +++ b/.gitignore @@ -1,14 +1,15 @@ -# See http://help.github.com/ignore-files/ for more about ignoring files. +# See https://help.github.com/articles/ignoring-files for more about ignoring files. # # If you find yourself ignoring temporary files generated by your text editor # or operating system, you probably want to add a global ignore instead: -# git config --global core.excludesfile ~/.gitignore_global +# git config --global core.excludesfile '~/.gitignore_global' -# Ignore bundler config +# Ignore bundler config. /.bundle # Ignore the default SQLite database. /db/*.sqlite3 +/db/*.sqlite3-journal # Ignore all logfiles and tempfiles. /log/* diff --git a/Gemfile b/Gemfile index 0c26a24bd..7af63091a 100644 --- a/Gemfile +++ b/Gemfile @@ -1,20 +1,23 @@ source 'https://rubygems.org' ruby '2.1.1' -gem 'rails', '3.2.17' +gem 'rails', '~> 4.0.4' gem "pg" -group :assets do - gem 'sass-rails', '~> 3.2.3' - gem 'coffee-rails', '~> 3.2.1' - gem 'uglifier', '>= 1.0.3' - gem 'bootstrap-sass' -end +gem 'sass-rails', '~> 4.0.2' +gem 'coffee-rails', '~> 4.0.0' +gem 'uglifier', '>= 1.3.0' +gem 'bootstrap-sass' + +# Rails 4 +gem 'protected_attributes' +#gem 'rails-observers' +#gem 'rails-perftest' # Front end gem 'jquery-rails' -gem "haml-rails", ">= 0.4" +gem "haml-rails", '~> 0.5.3' # Server for deployment gem "passenger" @@ -23,18 +26,18 @@ group :production, :staging do gem 'rails_12factor' # Heroku recommended end -# Test coverage -gem 'coveralls', require: false - group :test, :development do - gem "rspec-rails", ">= 2.12.2" + gem "rspec-rails", '~> 2.14.2' gem "factory_girl_rails", ">= 4.2.0" end group :test do gem "database_cleaner", ">= 1.0.0.RC1" gem "capybara" + #gem "minitest" gem 'shoulda-matchers' + # Test coverage + gem 'coveralls', require: false end group :development do @@ -54,7 +57,7 @@ gem "geocoder", :git => "git://github.com/alexreisner/geocoder.git", :ref => "35 gem "redis" # Format validation for URLs, phone numbers, zipcodes -gem "validates_formatting_of" +gem "validates_formatting_of", '~> 0.8.1' # CORS support gem 'rack-cors', :require => 'rack/cors' @@ -97,4 +100,4 @@ gem "tire", :git => "git://github.com/monfresh/tire.git", :ref => "2d174e792a" # Nested categories for OpenEligibility gem "ancestry" -gem "friendly_id", "~> 4.0.10" \ No newline at end of file +gem "friendly_id", "~> 5.0.3" \ No newline at end of file diff --git a/Gemfile.lock b/Gemfile.lock index dff9a8c4e..526ad45c5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -40,39 +40,35 @@ GIT GEM remote: https://rubygems.org/ specs: - actionmailer (3.2.17) - actionpack (= 3.2.17) + actionmailer (4.0.4) + actionpack (= 4.0.4) mail (~> 2.5.4) - actionpack (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - builder (~> 3.0.0) + actionpack (4.0.4) + activesupport (= 4.0.4) + builder (~> 3.1.0) erubis (~> 2.7.0) - journey (~> 1.0.4) - rack (~> 1.4.5) - rack-cache (~> 1.2) - rack-test (~> 0.6.1) - sprockets (~> 2.2.1) - activemodel (3.2.17) - activesupport (= 3.2.17) - builder (~> 3.0.0) - activerecord (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - arel (~> 3.0.2) - tzinfo (~> 0.3.29) - activeresource (3.2.17) - activemodel (= 3.2.17) - activesupport (= 3.2.17) - activesupport (3.2.17) - i18n (~> 0.6, >= 0.6.4) - multi_json (~> 1.0) + rack (~> 1.5.2) + rack-test (~> 0.6.2) + activemodel (4.0.4) + activesupport (= 4.0.4) + builder (~> 3.1.0) + activerecord (4.0.4) + activemodel (= 4.0.4) + activerecord-deprecated_finders (~> 1.0.2) + activesupport (= 4.0.4) + arel (~> 4.0.0) + activerecord-deprecated_finders (1.0.3) + activesupport (4.0.4) + i18n (~> 0.6, >= 0.6.9) + minitest (~> 4.2) + multi_json (~> 1.3) + thread_safe (~> 0.1) + tzinfo (~> 0.3.37) ancestry (2.0.0) activerecord (>= 3.0.0) ansi (1.4.3) - arel (3.0.3) + arel (4.0.2) arrayfields (4.9.2) - atomic (1.1.16) attribute_normalizer (1.1.0) awesome_print (1.2.0) axiom-types (0.0.5) @@ -86,7 +82,7 @@ GEM debug_inspector (>= 0.0.1) bootstrap-sass (2.3.2.2) sass (~> 3.2) - builder (3.0.4) + builder (3.1.4) cane (2.6.1) parallel capybara (2.2.1) @@ -110,9 +106,9 @@ GEM coderay (1.1.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) - coffee-rails (3.2.2) + coffee-rails (4.0.1) coffee-script (>= 2.2.0) - railties (~> 3.2.0) + railties (>= 4.0.0, < 5.0) coffee-script (2.2.0) coffee-script-source execjs @@ -157,8 +153,8 @@ GEM flog (4.2.0) ruby_parser (~> 3.1, > 3.1.0) sexp_processor (~> 4.4) - friendly_id (4.0.10.1) - activerecord (>= 3.0, < 4.0) + friendly_id (5.0.3) + activerecord (>= 4.0.0) grape (0.6.1) activesupport builder @@ -174,18 +170,17 @@ GEM multi_json (>= 1.3.2) haml (4.0.5) tilt - haml-rails (0.4) - actionpack (>= 3.1, < 4.1) - activesupport (>= 3.1, < 4.1) - haml (>= 3.1, < 4.1) - railties (>= 3.1, < 4.1) + haml-rails (0.5.3) + actionpack (>= 4.0.1) + activesupport (>= 4.0.1) + haml (>= 3.1, < 5.0) + railties (>= 4.0.1) hashie (2.0.5) hashr (0.0.22) hike (1.2.3) hirb (0.7.1) i18n (0.6.9) ice_nine (0.11.0) - journey (1.0.4) jquery-rails (3.1.0) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) @@ -221,6 +216,7 @@ GEM metric_fu-Saikuro (1.1.3) mime-types (1.25.1) mini_portile (0.5.2) + minitest (4.7.5) multi_json (1.9.2) multi_xml (0.5.5) newrelic-grape (1.3.1) @@ -237,33 +233,31 @@ GEM rake (>= 0.8.1) pg (0.17.1) polyglot (0.3.4) + protected_attributes (1.0.7) + activemodel (>= 4.0.1, < 5.0) pry (0.9.12.6) coderay (~> 1.0) method_source (~> 0.8) slop (~> 3.4) quiet_assets (1.0.2) railties (>= 3.1, < 5.0) - rack (1.4.5) + rack (1.5.2) rack-accept (0.4.5) rack (>= 0.4) - rack-cache (1.2) - rack (>= 0.4) rack-cors (0.2.9) rack-mount (0.8.3) rack (>= 1.0.0) - rack-ssl (1.3.3) - rack rack-test (0.6.2) rack (>= 1.0) rack-timeout (0.0.4) - rails (3.2.17) - actionmailer (= 3.2.17) - actionpack (= 3.2.17) - activerecord (= 3.2.17) - activeresource (= 3.2.17) - activesupport (= 3.2.17) - bundler (~> 1.0) - railties (= 3.2.17) + rails (4.0.4) + actionmailer (= 4.0.4) + actionpack (= 4.0.4) + activerecord (= 4.0.4) + activesupport (= 4.0.4) + bundler (>= 1.3.0, < 2.0) + railties (= 4.0.4) + sprockets-rails (~> 2.0.0) rails_12factor (0.0.2) rails_serve_static_assets rails_stdout_logging @@ -278,16 +272,12 @@ GEM ruby-progressbar rails_serve_static_assets (0.0.1) rails_stdout_logging (0.0.2) - railties (3.2.17) - actionpack (= 3.2.17) - activesupport (= 3.2.17) - rack-ssl (~> 1.3.2) + railties (4.0.4) + actionpack (= 4.0.4) + activesupport (= 4.0.4) rake (>= 0.8.7) - rdoc (~> 3.4) - thor (>= 0.14.6, < 2.0) - rake (10.1.1) - rdoc (3.12.2) - json (~> 1.4) + thor (>= 0.18.1, < 2.0) + rake (10.2.2) redcard (1.1.0) redis (3.0.7) reek (1.3.6) @@ -303,7 +293,7 @@ GEM rspec-expectations (2.14.5) diff-lcs (>= 1.1.3, < 2.0) rspec-mocks (2.14.6) - rspec-rails (2.14.1) + rspec-rails (2.14.2) actionpack (>= 3.0) activemodel (>= 3.0) activesupport (>= 3.0) @@ -318,10 +308,11 @@ GEM ruby_parser (3.4.1) sexp_processor (~> 4.1) sass (3.2.14) - sass-rails (3.2.6) - railties (~> 3.2.0) - sass (>= 3.1.10) - tilt (~> 1.3) + sass-rails (4.0.3) + railties (>= 4.0.0, < 5.0) + sass (~> 3.2.0) + sprockets (~> 2.8, <= 2.11.0) + sprockets-rails (~> 2.0) sexp_processor (4.4.2) shoulda-matchers (2.5.0) activesupport (>= 3.0.0) @@ -331,28 +322,31 @@ GEM simplecov-html (~> 0.8.0) simplecov-html (0.8.0) slop (3.5.0) - sprockets (2.2.2) + sprockets (2.11.0) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) + sprockets-rails (2.0.1) + actionpack (>= 3.0) + activesupport (>= 3.0) + sprockets (~> 2.8) swagger-ui_rails (0.1.7) term-ansicolor (1.2.2) tins (~> 0.8) - thor (0.19.0) - thread_safe (0.3.1) - atomic (>= 1.1.7, < 2) + thor (0.19.1) + thread_safe (0.3.3) tilt (1.4.1) tins (0.13.2) treetop (1.4.15) polyglot polyglot (>= 0.3.1) tzinfo (0.3.39) - uglifier (2.2.1) + uglifier (2.5.0) execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - validates_formatting_of (0.8.0) - activemodel (~> 3.0) + json (>= 1.8.0) + validates_formatting_of (0.8.1) + activemodel (~> 4.0) virtus (1.0.1) axiom-types (~> 0.0.5) coercible (~> 1.0) @@ -373,7 +367,7 @@ DEPENDENCIES binding_of_caller (>= 0.7.1) bootstrap-sass capybara - coffee-rails (~> 3.2.1) + coffee-rails (~> 4.0.0) coveralls dalli database_cleaner (>= 1.0.0.RC1) @@ -381,12 +375,12 @@ DEPENDENCIES enumerize factory_girl_rails (>= 4.2.0) figaro - friendly_id (~> 4.0.10) + friendly_id (~> 5.0.3) geocoder! grape grape-entity grape-swagger! - haml-rails (>= 0.4) + haml-rails (~> 0.5.3) jquery-rails kaminari! kgio @@ -396,16 +390,17 @@ DEPENDENCIES newrelic_rpm passenger pg + protected_attributes quiet_assets (>= 1.0.2) rack-cors rack-timeout - rails (= 3.2.17) + rails (~> 4.0.4) rails_12factor redis - rspec-rails (>= 2.12.2) - sass-rails (~> 3.2.3) + rspec-rails (~> 2.14.2) + sass-rails (~> 4.0.2) shoulda-matchers swagger-ui_rails tire! - uglifier (>= 1.0.3) - validates_formatting_of + uglifier (>= 1.3.0) + validates_formatting_of (~> 0.8.1) diff --git a/README.md b/README.md index 7ee61a956..8abc99d59 100644 --- a/README.md +++ b/README.md @@ -50,10 +50,10 @@ You can also try it from the Rails console, mimicking how the API would do it wh ## Stack Overview * Ruby version 2.1.1 -* Rails version 3.2.17 +* Rails version 4.0.4 * Postgres * Redis -* ElasticSearch +* ElasticSearch <=1.0.1 * API framework: Grape * Testing Frameworks: RSpec, Factory Girl, Capybara @@ -65,7 +65,7 @@ Please note that the instructions below have only been tested on OS X. If you ar ###Prerequisites -#### Git, Ruby 2.1+, Rails 3.2.17+ (+ Homebrew on OS X) +#### Git, Ruby 2.1.1, Rails 4.0.4 (+ Homebrew on OS X) **OS X**: [Set up a dev environment on OS X with Homebrew, Git, RVM, Ruby, and Rails](http://www.moncefbelyamani.com/how-to-install-xcode-homebrew-git-rvm-ruby-on-mac/) **Windows**: Try [RailsInstaller](http://railsinstaller.org), along with some of these [tutorials](https://www.google.com/search?q=install+rails+on+windows) if you get stuck. @@ -100,15 +100,21 @@ Follow the Homebrew instructions if you want Redis to start automatically every See the Download page on Redis.io for steps to install on other systems: [http://redis.io/download](http://redis.io/download) -#### ElasticSearch +#### ElasticSearch <=1.0.1 **OS X** +Please make sure you are using Elasticsearch 1.0.1 or lower. This app is currently not compatible with Elasticsearch 1.1.0. + On OS X, the easiest way to install ElasticSearch is with Homebrew: - brew install elasticsearch + brew install https://raw.github.com/Homebrew/homebrew/9b8103f6fb570dc3a5ce5b5b84cb76fb6915cace/Library/Formula/elasticsearch.rb Follow the Homebrew instructions to launch ElasticSearch. +If you already had 1.0.1 and then upgraded to 1.1.0, you can switch back to 1.0.1 with this command: + + brew switch elasticsearch 1.0.1 + **Other** Visit the Download page on elasticsearch.org for steps to install on other systems: [http://www.elasticsearch.org/download/](http://www.elasticsearch.org/download/) @@ -132,9 +138,11 @@ If you get a `permission denied` message, set the correct permissions: then run `script/bootstrap` again. -In `config/application.yml`, set the `ADMIN_APP_TOKEN` environment variable so that the tests can pass, and so you can run the [Ohana API Admin](https://github.com/codeforamerica/ohana-api-admin) app locally: +In `config/application.yml`, set the following environment variables so that the tests can pass, and so you can run the [Ohana API Admin](https://github.com/codeforamerica/ohana-api-admin) app locally: ADMIN_APP_TOKEN: your_token + API_BASE_URL: http://localhost:8080/api/ + API_BASE_HOST: http://localhost:8080/ `your_token` can be any string you want for testing purposes, but in production, you should use a random string, which you can generate from the command line: @@ -145,6 +153,8 @@ Start the app locally on port 8080 using Passenger: passenger start -p 8080 +If for some reason, you can't run on port 8080, make sure you update `API_BASE_URL` and `API_BASE_HOST` in `config/application.yml` if you change the port number. You'll also need to update the port number in `OHANA_API_ENDPOINT` when running the Admin Interface. + ### Verify the app is returning JSON To see all locations, 30 per page: @@ -241,13 +251,8 @@ If you want to wipe out the local test DB and reset it with the sample data, run script/reset -### User authentication and emails -The app allows developers to sign up for an account via the home page (http://localhost:8080), but all email addresses need to be verified first. In development, the app sends email via Gmail. If you want to try this email process on your local machine, you need to configure your Gmail username and password by creating a file called `application.yml` in the config folder, and entering your info like so: - - GMAIL_USERNAME: your_email@gmail.com - GMAIL_PASSWORD: your_password - -`application.yml` is ignored in `.gitignore`, so you don't have to worry about exposing your credentials if you ever push code to GitHub. If you don't care about email interactions, but still want to try out the signed in experience, you can [sign in](http://localhost:8080/users/sign_in) with either of the users whose username and password are stored in [db/seeds.rb](https://github.com/codeforamerica/ohana-api/blob/master/db/seeds.rb). +### User authentication +The app automatically sets up users you can [sign in](http://localhost:8080/users/sign_in) with. Their username and password are stored in [db/seeds.rb](https://github.com/codeforamerica/ohana-api/blob/master/db/seeds.rb). ### Test the app diff --git a/Rakefile b/Rakefile index 9925ec4ed..9bdb3c2c1 100644 --- a/Rakefile +++ b/Rakefile @@ -1,4 +1,3 @@ -#!/usr/bin/env rake # Add your own tasks in files placed in lib/tasks ending in .rake, # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. diff --git a/app/api/api.rb b/app/api/api.rb index d8f6028f0..eb8a1e797 100644 --- a/app/api/api.rb +++ b/app/api/api.rb @@ -25,7 +25,6 @@ def valid_api_token? token = env["HTTP_X_API_TOKEN"].to_s token.present? && token == ENV["ADMIN_APP_TOKEN"] end - end rescue_from ActiveRecord::RecordNotFound do @@ -41,7 +40,7 @@ def valid_api_token? elsif e.record.errors.first.first == :accessibility message = "Please enter a valid value for Accessibility" else - message = e.record.errors.first.last + message = e.message end rack_response({ diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 252125946..22f9d88a4 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -5,10 +5,10 @@ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. // // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the -// the compiled file. +// compiled file. // -// WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD -// GO AFTER THE REQUIRES BELOW. +// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details +// about supported directives. // //= require jquery //= require jquery_ujs diff --git a/app/assets/stylesheets/application.css.scss b/app/assets/stylesheets/application.css.scss index 15519509a..2b7c41f6c 100644 --- a/app/assets/stylesheets/application.css.scss +++ b/app/assets/stylesheets/application.css.scss @@ -5,12 +5,14 @@ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. * - * You're free to add application-wide styles to this file and they'll appear at the top of the - * compiled file, but it's generally better to create a new file per style scope. + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any styles + * defined in the other CSS/SCSS files in this directory. It is generally better to create a new + * file per style scope. * - *= require_self *= require swagger-ui *= require_tree . + *= require_self */ .content { diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f98552b37..5ea7c4e96 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,5 +1,7 @@ class ApplicationController < ActionController::Base - protect_from_forgery + # Prevent CSRF attacks by raising an exception (with: :exception), + # or, for APIs, you may want to use :null_session instead. + protect_from_forgery with: :null_session # This is to prevent the app from returning a 500 Internal Server Error # when a valid Accept Header is passed to a non-API URL, such as the diff --git a/app/models/address.rb b/app/models/address.rb index 9f1b605c7..08816d400 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -6,7 +6,7 @@ class Address < ActiveRecord::Base #validates_presence_of :location validates_presence_of :street, :city, :state, :zip, - message: lambda { |x,y| "#{y[:attribute]} can't be blank" } + message: "can't be blank for Address" validates_length_of :state, :maximum => 2, :minimum => 2, message: "Please enter a valid 2-letter state abbreviation" diff --git a/app/models/category.rb b/app/models/category.rb index f8e190773..f34ffb176 100644 --- a/app/models/category.rb +++ b/app/models/category.rb @@ -2,13 +2,22 @@ class Category < ActiveRecord::Base has_ancestry extend FriendlyId - friendly_id :name, use: [:slugged, :history] + friendly_id :slug_candidates, use: [:history] + + # Try building a slug based on the following fields in + # increasing order of specificity. + def slug_candidates + [ + :name, + [:name, :oe_id] + ] + end attr_accessible :name, :oe_id - has_and_belongs_to_many :services, uniq: true + has_and_belongs_to_many :services, -> { uniq } - self.include_root_in_json = false + validates_presence_of :name, :oe_id, message: "can't be blank for Category" include Grape::Entity::DSL entity do diff --git a/app/models/contact.rb b/app/models/contact.rb index 2ea9029bf..ac79509da 100644 --- a/app/models/contact.rb +++ b/app/models/contact.rb @@ -5,8 +5,7 @@ class Contact < ActiveRecord::Base normalize_attributes :name, :title, :email, :fax, :phone, :extension - validates_presence_of :name, :title, - message: lambda { |x,y| "#{y[:attribute]} can't be blank for Contact" } + validates_presence_of :name, :title, message: "can't be blank for Contact" validates_formatting_of :email, with: /.+@.+\..+/i, allow_blank: true, message: "%{value} is not a valid email" diff --git a/app/models/location.rb b/app/models/location.rb index f0f37b321..aa90e4d75 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -1,6 +1,24 @@ class Location < ActiveRecord::Base extend FriendlyId - friendly_id :name, use: [:slugged, :history] + friendly_id :slug_candidates, use: [:history] + + # Try building a slug based on the following fields in + # increasing order of specificity. + def slug_candidates + [ + :name, + [:name, :address_street], + [:name, :mail_address_city] + ] + end + + def address_street + address.street if address.present? + end + + def mail_address_city + mail_address.city if mail_address.present? + end extend Enumerize serialize :accessibility, Array @@ -72,7 +90,8 @@ class Location < ActiveRecord::Base # validates_presence_of :name, :hours, :phones validates_presence_of :description, :organization, :name, - message: lambda { |x,y| "#{y[:attribute]} can't be blank" } + message: "can't be blank for Location" + validate :address_presence ## Uncomment the line below if you want to require a short description. @@ -170,8 +189,6 @@ def needs_geocoding? # INDEX_NAME is defined in config/initializers/tire.rb index_name INDEX_NAME - self.include_root_in_json = false - # Defines the JSON output of search results. # Since search returns locations, we also want to include # the location's parent organization info, as well as the @@ -200,7 +217,7 @@ def to_indexed_json #hash[:organization].merge!("slugs" => organization.slugs.map(&:slug)) hash.merge!("accessibility" => accessibility.map(&:text)) hash.merge!("kind" => kind.text) if kind.present? - remove_nil_fields(hash,[:organization,:contacts,:faxes,:phones,:services]) + remove_nil_fields(hash,["organization","contacts","faxes","phones","services"]) hash.to_json end @@ -467,7 +484,7 @@ def address_presence end def url - "#{Rails.application.routes.url_helpers.root_url}locations/#{self.id}" + "#{ENV["API_BASE_URL"]}locations/#{self.id}" end def self.smc_service_areas diff --git a/app/models/mail_address.rb b/app/models/mail_address.rb index a5e3283b8..57cd0cf2b 100644 --- a/app/models/mail_address.rb +++ b/app/models/mail_address.rb @@ -7,7 +7,7 @@ class MailAddress < ActiveRecord::Base normalize_attributes :street, :city, :state, :zip validates_presence_of :street, :city, :state, :zip, - message: lambda { |x,y| "#{y[:attribute]} can't be blank" } + message: "can't be blank for Mail Address" validates_length_of :state, :maximum => 2, :minimum => 2, message: "Please enter a valid 2-letter state abbreviation" diff --git a/app/models/organization.rb b/app/models/organization.rb index f853c3ecc..325264d2f 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -2,7 +2,16 @@ class Organization < ActiveRecord::Base attr_accessible :name, :urls extend FriendlyId - friendly_id :name, use: [:slugged, :history] + friendly_id :slug_candidates, use: [:history] + + # Try building a slug based on the following fields in + # increasing order of specificity. + def slug_candidates + [ + :name, + [:name, :domain_name] + ] + end has_many :locations, dependent: :destroy #accepts_nested_attributes_for :locations @@ -15,23 +24,21 @@ class Organization < ActiveRecord::Base paginates_per Rails.env.test? ? 1 : 30 - self.include_root_in_json = false - after_save :refresh_tire_index def refresh_tire_index self.locations.each { |loc| loc.tire.update_index } end def url - "#{root_url}organizations/#{self.id}" + "#{ENV["API_BASE_URL"]}organizations/#{self.id}" end def locations_url - "#{root_url}organizations/#{self.id}/locations" + "#{ENV["API_BASE_URL"]}organizations/#{self.id}/locations" end - def root_url - Rails.application.routes.url_helpers.root_url + def domain_name + URI.parse(urls.first).host.gsub(/^www\./, '') if urls.present? end include Grape::Entity::DSL diff --git a/app/models/service.rb b/app/models/service.rb index a67933bec..73c0e841b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -1,7 +1,7 @@ class Service < ActiveRecord::Base belongs_to :location, touch: true - has_and_belongs_to_many :categories, uniq: true + has_and_belongs_to_many :categories, -> { uniq } #accepts_nested_attributes_for :categories #has_many :schedules @@ -83,8 +83,6 @@ def valid_service_areas ] end - self.include_root_in_json = false - include Grape::Entity::DSL entity do expose :id diff --git a/bin/autospec b/bin/autospec new file mode 100755 index 000000000..64dcb9cb0 --- /dev/null +++ b/bin/autospec @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# +# This file was generated by Bundler. +# +# The application 'autospec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('rspec-core', 'autospec') diff --git a/bin/bundle b/bin/bundle new file mode 100755 index 000000000..66e9889e8 --- /dev/null +++ b/bin/bundle @@ -0,0 +1,3 @@ +#!/usr/bin/env ruby +ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) +load Gem.bin_path('bundler', 'bundle') diff --git a/bin/rails b/bin/rails new file mode 100755 index 000000000..728cd85aa --- /dev/null +++ b/bin/rails @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' diff --git a/bin/rake b/bin/rake new file mode 100755 index 000000000..17240489f --- /dev/null +++ b/bin/rake @@ -0,0 +1,4 @@ +#!/usr/bin/env ruby +require_relative '../config/boot' +require 'rake' +Rake.application.run diff --git a/bin/rspec b/bin/rspec new file mode 100755 index 000000000..0c86b5c6f --- /dev/null +++ b/bin/rspec @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# +# This file was generated by Bundler. +# +# The application 'rspec' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('rspec-core', 'rspec') diff --git a/config.ru b/config.ru index d39ce626d..6ac7b4508 100644 --- a/config.ru +++ b/config.ru @@ -2,6 +2,4 @@ require ::File.expand_path('../config/environment', __FILE__) -NewRelic::Agent.manual_start - -run OhanaApi::Application +run Rails.application diff --git a/config/application.rb b/config/application.rb index 3ab73b7ca..0ca887527 100644 --- a/config/application.rb +++ b/config/application.rb @@ -4,16 +4,11 @@ require "active_record/railtie" require "action_controller/railtie" require "action_mailer/railtie" -require "active_resource/railtie" require "sprockets/railtie" -# # require "rails/test_unit/railtie" -if defined?(Bundler) - # If you precompile assets before deploying to production, use this line - Bundler.require(*Rails.groups(:assets => %w(development test))) - # If you want your assets lazily compiled in production, use this line - # Bundler.require(:default, :assets, Rails.env) -end +# Require the gems listed in Gemfile, including any gems +# you've limited to :test, :development, or :production. +Bundler.require(*Rails.groups) module OhanaApi class Application < Rails::Application @@ -33,18 +28,6 @@ class Application < Rails::Application # Application configuration should go into files in config/initializers # -- all .rb files in that directory are automatically loaded. - # Custom directories with classes and modules you want to be autoloadable. - # config.autoload_paths += %W(#{config.root}/extras) - config.autoload_paths += %W(#{config.root}/lib) - - - # Only load the plugins named here, in the order given (default is alphabetical). - # :all can be used as a placeholder for all plugins not explicitly named. - # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - - # Activate observers that should always be running. - # config.active_record.observers = :cacher, :garbage_collector, :forum_observer - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. config.time_zone = 'Pacific Time (US & Canada)' @@ -53,35 +36,6 @@ class Application < Rails::Application # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de - # Configure the default encoding used in templates for Ruby 1.9. - config.encoding = "utf-8" - - # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password, :password_confirmation] - - # Enable escaping HTML in JSON. - config.active_support.escape_html_entities_in_json = true - - # Use SQL instead of Active Record's schema dumper when creating the database. - # This is necessary if your schema can't be completely dumped by the schema dumper, - # like if you have constraints or database-specific column types - # config.active_record.schema_format = :sql - - # Enforce whitelist mode for mass assignment. - # This will create an empty whitelist of attributes available for mass-assignment for all models - # in your app. As such, your models will need to explicitly whitelist or blacklist accessible - # parameters by using an attr_accessible or attr_protected declaration. - # config.active_record.whitelist_attributes = true - - # Enable the asset pipeline - config.assets.enabled = true - - # Required for Heroku to work - config.assets.initialize_on_precompile = false - - # Version of your assets, change this if you want to expire all your assets - config.assets.version = '1.0' - ## Uncomment the following 2 lines if you want to activate rate limiting ## You would also need to uncomment the following specs: ## no_api_token_spec.rb, api_token_spec.rb, no_user_agent_spec.rb, diff --git a/config/boot.rb b/config/boot.rb index 4489e5868..5e5f0c1fa 100644 --- a/config/boot.rb +++ b/config/boot.rb @@ -1,6 +1,4 @@ -require 'rubygems' - # Set up gems listed in the Gemfile. ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) -require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) +require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) diff --git a/config/environment.rb b/config/environment.rb index 4364e467f..9eb6390fb 100644 --- a/config/environment.rb +++ b/config/environment.rb @@ -1,5 +1,5 @@ -# Load the rails application +# Load the rails application. require File.expand_path('../application', __FILE__) -# Initialize the rails application +# Initialize the Rails application. OhanaApi::Application.initialize! diff --git a/config/environments/development.rb b/config/environments/development.rb index 5bd1aebba..4d5c735a2 100644 --- a/config/environments/development.rb +++ b/config/environments/development.rb @@ -1,48 +1,42 @@ OhanaApi::Application.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. # In the development environment your application's code is reloaded on # every request. This slows down response time but is perfect for development # since you don't have to restart the web server when you make code changes. config.cache_classes = false - # Log error messages when you accidentally call methods on nil. - config.whiny_nils = true + # Do not eager load code on boot. + config.eager_load = false - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - # ActionMailer Config + # Don't care if the mailer can't send. + config.action_mailer.raise_delivery_errors = false config.action_mailer.default_url_options = { :host => 'localhost:8080' } - # A dummy setup for development - no deliveries, but logged - config.action_mailer.delivery_method = :smtp - config.action_mailer.perform_deliveries = false - config.action_mailer.raise_delivery_errors = true - config.action_mailer.default :charset => "utf-8" - - config.action_mailer.smtp_settings = { - address: "smtp.gmail.com", - port: 587, - domain: "ohanapi.org", - authentication: "plain", - enable_starttls_auto: true, - user_name: ENV["GMAIL_USERNAME"], - password: ENV["GMAIL_PASSWORD"] - } - - # Print deprecation notices to the Rails logger - config.active_support.deprecation = :log - # Only use best-standards-support built into browsers - config.action_dispatch.best_standards_support = :builtin + # Print deprecation notices to the Rails logger. + config.active_support.deprecation = :log - # Do not compress assets - config.assets.compress = false + # Raise an error on page load if there are pending migrations. + config.active_record.migration_error = :page_load - # Expands the lines which load the assets + # Debug mode disables concatenation and preprocessing of assets. + # This option may cause significant delays in view rendering with a large + # number of complex assets. config.assets.debug = true + # Adds additional error checking when serving assets at runtime. + # Checks for improperly declared sprockets dependencies. + # Raises helpful error messages. + # This is a 4.1 setting. Uncomment after upgrading. + #config.assets.raise_runtime_errors = true + + # Raises error for missing translations. + # config.action_view.raise_on_missing_translations = true + # Bullet gem config # config.after_initialize do # Bullet.enable = true @@ -50,9 +44,4 @@ # Bullet.bullet_logger = true # Bullet.rails_logger = true # end - - # Roar-Rails hypermedia links base url - #config.representer.default_url_options = { :host => "localhost:8080/api" } - - config.api_base_host = "localhost:8080/api" end diff --git a/config/environments/production.rb b/config/environments/production.rb index dd942aa60..4e6730d14 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -1,60 +1,73 @@ OhanaApi::Application.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. - # Code is not reloaded between requests + # Code is not reloaded between requests. config.cache_classes = true - # Full error reports are disabled and caching is turned on + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + + # Full error reports are disabled and caching is turned on. config.consider_all_requests_local = false config.action_controller.perform_caching = true - # Disable Rails's static asset server (Apache or nginx will already do this) + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true + + # Disable Rails's static asset server (Apache or nginx will already do this). config.serve_static_assets = false - # Compress JavaScripts and CSS - config.assets.compress = true + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass - # Don't fallback to assets pipeline if a precompiled asset is missed + # Do not fallback to assets pipeline if a precompiled asset is missed. config.assets.compile = false - # Generate digests for assets URLs + # Generate digests for assets URLs. config.assets.digest = true - # Defaults to nil and saved in location specified by config.assets.prefix - # config.assets.manifest = YOUR_PATH + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.0' - # Specifies the header that your server uses for sending files + # Specifies the header that your server uses for sending files. # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for nginx # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # See everything in the log (default is :info) - # config.log_level = :debug + # Set to :debug to see everything in the log. + config.log_level = :info - # Prepend all log lines with the following tags + # Prepend all log lines with the following tags. # config.log_tags = [ :subdomain, :uuid ] - # Use a different logger for distributed setups + # Use a different logger for distributed setups. # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - # Use a different cache store in production + # Use a different cache store in production. # config.cache_store = :mem_cache_store - # Enable serving of images, stylesheets, and JavaScripts from an asset server + # Enable serving of images, stylesheets, and JavaScripts from an asset server. # config.action_controller.asset_host = "http://assets.example.com" - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # config.assets.precompile += %w( search.js ) - # ActionMailer Config - # Setup for production - deliveries, no errors raised + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + config.action_mailer.raise_delivery_errors = true config.action_mailer.default_url_options = { :host => ENV["MAILER_URL"] } config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true - # Disable delivery errors, bad email addresses will be ignored - config.action_mailer.raise_delivery_errors = false + config.action_mailer.default :charset => "utf-8" config.action_mailer.smtp_settings = { @@ -66,16 +79,20 @@ :authentication => :plain } - # Enable threaded mode - # config.threadsafe! - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to - # the I18n.default_locale when a translation can not be found) + # the I18n.default_locale when a translation can not be found). config.i18n.fallbacks = true - # Send deprecation notices to registered listeners + # Send deprecation notices to registered listeners. config.active_support.deprecation = :notify - config.api_base_host = ENV["API_BASE_HOST"] + # Disable automatic flushing of the log to improve performance. + # config.autoflush_log = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + # Do not dump schema after migrations. + # This is a Rails 4.1 setting. Uncomment after upgrading. + #config.active_record.dump_schema_after_migration = false end diff --git a/config/environments/staging.rb b/config/environments/staging.rb index e792d510d..c96c2106f 100644 --- a/config/environments/staging.rb +++ b/config/environments/staging.rb @@ -4,15 +4,27 @@ # Code is not reloaded between requests config.cache_classes = true + # Eager load code on boot. This eager loads most of Rails and + # your application in memory, allowing both threaded web servers + # and those relying on copy on write to perform better. + # Rake tasks automatically ignore this option for performance. + config.eager_load = true + # Full error reports are disabled and caching is turned on config.consider_all_requests_local = false config.action_controller.perform_caching = true + # Enable Rack::Cache to put a simple HTTP cache in front of your application + # Add `rack-cache` to your Gemfile before enabling this. + # For large-scale production use, consider using a caching reverse proxy like nginx, varnish or squid. + # config.action_dispatch.rack_cache = true + # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false - # Compress JavaScripts and CSS - config.assets.compress = true + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = false @@ -20,8 +32,8 @@ # Generate digests for assets URLs config.assets.digest = true - # Defaults to nil and saved in location specified by config.assets.prefix - # config.assets.manifest = YOUR_PATH + # Version of your assets, change this if you want to expire all your assets. + config.assets.version = '1.0' # Specifies the header that your server uses for sending files # config.action_dispatch.x_sendfile_header = "X-Sendfile" # for apache @@ -30,8 +42,8 @@ # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. # config.force_ssl = true - # See everything in the log (default is :info) - # config.log_level = :debug + # Set to :debug to see everything in the log. + config.log_level = :info # Prepend all log lines with the following tags # config.log_tags = [ :subdomain, :uuid ] @@ -45,16 +57,17 @@ # Enable serving of images, stylesheets, and JavaScripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" - # Precompile additional assets (application.js, application.css, and all non-JS/CSS are already added) + # Precompile additional assets. + # application.js, application.css, and all non-JS/CSS in app/assets folder are already added. # config.assets.precompile += %w( search.js ) - # ActionMailer Config - # Setup for staging - deliveries, no errors raised + # Ignore bad email addresses and do not raise email delivery errors. + # Set this to true and configure the email server for immediate delivery to raise delivery errors. + config.action_mailer.raise_delivery_errors = true config.action_mailer.default_url_options = { :host => ENV["MAILER_URL"] } config.action_mailer.delivery_method = :smtp config.action_mailer.perform_deliveries = true - # Disable delivery errors, bad email addresses will be ignored - config.action_mailer.raise_delivery_errors = false + config.action_mailer.default :charset => "utf-8" config.action_mailer.smtp_settings = { @@ -66,9 +79,6 @@ :authentication => :plain } - # Enable threaded mode - # config.threadsafe! - # Enable locale fallbacks for I18n (makes lookups for any locale fall back to # the I18n.default_locale when a translation can not be found) config.i18n.fallbacks = true @@ -76,6 +86,13 @@ # Send deprecation notices to registered listeners config.active_support.deprecation = :notify - config.api_base_host = ENV["API_BASE_HOST"] + # Disable automatic flushing of the log to improve performance. + # config.autoflush_log = false + + # Use default logging formatter so that PID and timestamp are not suppressed. + config.log_formatter = ::Logger::Formatter.new + # Do not dump schema after migrations. + # This is a Rails 4.1 setting. Uncomment after upgrading. + #config.active_record.dump_schema_after_migration = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index 414282305..00e1f7dfc 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -1,5 +1,5 @@ OhanaApi::Application.configure do - # Settings specified here will take precedence over those in config/application.rb + # Settings specified here will take precedence over those in config/application.rb. # The test environment is used exclusively to run your application's # test suite. You never need to work with it otherwise. Remember that @@ -7,22 +7,24 @@ # and recreated between test runs. Don't rely on the data there! config.cache_classes = true - # Configure static asset server for tests with Cache-Control for performance + # Do not eager load code on boot. This avoids loading your whole application + # just for the purpose of running a single test. If you are using a tool that + # preloads Rails for running tests, you may have to set it to true. + config.eager_load = false + + # Configure static asset server for tests with Cache-Control for performance. config.serve_static_assets = true config.static_cache_control = "public, max-age=3600" - # Log error messages when you accidentally call methods on nil - config.whiny_nils = true - - # Show full error reports and disable caching + # Show full error reports and disable caching. config.consider_all_requests_local = true config.action_controller.perform_caching = false - # Raise exceptions instead of rendering exception templates + # Raise exceptions instead of rendering exception templates. config.action_dispatch.show_exceptions = false - # Disable request forgery protection in test environment - config.action_controller.allow_forgery_protection = false + # Disable request forgery protection in test environment. + config.action_controller.allow_forgery_protection = false # Tell Action Mailer not to deliver emails to the real world. # The :test delivery method accumulates sent emails in the @@ -31,11 +33,9 @@ # Required for specs to pass. Any host value should work. config.action_mailer.default_url_options = { :host => 'example.com' } - # Print deprecation notices to the stderr + # Print deprecation notices to the stderr. config.active_support.deprecation = :stderr - # Roar-Rails hypermedia links base url - #config.representer.default_url_options = { :host => "example.com/api" } - - config.api_base_host = "example.com/api" + # Raises error for missing translations + # config.action_view.raise_on_missing_translations = true end diff --git a/config/initializers/filter_parameter_logging.rb b/config/initializers/filter_parameter_logging.rb new file mode 100644 index 000000000..5827f8092 --- /dev/null +++ b/config/initializers/filter_parameter_logging.rb @@ -0,0 +1,4 @@ +# Be sure to restart your server when you modify this file. + +# Configure sensitive parameters which will be filtered from the log file. +Rails.application.config.filter_parameters += [:password, :password_confirmation] \ No newline at end of file diff --git a/config/initializers/friendly_id.rb b/config/initializers/friendly_id.rb new file mode 100644 index 000000000..d045a6eb3 --- /dev/null +++ b/config/initializers/friendly_id.rb @@ -0,0 +1,88 @@ +# FriendlyId Global Configuration +# +# Use this to set up shared configuration options for your entire application. +# Any of the configuration options shown here can also be applied to single +# models by passing arguments to the `friendly_id` class method or defining +# methods in your model. +# +# To learn more, check out the guide: +# +# http://norman.github.io/friendly_id/file.Guide.html + +FriendlyId.defaults do |config| + # ## Reserved Words + # + # Some words could conflict with Rails's routes when used as slugs, or are + # undesirable to allow as slugs. Edit this list as needed for your app. + config.use :reserved + + config.reserved_words = %w(new edit index session login logout users admin + stylesheets assets javascripts images) + + # ## Friendly Finders + # + # Uncomment this to use friendly finders in all models. By default, if + # you wish to find a record by its friendly id, you must do: + # + # MyModel.friendly.find('foo') + # + # If you uncomment this, you can do: + # + # MyModel.find('foo') + # + # This is significantly more convenient but may not be appropriate for + # all applications, so you must explicity opt-in to this behavior. You can + # always also configure it on a per-model basis if you prefer. + # + # Something else to consider is that using the :finders addon boosts + # performance because it will avoid Rails-internal code that makes runtime + # calls to `Module.extend`. + # + config.use :finders + # + # ## Slugs + # + # Most applications will use the :slugged module everywhere. If you wish + # to do so, uncomment the following line. + # + config.use :slugged + # + # By default, FriendlyId's :slugged addon expects the slug column to be named + # 'slug', but you can change it if you wish. + # + # config.slug_column = 'slug' + # + # When FriendlyId can not generate a unique ID from your base method, it appends + # a UUID, separated by a single dash. You can configure the character used as the + # separator. If you're upgrading from FriendlyId 4, you may wish to replace this + # with two dashes. + # + # config.sequence_separator = '-' + # + # ## Tips and Tricks + # + # ### Controlling when slugs are generated + # + # As of FriendlyId 5.0, new slugs are generated only when the slug field is + # nil, but if you're using a column as your base method can change this + # behavior by overriding the `should_generate_new_friendly_id` method that + # FriendlyId adds to your model. The change below makes FriendlyId 5.0 behave + # more like 4.0. + # + config.use Module.new { + def should_generate_new_friendly_id? + slug.blank? || name_changed? + end + } + # + # FriendlyId uses Rails's `parameterize` method to generate slugs, but for + # languages that don't use the Roman alphabet, that's not usually suffient. Here + # we use the Babosa library to transliterate Russian Cyrillic slugs to ASCII. If + # you use this, don't forget to add "babosa" to your Gemfile. + # + # config.use Module.new { + # def normalize_friendly_id(text) + # text.to_slug.normalize! :transliterations => [:russian, :latin] + # end + # } +end diff --git a/config/initializers/inflections.rb b/config/initializers/inflections.rb index 0197f32e9..69879edeb 100644 --- a/config/initializers/inflections.rb +++ b/config/initializers/inflections.rb @@ -1,15 +1,16 @@ # Be sure to restart your server when you modify this file. -# Add new inflection rules using the following format -# (all these examples are active by default): -ActiveSupport::Inflector.inflections do |inflect| +# Add new inflection rules using the following format. Inflections +# are locale specific, and you may define rules for as many different +# locales as you wish. All of these examples are active by default: +ActiveSupport::Inflector.inflections(:en) do |inflect| #inflect.plural /^(ox)$/i, '\1en' #inflect.singular /^(ox)en/i, '\1' inflect.irregular 'fax', 'faxes' #inflect.uncountable %w( fish sheep ) end -# + # These inflection rules are supported but not enabled by default: -# ActiveSupport::Inflector.inflections do |inflect| +# ActiveSupport::Inflector.inflections(:en) do |inflect| # inflect.acronym 'RESTful' # end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 72aca7e44..dc1899682 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -2,4 +2,3 @@ # Add new mime types for use in respond_to blocks: # Mime::Type.register "text/richtext", :rtf -# Mime::Type.register_alias "text/html", :iphone diff --git a/config/initializers/secret_token.rb b/config/initializers/secret_token.rb index 4297e8014..bedf0bfbc 100644 --- a/config/initializers/secret_token.rb +++ b/config/initializers/secret_token.rb @@ -1,13 +1,19 @@ # Be sure to restart your server when you modify this file. -# Your secret key for verifying the integrity of signed cookies. +# Your secret key is used for verifying the integrity of signed cookies. # If you change this key, all old signed cookies will become invalid! + # Make sure the secret is at least 30 characters and all random, # no regular words or you'll be exposed to dictionary attacks. +# You can use `rake secret` to generate a secure secret key. if Rails.env.production? && ENV['SECRET_TOKEN'].blank? - raise 'The SECRET_TOKEN environment variable is not set.\n - To generate it, run "rake secret", then set it with "heroku config:set SECRET_TOKEN=the_token_you_generated"' + fail "The SECRET_TOKEN environment variable is not set on your production"+ + " server. To generate a random token, run \"rake secret\" from the command"+ + " line, then set it in production. If you're using Heroku, you can set it "+ + "like this: \"heroku config:set SECRET_TOKEN=the_token_you_generated\"." end -OhanaApi::Application.config.secret_token = ENV['SECRET_TOKEN'] || '7bb1869cc43f25b000d02cf02245a55e330d67da37f97d2c6fb8cd12af3052bab3cc0f630938f531fa35b55976026458ac987e55c103600514bf4810a1945f96' +# Make sure the secrets in this file are kept private +# if you're sharing your code publicly. +OhanaApi::Application.config.secret_key_base = ENV['SECRET_TOKEN'] || '44cd5d3990ada5d792a06bd92c796edf8474dc92c1e88eecb458d524776ee26703260a07338cfede45db5246d4afcff74965a034fed46dbd1d424dc857f7748b' diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index f59ccd231..49c44624f 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -1,8 +1,3 @@ # Be sure to restart your server when you modify this file. OhanaApi::Application.config.session_store :cookie_store, key: '_ohana-api_session' - -# Use the database for sessions instead of the cookie-based default, -# which shouldn't be used to store highly confidential information -# (create the session table with "rails generate session_migration") -# OhanaApi::Application.config.session_store :active_record_store diff --git a/config/initializers/wrap_parameters.rb b/config/initializers/wrap_parameters.rb index 369b465f8..0653cefc3 100644 --- a/config/initializers/wrap_parameters.rb +++ b/config/initializers/wrap_parameters.rb @@ -1,10 +1,14 @@ # Be sure to restart your server when you modify this file. -# + # This file contains settings for ActionController::ParamsWrapper which # is enabled by default. # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. ActiveSupport.on_load(:action_controller) do - wrap_parameters format: [:json] + wrap_parameters format: [:json] if respond_to?(:wrap_parameters) end +# To enable root element in JSON for ActiveRecord objects. +# ActiveSupport.on_load(:active_record) do +# self.include_root_in_json = true +# end \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index d7441ad9a..47befb5c7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -1,5 +1,23 @@ -# Sample localization file for English. Add more files in this directory for other locales. -# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points. +# Files in the config/locales directory are used for internationalization +# and are automatically loaded by Rails. If you want to use locales other +# than English, add the necessary files in this directory. + +# To use the locales, use `I18n.t`: +# +# I18n.t 'hello' +# +# In views, this is aliased to just `t`: +# +# <%= t('hello') %> +# +# To use a different locale, set it with `I18n.locale`: +# +# I18n.locale = :es +# +# This would use the information in config/locales/es.yml. +# +# To learn more, please read the Rails Internationalization guide +# available at http://guides.rubyonrails.org/i18n.html. en: enumerize: diff --git a/config/routes.rb b/config/routes.rb index d7ad52c44..b5658990f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -1,4 +1,8 @@ OhanaApi::Application.routes.draw do + # The priority is based upon order of creation: first created -> highest priority. + # See how all your routes lay out with "rake routes". + + # Read more about routing: http://guides.rubyonrails.org/routing.html devise_for :users @@ -13,5 +17,5 @@ get '.well-known/status' => "status#get_status" - default_url_options host: Rails.application.config.api_base_host + default_url_options host: ENV["API_BASE_HOST"] end diff --git a/db/schema.rb b/db/schema.rb index aa9fbabe4..5a64762fd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -9,56 +9,59 @@ # from scratch. The latter is a flawed and unsustainable approach (the more migrations # you'll amass, the slower it'll run and the greater likelihood for issues). # -# It's strongly recommended to check this file into your version control system. +# It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(:version => 20140404220233) do +ActiveRecord::Schema.define(version: 20140404220233) do - create_table "addresses", :force => true do |t| + # These are extensions that must be enabled in order to support this database + enable_extension "plpgsql" + + create_table "addresses", force: true do |t| t.integer "location_id" t.text "street" t.text "city" t.text "state" t.text "zip" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "addresses", ["location_id"], :name => "index_addresses_on_location_id" + add_index "addresses", ["location_id"], name: "index_addresses_on_location_id", using: :btree - create_table "api_applications", :force => true do |t| + create_table "api_applications", force: true do |t| t.integer "user_id" t.text "name" t.text "main_url" t.text "callback_url" t.text "api_token" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "api_applications", ["api_token"], :name => "index_api_applications_on_api_token", :unique => true - add_index "api_applications", ["user_id"], :name => "index_api_applications_on_user_id" + add_index "api_applications", ["api_token"], name: "index_api_applications_on_api_token", unique: true, using: :btree + add_index "api_applications", ["user_id"], name: "index_api_applications_on_user_id", using: :btree - create_table "categories", :force => true do |t| + create_table "categories", force: true do |t| t.text "name" t.text "oe_id" t.text "slug" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false t.string "ancestry" end - add_index "categories", ["ancestry"], :name => "index_categories_on_ancestry" - add_index "categories", ["slug"], :name => "index_categories_on_slug", :unique => true + add_index "categories", ["ancestry"], name: "index_categories_on_ancestry", using: :btree + add_index "categories", ["slug"], name: "index_categories_on_slug", unique: true, using: :btree - create_table "categories_services", :id => false, :force => true do |t| - t.integer "category_id", :null => false - t.integer "service_id", :null => false + create_table "categories_services", id: false, force: true do |t| + t.integer "category_id", null: false + t.integer "service_id", null: false end - add_index "categories_services", ["category_id", "service_id"], :name => "index_categories_services_on_category_id_and_service_id", :unique => true - add_index "categories_services", ["service_id", "category_id"], :name => "index_categories_services_on_service_id_and_category_id", :unique => true + add_index "categories_services", ["category_id", "service_id"], name: "index_categories_services_on_category_id_and_service_id", unique: true, using: :btree + add_index "categories_services", ["service_id", "category_id"], name: "index_categories_services_on_service_id_and_category_id", unique: true, using: :btree - create_table "contacts", :force => true do |t| + create_table "contacts", force: true do |t| t.integer "location_id" t.text "name" t.text "title" @@ -66,34 +69,34 @@ t.text "fax" t.text "phone" t.text "extension" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "contacts", ["location_id"], :name => "index_contacts_on_location_id" + add_index "contacts", ["location_id"], name: "index_contacts_on_location_id", using: :btree - create_table "faxes", :force => true do |t| + create_table "faxes", force: true do |t| t.integer "location_id" t.text "number" t.text "department" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "faxes", ["location_id"], :name => "index_faxes_on_location_id" + add_index "faxes", ["location_id"], name: "index_faxes_on_location_id", using: :btree - create_table "friendly_id_slugs", :force => true do |t| - t.string "slug", :null => false - t.integer "sluggable_id", :null => false - t.string "sluggable_type", :limit => 40 + create_table "friendly_id_slugs", force: true do |t| + t.string "slug", null: false + t.integer "sluggable_id", null: false + t.string "sluggable_type", limit: 40 t.datetime "created_at" end - add_index "friendly_id_slugs", ["slug", "sluggable_type"], :name => "index_friendly_id_slugs_on_slug_and_sluggable_type", :unique => true - add_index "friendly_id_slugs", ["sluggable_id"], :name => "index_friendly_id_slugs_on_sluggable_id" - add_index "friendly_id_slugs", ["sluggable_type"], :name => "index_friendly_id_slugs_on_sluggable_type" + add_index "friendly_id_slugs", ["slug", "sluggable_type"], name: "index_friendly_id_slugs_on_slug_and_sluggable_type", unique: true, using: :btree + add_index "friendly_id_slugs", ["sluggable_id"], name: "index_friendly_id_slugs_on_sluggable_id", using: :btree + add_index "friendly_id_slugs", ["sluggable_type"], name: "index_friendly_id_slugs_on_sluggable_type", using: :btree - create_table "locations", :force => true do |t| + create_table "locations", force: true do |t| t.integer "organization_id" t.text "accessibility" t.text "admin_emails" @@ -109,50 +112,50 @@ t.text "transportation" t.text "urls" t.text "slug" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "locations", ["latitude", "longitude"], :name => "index_locations_on_latitude_and_longitude" - add_index "locations", ["organization_id"], :name => "index_locations_on_organization_id" - add_index "locations", ["slug"], :name => "index_locations_on_slug", :unique => true + add_index "locations", ["latitude", "longitude"], name: "index_locations_on_latitude_and_longitude", using: :btree + add_index "locations", ["organization_id"], name: "index_locations_on_organization_id", using: :btree + add_index "locations", ["slug"], name: "index_locations_on_slug", unique: true, using: :btree - create_table "mail_addresses", :force => true do |t| + create_table "mail_addresses", force: true do |t| t.integer "location_id" t.text "attention" t.text "street" t.text "city" t.text "state" t.text "zip" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "mail_addresses", ["location_id"], :name => "index_mail_addresses_on_location_id" + add_index "mail_addresses", ["location_id"], name: "index_mail_addresses_on_location_id", using: :btree - create_table "organizations", :force => true do |t| + create_table "organizations", force: true do |t| t.text "name" t.text "urls" t.text "slug" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "organizations", ["slug"], :name => "index_organizations_on_slug", :unique => true + add_index "organizations", ["slug"], name: "index_organizations_on_slug", unique: true, using: :btree - create_table "phones", :force => true do |t| + create_table "phones", force: true do |t| t.integer "location_id" t.text "number" t.text "department" t.text "extension" t.text "vanity_number" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "phones", ["location_id"], :name => "index_phones_on_location_id" + add_index "phones", ["location_id"], name: "index_phones_on_location_id", using: :btree - create_table "services", :force => true do |t| + create_table "services", force: true do |t| t.integer "location_id" t.text "audience" t.text "description" @@ -166,20 +169,20 @@ t.text "funding_sources" t.text "service_areas" t.text "keywords" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "services", ["location_id"], :name => "index_services_on_location_id" + add_index "services", ["location_id"], name: "index_services_on_location_id", using: :btree - create_table "users", :force => true do |t| - t.string "email", :default => "", :null => false - t.string "encrypted_password", :default => "", :null => false - t.string "name", :default => "", :null => false + create_table "users", force: true do |t| + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "name", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", :default => 0, :null => false + t.integer "sign_in_count", default: 0, null: false t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -188,12 +191,12 @@ t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end - add_index "users", ["confirmation_token"], :name => "index_users_on_confirmation_token", :unique => true - add_index "users", ["email"], :name => "index_users_on_email", :unique => true - add_index "users", ["reset_password_token"], :name => "index_users_on_reset_password_token", :unique => true + add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree + add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree end diff --git a/lib/tasks/oe.rake b/lib/tasks/oe.rake index 9a51d5468..6f801fb92 100644 --- a/lib/tasks/oe.rake +++ b/lib/tasks/oe.rake @@ -27,4 +27,5 @@ task :create_categories => :environment do end end end + puts "===> All done creating categories." end diff --git a/public/404.html b/public/404.html index 9a48320a5..82616dcf1 100644 --- a/public/404.html +++ b/public/404.html @@ -2,25 +2,66 @@ The page you were looking for doesn't exist (404) -
-

The page you were looking for doesn't exist.

-

You may have mistyped the address or the page may have moved.

+
+

The page you were looking for doesn't exist.

+

You may have mistyped the address or the page may have moved.

+
+

If you are the application owner, check the logs for more information.

- + \ No newline at end of file diff --git a/public/422.html b/public/422.html index 83660ab18..5c281f118 100644 --- a/public/422.html +++ b/public/422.html @@ -2,25 +2,66 @@ The change you wanted was rejected (422) -
-

The change you wanted was rejected.

-

Maybe you tried to change something you didn't have access to.

+
+

The change you wanted was rejected.

+

Maybe you tried to change something you didn't have access to.

+
+

If you are the application owner, check the logs for more information.

- + \ No newline at end of file diff --git a/public/500.html b/public/500.html index f3648a0db..13ebe7d12 100644 --- a/public/500.html +++ b/public/500.html @@ -2,24 +2,65 @@ We're sorry, but something went wrong (500) -
-

We're sorry, but something went wrong.

+
+

We're sorry, but something went wrong.

+
+

If you are the application owner, check the logs for more information.

- + \ No newline at end of file diff --git a/script/rails b/script/rails deleted file mode 100755 index f8da2cffd..000000000 --- a/script/rails +++ /dev/null @@ -1,6 +0,0 @@ -#!/usr/bin/env ruby -# This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application. - -APP_PATH = File.expand_path('../../config/application', __FILE__) -require File.expand_path('../../config/boot', __FILE__) -require 'rails/commands' diff --git a/spec/api/locations_spec.rb b/spec/api/locations_spec.rb index 93185a19c..ec4d0bc8d 100644 --- a/spec/api/locations_spec.rb +++ b/spec/api/locations_spec.rb @@ -32,9 +32,9 @@ expect(json.first["kind"]).to eq("Other") expect(json.first["organization"].keys).to include("locations_url") expect(json.first["url"]). - to eq("http://example.com/api/locations/#{loc1.id}") + to eq("#{ENV["API_BASE_URL"]}locations/#{loc1.id}") expect(json.first["organization"]["url"]). - to eq("http://example.com/api/organizations/#{loc1.organization.id}") + to eq("#{ENV["API_BASE_URL"]}organizations/#{loc1.organization.id}") end it "displays address when present" do @@ -171,21 +171,21 @@ "name" => @location.name, "short_desc" => "short description", "slug" => "vrs-services", - "updated_at" => @location.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z"), - "url" => "http://example.com/api/locations/#{@location.id}", + "updated_at" => @location.updated_at.strftime("%Y-%m-%dT%H:%M:%S.%3N%:z"), + "url" => "#{ENV["API_BASE_URL"]}locations/#{@location.id}", "services" => [{ "id" => @location.services.reload.first.id, "description" => @location.services.first.description, "keywords" => @location.services.first.keywords, "name" => @location.services.first.name, - "updated_at" => @location.services.first.updated_at.strftime("%Y-%m-%dT%H:%M:%S%:z") + "updated_at" => @location.services.first.updated_at.strftime("%Y-%m-%dT%H:%M:%S.%3N%:z") }], "organization" => { "id" => @location.organization.id, "name"=> "Parent Agency", "slug" => "parent-agency", - "url" => "http://example.com/api/organizations/#{@location.organization.id}", - "locations_url" => "http://example.com/api/organizations/#{@location.organization.id}/locations" + "url" => "#{ENV["API_BASE_URL"]}organizations/#{@location.organization.id}", + "locations_url" => "#{ENV["API_BASE_URL"]}organizations/#{@location.organization.id}/locations" } } json.should == represented @@ -391,7 +391,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Name can't be blank for Contact" + json["message"].should include "name can't be blank" end it "requires contact title" do @@ -400,7 +400,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Title can't be blank for Contact" + json["message"].should include "title can't be blank" end it "requires description" do @@ -495,7 +495,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Street can't be blank" + json["message"].should include "street can't be blank" end it "requires location address state" do @@ -506,7 +506,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "State can't be blank" + json["message"].should include "state can't be blank" end it "requires location address city" do @@ -517,7 +517,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "City can't be blank" + json["message"].should include "city can't be blank" end it "requires location address zip" do @@ -528,7 +528,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Zip can't be blank" + json["message"].should include "zip can't be blank" end it "validates location mail address state" do @@ -562,7 +562,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Street can't be blank" + json["message"].should include "street can't be blank" end it "requires location mail_address state" do @@ -573,7 +573,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "State can't be blank" + json["message"].should include "state can't be blank" end it "requires location mail_address city" do @@ -584,7 +584,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "City can't be blank" + json["message"].should include "city can't be blank" end it "requires location mail_address zip" do @@ -595,7 +595,7 @@ { 'HTTP_X_API_TOKEN' => @token } @loc.reload expect(response.status).to eq(400) - json["message"].should include "Zip can't be blank" + json["message"].should include "zip can't be blank" end it "rejects location with neither address nor mail address" do diff --git a/spec/api/organizations_spec.rb b/spec/api/organizations_spec.rb index 945b54f95..d307e599c 100644 --- a/spec/api/organizations_spec.rb +++ b/spec/api/organizations_spec.rb @@ -28,8 +28,8 @@ "id" => orgs.last.id, "name" => "#{orgs.last.name}", "slug" => orgs.last.slug, - "url" => "http://example.com/api/organizations/#{orgs.last.id}", - "locations_url" => "http://example.com/api/organizations/#{orgs.last.id}/locations" + "url" => "#{ENV["API_BASE_URL"]}organizations/#{orgs.last.id}", + "locations_url" => "#{ENV["API_BASE_URL"]}organizations/#{orgs.last.id}/locations" }] json.should == represented end @@ -53,8 +53,8 @@ "id" => @org.id, "name" => "#{@org.name}", "slug" => "parent-agency", - "url" => "http://example.com/api/organizations/#{@org.id}", - "locations_url" => "http://example.com/api/organizations/#{@org.id}/locations" + "url" => "#{ENV["API_BASE_URL"]}organizations/#{@org.id}", + "locations_url" => "#{ENV["API_BASE_URL"]}organizations/#{@org.id}/locations" } json.should == represented end diff --git a/spec/api/search_spec.rb b/spec/api/search_spec.rb index 52980c0c6..f23af8ef6 100644 --- a/spec/api/search_spec.rb +++ b/spec/api/search_spec.rb @@ -243,7 +243,7 @@ before(:each) do create(:far_loc) create(:farmers_market_loc) - cat = Category.create!(:name => "food") + cat = create(:category) create_service @service.category_ids = [cat.id] @service.save @@ -260,7 +260,7 @@ before(:each) do create(:nearby_loc) create(:farmers_market_loc) - cat = Category.create!(:name => "Jobs") + cat = create(:jobs) create_service @service = @location.services.first @service.category_ids = [cat.id] diff --git a/spec/controllers/status_controller_spec.rb b/spec/controllers/status_controller_spec.rb index 446f2f312..288a41ed6 100644 --- a/spec/controllers/status_controller_spec.rb +++ b/spec/controllers/status_controller_spec.rb @@ -10,8 +10,8 @@ expect(response).to be_success end - context "when DB is down or empty" do - it "returns DB failure error" do + context "when DB can't find any Categories" do + it "returns error" do create(:loc_with_nil_fields) get "get_status" body = JSON.parse(response.body) @@ -22,7 +22,7 @@ context "when DB and search are up and running" do it "returns ok status" do create(:loc_with_nil_fields) - category = Category.create!(:name => "food") + category = create(:category) FactoryGirl.create(:service_with_nil_fields, :category_ids => ["#{category.id}"]) get "get_status" @@ -34,7 +34,7 @@ context "when search returns no results" do it "returns search failure status" do create(:location) - category = Category.create!(:name => "foobar") + category = create(:category) get "get_status" body = JSON.parse(response.body) body["status"].should == "Search returned no results" diff --git a/spec/factories/categories.rb b/spec/factories/categories.rb index d75699e7e..b9f5ac27c 100644 --- a/spec/factories/categories.rb +++ b/spec/factories/categories.rb @@ -5,4 +5,14 @@ name "Food" oe_id "101" end + + factory :health, class: Category do + name "Health" + oe_id "102" + end + + factory :jobs, class: Category do + name "Jobs" + oe_id "105" + end end diff --git a/spec/models/category_spec.rb b/spec/models/category_spec.rb index ce050d447..23f302e1f 100644 --- a/spec/models/category_spec.rb +++ b/spec/models/category_spec.rb @@ -9,4 +9,42 @@ it { should allow_mass_assignment_of(:name) } it { should allow_mass_assignment_of(:oe_id) } + + it do + should validate_presence_of(:name). + with_message("can't be blank for Category") + end + + it do + should validate_presence_of(:oe_id). + with_message("can't be blank for Category") + end + + it { should respond_to(:slug_candidates) } + + describe "slug candidates" do + before(:each) { @category = create(:category) } + + context "when name is already taken" do + it "creates a new slug based on oe_id" do + new_category = create(:health) + new_category.update_attributes!(name: "Food") + new_category.reload.slug.should eq("food-102") + end + end + + context "when name is not taken" do + it "creates a new slug based on name" do + new_category = create(:health) + new_category.reload.slug.should eq("health") + end + end + + context "when name is not updated" do + it "doesn't update slug" do + @category.update_attributes!(oe_id: "103") + @category.reload.slug.should eq("food") + end + end + end end diff --git a/spec/models/contact_spec.rb b/spec/models/contact_spec.rb index 821c6556c..4df436160 100644 --- a/spec/models/contact_spec.rb +++ b/spec/models/contact_spec.rb @@ -6,18 +6,18 @@ it { should be_valid } - describe "invalid data" do - before(:each) { @attrs = {} } + it do + should validate_presence_of(:name). + with_message("can't be blank for Contact") + end - # context "without a name" do - # subject { build(:contact, name: @attrs) } - # it { should_not be_valid } - # end + it do + should validate_presence_of(:title). + with_message("can't be blank for Contact") + end - # context "without a title" do - # subject { build(:contact, title: @attrs) } - # it { should_not be_valid } - # end + describe "invalid data" do + before(:each) { @attrs = {} } context "email without period" do subject { build(:contact, email: "moncef@blahcom") } diff --git a/spec/models/location_spec.rb b/spec/models/location_spec.rb index 60786d9d2..dcb4603fa 100644 --- a/spec/models/location_spec.rb +++ b/spec/models/location_spec.rb @@ -109,4 +109,38 @@ it { should be_valid } end end + + describe "slug candidates" do + before(:each) { @loc = create(:location) } + + context "when address is present and name is already taken" do + it "creates a new slug based on address street" do + new_loc = create(:nearby_loc) + new_loc.update_attributes!(name: "VRS Services") + new_loc.reload.slug.should eq("vrs-services-250-myrtle-road") + end + end + + context "when mail_address is present and name is taken" do + it "creates a new slug based on mail_address city" do + new_loc = create(:no_address) + new_loc.update_attributes!(name: "VRS Services") + new_loc.reload.slug.should eq("vrs-services-la-honda") + end + end + + context "when name is not taken" do + it "creates a new slug based on name" do + new_loc = create(:no_address) + new_loc.reload.slug.should eq("no-address") + end + end + + context "when name is not updated" do + it "doesn't update slug" do + @loc.update_attributes!(description: "new description") + @loc.reload.slug.should eq("vrs-services") + end + end + end end diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index db81b53de..c6166cbb8 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -12,4 +12,30 @@ it { should_not be_valid } end end + + describe "slug candidates" do + before(:each) { @org = create(:organization) } + + context "when name is already taken" do + it "creates a new slug" do + new_org = Organization.create!(name: "Parent Agency") + new_org.reload.slug.should_not eq("parent-agency") + end + end + + context "when url is present and name is taken" do + it "creates a new slug based on url" do + new_org = Organization.create!(name: "Parent Agency", + urls: ["http://monfresh.com"]) + new_org.reload.slug.should eq("parent-agency-monfresh-com") + end + end + + context "when name is not updated" do + it "doesn't update slug" do + @org.update_attributes!(urls: ["http://monfresh.com"]) + @org.reload.slug.should eq("parent-agency") + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 5e177873d..6fca01e2f 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -5,11 +5,17 @@ ENV["RAILS_ENV"] ||= 'test' require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' -#require 'garner' +#Keep rspec/autorun disabled if you're using Zeus +#require 'rspec/autorun' # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. -Dir[Rails.root.join("spec/support/**/*.rb")].each {|f| require f} +Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f } + +# Checks for pending migrations before tests are run. +# If you are not using ActiveRecord, you can remove this line. +# This only works in Rails 4.1. Uncomment after upgrading from 4.0.4 +#ActiveRecord::Migration.maintain_test_schema! RSpec.configure do |config| config.include FactoryGirl::Syntax::Methods @@ -57,8 +63,6 @@ DatabaseCleaner.start Location.tire.index.delete Location.create_elasticsearch_index - # Garner.config.reset! - # Garner.config.cache.clear end config.after(:each) do From 5a814869121f50b9e46f9b2db62e22ae7a37dcd9 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Thu, 10 Apr 2014 17:22:01 -0400 Subject: [PATCH 14/17] Remove hours field from phone object in sample_data The app currently doesn't use that field, so pushing to Heroku will display warnings about not being able to mass assign protected attributes for Phone: hours. --- data/sample_data.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/data/sample_data.json b/data/sample_data.json index 8d2a59af9..8564005b9 100644 --- a/data/sample_data.json +++ b/data/sample_data.json @@ -1,7 +1,7 @@ -{"name":"Peninsula Family Service","locations":[{"name":"Fair Oaks Adult Activity Center","contacts_attributes":[{"name":"Susan Houston","title":"Director of Older Adult Services"},{"name":" Christina Gonzalez","title":"Center Director"}],"description":"A walk-in center for older adults that provides social services, wellness, recreational, educational and creative activities including arts and crafts, computer classes and gardening classes. Coffee and healthy breakfasts are available daily. A hot lunch is served Tuesday-Friday for persons age 60 or over and spouse. Provides case management (including in-home assessments) and bilingual information and referral about community services to persons age 60 or over on questions of housing, employment, household help, recreation and social activities, home delivered meals, health and counseling services and services to shut-ins. Health insurance and legal counseling is available by appointment. Lectures on a variety of health and fitness topics are held monthly in both English and Spanish. Provides a variety of physical fitness opportunities, including a garden club, yoga, tai chi, soul line dance and aerobics classes geared toward older adults. Also provides free monthly blood pressure screenings, quarterly blood glucose monitoring and health screenings by a visiting nurse. Offers a Brown Bag Program in which low-income seniors can receive a bag of groceries each week for a membership fee of $10 a year. Offers Spanish lessons. Formerly known as Peninsula Family Service, Fair Oaks Intergenerational Center. Formerly known as the Fair Oaks Senior Center. Formerly known as Family Service Agency of San Mateo County, Fair Oaks Intergenerational Center.","short_desc":"A multipurpose senior citizens' center serving the Redwood City area.","address_attributes":{"street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Fair Oaks Intergenerational Center","street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["cgonzalez@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 701-0856"}],"phones_attributes":[{"number":"650 780-7525","hours":"(Monday-Friday, 8:30-5)"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Older adults age 55 or over, ethnic minorities and low-income persons","eligibility":"Age 55 or over for most programs, age 60 or over for lunch program","fees":"$2.50 suggested donation for lunch for age 60 or over, donations for other services appreciated. Cash and checks accepted.","how_to_apply":"Walk in or apply by phone.","service_areas":["Redwood City"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","English Language","RECREATION/LEISURE SERVICES","Arts and Crafts","Sports/Games/Exercise","Brown Bag Food Programs","Congregate Meals/Nutrition Sites","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["City","County","Donations","Fees","Fundraising"]}]},{"name":"Second Career Employment Program","contacts_attributes":[{"name":"Brenda Brown","title":"Director, Second Career Services"}],"description":"Provides training and job placement to eligible people age 55 or over who meet certain income qualifications. An income of 125% of poverty level or less is required for subsidized employment and training. (No income requirements for job matchup program.) If a person seems likely to be qualified after a preliminary phone call or visit, he or she must complete an application at this office. Staff will locate appropriate placements for applicants, provide orientation and on-the-job training and assist with finding a job outside the program. Any county resident, regardless of income, age 55 or over has access to the program, job developers and the job bank. Also provides referrals to classroom training. Formerly known as Family Service Agency of San Mateo County, Senior Employment Services.","short_desc":"Provides training and job placement to eligible persons age 55 or over. All persons age 55 or over have access to information in the program's job bank.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"PFS Second Career Services","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Chinese (Mandarin)","Filipino (Tagalog)","Italian","Spanish"],"emails":["bbrown@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4302"}],"phones_attributes":[{"number":"650 403-4300","department":" Ext. 4385","hours":"(Monday-Friday, 9-5)"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Residents of San Mateo County age 55 or over","eligibility":"Age 55 or over, county resident and willing and able to work. Income requirements vary according to program","fees":"None. Donations requested of clients who can afford it.","how_to_apply":"Apply by phone for an appointment.","service_areas":["San Mateo County"],"keywords":["EMPLOYMENT/TRAINING SERVICES","Job Development","Job Information/Placement/Referral","Job Training","Job Training Formats","Job Search/Placement","Older Adults"],"wait":"Varies.","funding_sources":["County","Federal","State"]}]},{"name":"Senior Peer Counseling","contacts_attributes":[{"name":"Howard Lader, LCSW","title":"Manager, Senior Peer Counseling"}],"description":"Offers supportive counseling services to San Mateo County residents age 55 or over. Volunteer counselors are selected and professionally trained to help their peers face the challenges and concerns of growing older. Training provides topics that include understanding depression, cultural competence, sexuality, community resources, and other subjects that are relevant to working with older adults. After completion of training, weekly supervision groups are provided for the volunteer senior peer counselors, as well as quarterly in-service training seminars. Peer counseling services are provided in English, Spanish, Chinese (Cantonese and Mandarin), Tagalog, and to the lesbian, gay, bisexual, transgender older adult community. Formerly known as Family Service Agency of San Mateo County, Senior Peer Counseling.","short_desc":"Offers supportive counseling services to older persons.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address_attributes":{"attention":"PFS Senior Peer Counseling","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"hours":"Any time that is convenient for the client and peer counselor","transportation":"Service is provided in person's home or at a mutually agreed upon location.","accessibility":[],"languages":["Chinese (Cantonese)","Chinese (Mandarin)","Filipino (Tagalog)","Spanish"],"emails":null,"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","department":"English"}],"urls":null,"services_attributes":[{"audience":"Older adults age 55 or over who can benefit from counseling","eligibility":"Resident of San Mateo County age 55 or over","fees":"None.","how_to_apply":"Phone for information (403-4300 Ext. 4322).","service_areas":["San Mateo County"],"keywords":["Geriatric Counseling","Older Adults","Gay, Lesbian, Bisexual, Transgender Individuals"],"wait":"Varies.","funding_sources":["County","Donations","Grants"]}]},{"name":"Family Visitation Center","contacts_attributes":[{"name":"Kimberly Pesavento","title":"Director of Visitation"}],"description":"Provides supervised visitation services and a neutral site for parents to carry out supervised exchanges of children in a safe manner. Therapeutic visitation and other counseling services available. Kids in the Middle is an education class for separating or divorcing parents. The goal is to enable parents to focus on their children's needs while the family is going through separation, divorce or other transition. The class explores the psychological aspects of divorce and its impact on children, builds effective communication techniques and points out areas in which outside help may be indicated. The fee is $50 for working parents, $15 for unemployed people. Classes are scheduled on Saturdays and Sundays and held at various sites throughout the county. Call 650-403-4300 ext. 4500 to register. Formerly known as Family Service Agency of San Mateo County, Family Visitation Center.","short_desc":"Provides supervised visitation services and a neutral site for parents in extremely hostile divorces to carry out supervised exchanges of children.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"PFS Family Visitation Center","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":"Monday, 10-6; Tuesday-Friday, 10-8; Saturday, Sunday, 9:30-5:30","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 4 blocks.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":["kpesavento@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","extension":"4500","hours":"(Tuesday, Wednesday, 10-6; Thursday, Friday, 10-8; Saturday, Sunday, 9:30-5:30)"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Parents, children, families with problems of custody disputes, domestic violence or substance abuse, families going through a separation or divorce","eligibility":"None","fees":"Vary according to income ($5-$90). Cash, checks and credit cards accepted.","how_to_apply":"Apply by phone.","service_areas":["San Mateo County"],"keywords":["INDIVIDUAL AND FAMILY DEVELOPMENT SERVICES","Growth and Adjustment","LEGAL AND CRIMINAL JUSTICE SERVICES","Mediation","Parental Visitation Monitoring","Divorce Counseling","Youth"],"wait":"No wait.","funding_sources":["County","Donations","Grants"]}]},{"name":"Economic Self - Sufficiency Program","contacts_attributes":[{"name":"Joe Bloom","title":"Financial Empowerment Programs Program Director"}],"description":"Provides fixed 8% short term loans to eligible applicants for the purchase of a reliable, used autmobile. Loans are up to $6,000 over 2 1/2 years (30 months). Funds must go towards the entire purchase of the automobile. Peninsula Family Service originates loans and collaborates with commercial partner banks to service the loans, helping clients build credit histories. Formerly known as Family Service Agency of San Mateo County, Ways to Work Family Loan Program.","short_desc":"Makes small loans to working families.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"Economic Self - Sufficiency Program","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":null,"transportation":"SAMTRANS stops within 1 block. CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Hindi","Spanish"],"emails":["waystowork@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","extension":"4100"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Target group: Low-income working families with children transitioning from welfare to work and poor or who do not have access to conventional credit","eligibility":"Eligibility: Low-income family with legal custody of a minor child or an involved parent of a dependent minor child. Must reside and/or work in San Mateo County. Must be working and have verifiable income and ability to pay off loan. No bankruptcies in the past two years and unable to qualify for other funding sources. Loans approved by loan committee.","fees":"$60 application fee. Cash or checks accepted.","how_to_apply":"Phone for information.","service_areas":["San Mateo County"],"keywords":["COMMUNITY SERVICES","Speakers","Automobile Loans"],"wait":null,"funding_sources":["County","Grants","State"]}]}]} -{"name":"Peninsula Volunteers","locations":[{"name":"Little House","contacts_attributes":[{"name":"Peter Olson","title":"Little House Director"},{"name":" Bart Charlow","title":"Executive Director, Peninsula Volunteers"}],"description":"A multipurpose center offering a wide variety of recreational, education and cultural activities. Lifelong learning courses cover topics such as music, art, languages, etc., are hosted at this location. Little House offers a large variety of classes including arts and crafts, jewelry, languages, current events, lapidary, woodwork, painting, and fitness courses (yoga, strength straining, tai chi). There are monthly art and cultural lectures, movie showings, and a computer center. Recreation activities include mah jong, pinochle, ballroom dancing, bridge, trips and tours. Partners with the Sequoia Adult Education Program. The Alzheimer's Cafe, open the third Tuesday of every month from 2:00 - 4:00 pm, is a place that brings together people liviing with dementia, their families, and their caregivers. Free and no registration is needed. The Little House Community Service Desk offers information and referrals regarding social service issues, such as housing, food, transportation, health insurance counseling, and estate planning. Massage, podiatry, and acupuncture are available by appointment. Lunch is served Monday-Friday, 11:30 am-1:00 pm. Prices vary according to selection.","short_desc":"A multipurpose senior citizens' center.","address_attributes":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address_attributes":{"attention":"Little House","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"hours":"Monday-Thursday, 8 am-9 pm; Friday, 8-5","transportation":"SAMTRANS stops within 3 blocks, RediWheels and Menlo Park Shuttle stop at door.","accessibility":["disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["polson@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 326-9547"}],"phones_attributes":[{"number":"650 326-2025","hours":"(Monday-Thursday, 8 am-9 pm; Friday, 8-5))"}],"urls":["http://www.penvol.org/littlehouse"],"services_attributes":[{"audience":"Any age","eligibility":"None","fees":"$55 per year membership dues. Classes have fees. Discounts are available for members. Cash, checks and credit cards accepted.","how_to_apply":"Walk in or apply by phone for membership application.","service_areas":["San Mateo County","Santa Clara County"],"keywords":["ADULT PROTECTION AND CARE SERVICES","In-Home Supportive","Meal Sites/Home-delivered Meals","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","Adult","HEALTH SERVICES","Education/Information","Family Support","Individual/Group Counseling","Screening/Immunization","RECREATION/LEISURE SERVICES","Sports/Games/Exercise","Community Adult Schools","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["Fees","Fundraising","Grants","Membership dues"]}]},{"name":"Rosener House Adult Day Services","contacts_attributes":[{"name":"Bart Charlow","title":"Executive Director, Peninsula Volunteers"},{"name":" Barbara Kalt","title":"Director"}],"description":"Rosener House is a day center for older adults who may be unable to live independently but do not require 24-hour nursing care, may be isolated and in need of a planned activity program, may need assistance with activities of daily living or are living in a family situation where the caregiver needs respite from giving full-time care. Assists elderly persons to continue to live with family or alone rather than moving to a skilled nursing facility. Activities are scheduled Monday-Friday, 10 am-2:30 pm, and participants may come two to five days per week. The facility is open from 8 am to 5:30 pm for participants who need to remain all day. Small group late afternoon activities are held from 3-5:30 pm. The program provides a noon meal including special diets as required. Services offered include social and therapeutic recreational activities, individual and family counseling and occupational, physical and speech therapy. A registered nurse is available daily. The Dementia and Alzheimer's Services Program provides specialized activities in a supportive environment for participants with Alzheimer's disease and other dementias. Holds a weekly support group for caregivers. An early memory loss class for independent adults, \"Minds in Motion\" meets weekly at Rosener House on Wednesday mornings. Call for more information.","short_desc":"A day center for adults age 50 or over.","address_attributes":{"street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"mail_address_attributes":{"attention":"Rosener House","street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"hours":"Monday-Friday, 8-5:30","transportation":"Transportation can be arranged via Redi-Wheels or Outreach.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish","Filipino (Tagalog)","Vietnamese"],"emails":["bkalt@peninsulavolunteers.org","fmarchick@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 322-4067"}],"phones_attributes":[{"number":"650 322-0126","hours":"(Monday-Friday, 8-5:30)"}],"urls":["http://www.penvol.org/rosenerhouse"],"services_attributes":[{"audience":"Older adults who have memory or sensory loss, mobility limitations and may be lonely and in need of socialization","eligibility":"Age 18 or over","fees":"$85 per day. Vary according to income for those unable to pay full fee. Cash, checks, credit cards, private insurance and vouchers accepted.","how_to_apply":"Apply by phone or be referred by a doctor, social worker or other professional. All prospective participants are interviewed individually before starting the program. A recent physical examination is required, including a TB test.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Los Altos","Los Altos Hills","Menlo Park","Mountain View","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Sunnyvale","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Adult Day Health Care","Dementia Management","Adult Day Programs","Older Adults"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"Meals on Wheels - South County","contacts_attributes":[{"name":"Marilyn Baker-Venturini","title":"Director"},{"name":" Graciela Hernandez","title":"Assistant Manager"},{"name":" Julie Avelino","title":"Assessment Specialist"}],"description":"Delivers a hot meal to the home of persons age 60 or over who are primarily homebound and unable to prepare their own meals, and have no one to prepare meals. Also, delivers a hot meal to the home of disabled individuals ages 18-59. Meals are delivered between 9 am-1:30 pm, Monday-Friday. Special diets are accommodated: low fat, low sodium, and low sugar.","short_desc":"Will deliver a hot meal to the home of persons age 60 or over who are homebound and unable to prepare their own meals. Can provide special diets.","address_attributes":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address_attributes":{"attention":"Meals on Wheels - South County","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"hours":"Delivery times: Monday-Friday, 9-1:30","transportation":"Not necessary for service.","accessibility":null,"languages":["Spanish"],"emails":["mbaker-venturini@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 326-9547"}],"phones_attributes":[{"number":"650 323-2022","hours":"(Monday-Friday, 8-2)"}],"urls":["http://www.peninsulavolunteers.org"],"services_attributes":[{"audience":"Senior citizens age 60 or over, disabled individuals age 18-59","eligibility":"Homebound person unable to cook or shop","fees":"Suggested donation of $4.25 per meal for seniors 60 or over. Mandatory charge of $2 per meal for disabled individuals ages 18-59.","how_to_apply":"Apply by phone.","service_areas":["Atherton","Belmont","East Palo Alto","Menlo Park","Portola Valley","Redwood City","San Carlos","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","HEALTH SERVICES","Nutrition","Home Delivered Meals","Older Adults","Disabilities Issues"],"wait":"No wait.","funding_sources":["County","Donations"]}]}]} -{"name":"Redwood City Public Library","locations":[{"name":"Fair Oaks Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Divisions Manager"}],"description":"Provides general reading material, including bilingual, multi-cultural books, CDs and cassettes, bilingual and Spanish language reference services. School, class and other group visits may be arranged by appointment. The library is a member of the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Fair Oaks Branch","street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-7; Friday, 10-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"650 569-3371"}],"phones_attributes":[{"number":"650 780-7261","hours":"(Monday-Thursday, 10-7; Friday, 10-5)"}],"urls":null,"services_attributes":[{"audience":"Ethnic minorities, especially Spanish speaking","eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of residency in California required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City","County"]}]},{"name":"Main Library","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Division Manager"}],"description":"Provides general reading and media materials, literacy and homework assistance, and programs for all ages. Provides public computers, wireless connectivity, a children's room, teen center, and a local history collection. The library is a member of the Peninsula Library System. The Fair Oaks Branch (650-780-7261) is located at 2510 Middlefield Road and is open Monday-Thursday, 10-7; Friday, 10-5. The Schaberg Branch (650-780-7010) is located at 2140 Euclid Avenue and is open Tuesday-Thursday, 1-6; Saturday, 10-3. The Redwood Shores Branch (650-780-5740) is located at 399 Marine Parkway and is open Monday-Thursday, 10-8; Saturday, 10-5; Sunday 12-5.","short_desc":"Provides general reference and reading materials to adults, teenagers and children.","address_attributes":{"street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Main Library","street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5","transportation":"SAMTRANS stops within 1 block; CALTRAIN stops within 1 block.","accessibility":["elevator","tape_braille","ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish"],"emails":["rclinfo@redwoodcity.org"],"faxes_attributes":[{"number":"650 780-7069"}],"phones_attributes":[{"number":"650 780-7018","department":"Circulation","hours":"(Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5)"}],"urls":["http://www.redwoodcity.org/library"],"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a card","fees":"None.","how_to_apply":"Walk in. Proof of California residency to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Schaberg Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Elizabeth Meeks","title":"Branch Manager"}],"description":"Provides general reading materials, including large-type books, DVD's and CDs, books on CD and some Spanish language materials to children. Offers children's programs and a Summer Reading Club. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"2140 Euclid Avenue.","city":"Redwood City","state":"CA","zip":"94061"},"mail_address_attributes":{"attention":"Schaberg Branch","street":"2140 Euclid Avenue","city":"Redwood City","state":"CA","zip":"94061"},"hours":"Tuesday-Thursday, 1-6, Saturday, 10-3","transportation":"SAMTRANS stops within 1 block.","accessibility":["ramp"],"languages":null,"emails":null,"faxes_attributes":[{"number":"650 365-3738"}],"phones_attributes":[{"number":"650 780-7010","hours":"(Tuesday-Thursday, 1-6, Saturday, 10-3)"}],"urls":null,"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a library card for borrowing materials","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Project Read","contacts_attributes":[{"name":"Kathy Endaya","title":"Director"}],"description":"Offers an intergenerational literacy program for youth and English-speaking adults seeking to improver literacy skills. Adult services include: adult one-to-one tutoring to improve basic skills in reading, writing and critical thinking; Families for Literacy (FFL), a home-based family literacy program for parents who want to be able to read to their young children; and small group/English as a Second Language (ESL). Youth services include: Youth Tutoring, Families in Partnership (FIP); Teen-to-Elementary Student Tutoring, Kids in Partnership (KIP); and computer-aided literacy. Redwood City Friends of Literacy is a fundraising board that helps to support and to fund Redwood City's Project Read. Call for more information about each service.","short_desc":"Offers an intergenerational literacy program for adults and youth seeking to improver literacy skills.","address_attributes":{"street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Project Read","street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-8:30; Friday, 10-5","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","restroom","disabled_parking"],"languages":null,"emails":["rclread@redwoodcity.org"],"faxes_attributes":[{"number":"650 780-7004"}],"phones_attributes":[{"number":"650 780-7077","hours":"(Monday-Thursday, 9:30 am-9 pm; Friday, 9:30-5)"}],"urls":["http://www.projectreadredwoodcity.org"],"services_attributes":[{"audience":"Adults, parents, children in 1st-12th grades in the Redwood City school districta","eligibility":"English-speaking adult reading at or below 7th grade level or child in 1st-12th grade in the Redwood City school districts","fees":"None.","how_to_apply":"Walk in or apply by phone, email or webpage registration.","service_areas":["Redwood City"],"keywords":["EDUCATION SERVICES","Adult","Alternative","Literacy","Literacy Programs","Libraries","Public Libraries","Youth"],"wait":"Depends on availability of tutors for small groups and one-on-one.","funding_sources":["City","Donations","Federal","Grants","State"]}]},{"name":"Redwood Shores Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"}],"description":"Provides general reading materials, including large-type books, videos, music cassettes and CDs, and books on tape. Offers children's programs and a Summer Reading Club. Meeting room is available to nonprofit groups. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"399 Marine Parkway.","city":"Redwood City","state":"CA","zip":"94065"},"mail_address_attributes":{"attention":"Redwood Shores Branch","street":"399 Marine Parkway","city":"Redwood City","state":"CA","zip":"94065"},"hours":null,"transportation":null,"accessibility":null,"languages":null,"emails":null,"phones_attributes":[{"number":"650 780-5740"}],"urls":["http://www.redwoodcity.org/library"],"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]}]} -{"name":"Salvation Army","locations":[{"name":"Redwood City Corps","contacts_attributes":[{"name":"Andres Espinoza","title":"Captain, Commanding Officer"}],"description":"Provides food, clothing, bus tokens and shelter to individuals and families in times of crisis from the Redwood City Corps office and community centers throughout the county. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Counseling and translation services (English/Spanish) are available either on a walk-in basis or by appointment. Rental assistance with available funds. Another office (described separately) is located at 409 South Spruce Avenue, South San Francisco (650-266-4591).","short_desc":"Provides a variety of emergency services to low-income persons. Also sponsors recreational and educational activities.","address_attributes":{"street":"660 Veterans Blvd.","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Salvation Army","street":"P.O. Box 1147","city":"Redwood City","state":"CA","zip":"94064"},"hours":null,"transportation":"SAMTRANS stops nearby.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"650 364-1712"}],"phones_attributes":[{"number":"650 368-4643","hours":"(Monday, Tuesday, Wednesday, Friday, 9-12, 1-4; Thursday, 1-4)"}],"urls":["http://www.tsagoldenstate.org"],"services_attributes":[{"audience":"Individuals or families with low or no income and/or trying to obtain public assistance","eligibility":"None for most services. For emergency assistance, must have low or no income and be willing to apply for public assistance","fees":"None.","how_to_apply":"Call for appointment. Referral from human service professional preferred for emergency assistance.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Foster City","Menlo Park","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Woodside"],"keywords":["COMMUNITY SERVICES","Interpretation/Translation","EMERGENCY SERVICES","Shelter/Refuge","FINANCIAL ASSISTANCE SERVICES","Utilities","MENTAL HEALTH SERVICES","Individual/Group Counseling","Food Pantries","Homeless Shelter","Rental Deposit Assistance","Utility Service Payment Assistance"],"wait":"Up to 20 minutes.","funding_sources":["Donations","Grants"]}]},{"name":"Adult Rehabilitation Center","contacts_attributes":[{"name":"Jack Phillips","title":"Administrator"}],"description":"Provides a long-term (6-12 month) residential rehabilitation program for men and women with substance abuse and other problems. Residents receive individual counseling, and drug and alcohol education. The spiritual side of recovery is address_attributesed through chapel services and Bible study as well as 12-step programs. Nicotine cessation is a part of the program. Residents must be physically able to work, seeking treatment for substance abuse, sober long enough to pass urine drug screen before entering and agreeable to participating in weekly 12-step programs such as Alcoholics Anonymous or Narcotics Anonymous. Pinehurst Lodge is a separate facility for women only. Transition houses for men and women graduates also available.","short_desc":"Long-term (6-12 month) residential treatment program for men/women age 21-60.","address_attributes":{"street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"mail_address_attributes":{"attention":"Adult Rehabilitation Center","street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"hours":"Monday-Friday, 8-4","transportation":"MUNI - 26 Valencia, Mission Street lines.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"415 285-1391"}],"phones_attributes":[{"number":"415 643-8000","hours":"(Monday-Friday, 8-3)"}],"urls":null,"services_attributes":[{"audience":"Adult alcoholic/drug addictive men and women with social and spiritual problems","eligibility":"Age 21-60, detoxed, physically able and willing to participate in a work therapy program","fees":"None.","how_to_apply":"Walk in or through other agency referral.","service_areas":["Alameda County","Contra Costa County","Marin County","San Francisco County","San Mateo County","Santa Clara County","Northern California"],"keywords":["ALCOHOLISM SERVICES","Residential Care","DRUG ABUSE SERVICES"],"wait":"Varies according to available beds for men and women. Women have a longer wait due to small number of beds statewide.","funding_sources":["Donations","Sales"]}]},{"name":"Sunnyvale Corps","contacts_attributes":[{"name":"James Lee","title":"Commanding Officer"}],"description":"Provides emergency assistance including food and clothing for persons in immediate need. Provides PG&E assistance through REACH program. Youth programs offer tutoring, music and troops. Information on related resources is available. Also provides rental assistance when funds are available.","short_desc":"Provides emergency assistance to persons in immediate need and offers after school activities and summer day camp program.","address_attributes":{"street":"1161 South Bernardo","city":"Sunnyvale","state":"CA","zip":"94087"},"mail_address_attributes":{"attention":"Salvation Army","street":"P.O. Box 61868","city":"Sunnyvale","state":"CA","zip":"94088"},"hours":"Monday-Friday, 9-4","transportation":"VTA stops within 4 blocks.","accessibility":[],"languages":["Korean"],"emails":["william_nichols@usw.salvationarmy.org"],"faxes_attributes":[{"number":"408 720-8075"}],"phones_attributes":[{"number":"408 720-0420","hours":"(Monday-Friday, 9-4)"}],"urls":null,"services_attributes":[{"audience":null,"eligibility":"None for emergency assistance","fees":"None for emergency services. Vary for after school activities. Cash and checks accepted.","how_to_apply":"Walk in. Written application, identification required for emergency assistance.","service_areas":["Los Altos","Mountain View","Sunnyvale"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","CHILD PROTECTION AND CARE SERVICES","Day Care","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","RECREATION/LEISURE SERVICES","Camping","Emergency Food","Clothing","Utility Assistance","Youth Development"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"South San Francisco Citadel Corps","contacts_attributes":[{"name":"Kenneth Gibson","title":"Major"}],"description":"Provides emergency food, clothing and furniture vouchers to low-income families in times of crisis. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Offers a Saturday morning Homeless Feeding Program at 10:30, as well as Sunday services and spiritual counseling. Provides Christmas toys and Back-to-School clothes and supplies. Offers case management, advocacy and referrals to other agencies.","short_desc":"Provides emergency food and clothing and furniture vouchers to low-income families in times of crisis.","address_attributes":{"street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"mail_address_attributes":{"attention":"Salvation Army","street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"hours":"Monday-Thursday, 9-4:30","transportation":"SAMTRANS stops within 1 block, BART stops within 3 blocks.","accessibility":["wheelchair"],"languages":null,"emails":null,"faxes_attributes":[{"number":"650 266-2594"},{"number":"650 266-4594"}],"phones_attributes":[{"number":"650 266-4591","hours":"(Monday-Thursday, 9-4:30)"}],"urls":["http://www.tsagoldenstate.org"],"services_attributes":[{"audience":null,"eligibility":"Low-income families","fees":"None.","how_to_apply":"Call for information.","service_areas":["Brisbane","Colma","Daly City","Millbrae","Pacifica","San Bruno","South San Francisco"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","Emergency Food","Food Pantries","Furniture","Clothing","Utility Assistance","School Supplies","Case/Care Management","Holiday Programs","Pastoral Counseling","Low Income"],"wait":null,"funding_sources":["Donations"]}]}]} -{"name":"Samaritan House","locations":[{"name":"Redwood City Free Medical Clinic","contacts_attributes":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical care to those in need. Offers basic medical exams for adults and tuberculosis screening. Assists the individual to access other services in the community. By appointment only, Project Smile provides a free dental exam, dental cleaning and oral hygiene instruction for children, age 3-12, of Samaritan House clients.","short_desc":"Provides free medical care to those in need.","address_attributes":{"street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Redwood City Free Medical Clinic","street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Friday, 9-12, 2-5","transportation":"SAMTRANS stops within 2 blocks.","accessibility":["restroom","wheelchair"],"languages":["Spanish"],"emails":["gracie@samaritanhouse.com"],"faxes_attributes":[{"number":"650 839-1457"}],"phones_attributes":[{"number":"650 839-1447","hours":"(Monday-Friday, 8:30-12, 1:30-5)"}],"urls":["http://www.samaritanhouse.com"],"services_attributes":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment. Medical visits are by appointment only.","service_areas":["Atherton","East Palo Alto","Menlo Park","Redwood City","San Carlos"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]},{"name":"San Mateo Free Medical Clinic","contacts_attributes":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","short_desc":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","address_attributes":{"street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address_attributes":{"attention":"San Mateo Free Medical/Dental","street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"hours":"Monday-Friday, 9-12, 1-4","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","wheelchair"],"languages":["Spanish"],"emails":["smcmed@samaritanhouse.com"],"faxes_attributes":[{"number":"650 578-0440"}],"phones_attributes":[{"number":"650 578-0400","hours":"(Monday-Friday, 9-12, 1-4)"}],"urls":["http://www.samaritanhouse.com"],"services_attributes":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment (650-347-3648).","service_areas":["Belmont","Burlingame","Foster City","Millbrae","San Carlos","San Mateo"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]}]} +{"name":"Peninsula Family Service","locations":[{"name":"Fair Oaks Adult Activity Center","contacts_attributes":[{"name":"Susan Houston","title":"Director of Older Adult Services"},{"name":" Christina Gonzalez","title":"Center Director"}],"description":"A walk-in center for older adults that provides social services, wellness, recreational, educational and creative activities including arts and crafts, computer classes and gardening classes. Coffee and healthy breakfasts are available daily. A hot lunch is served Tuesday-Friday for persons age 60 or over and spouse. Provides case management (including in-home assessments) and bilingual information and referral about community services to persons age 60 or over on questions of housing, employment, household help, recreation and social activities, home delivered meals, health and counseling services and services to shut-ins. Health insurance and legal counseling is available by appointment. Lectures on a variety of health and fitness topics are held monthly in both English and Spanish. Provides a variety of physical fitness opportunities, including a garden club, yoga, tai chi, soul line dance and aerobics classes geared toward older adults. Also provides free monthly blood pressure screenings, quarterly blood glucose monitoring and health screenings by a visiting nurse. Offers a Brown Bag Program in which low-income seniors can receive a bag of groceries each week for a membership fee of $10 a year. Offers Spanish lessons. Formerly known as Peninsula Family Service, Fair Oaks Intergenerational Center. Formerly known as the Fair Oaks Senior Center. Formerly known as Family Service Agency of San Mateo County, Fair Oaks Intergenerational Center.","short_desc":"A multipurpose senior citizens' center serving the Redwood City area.","address_attributes":{"street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Fair Oaks Intergenerational Center","street":"2600 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["cgonzalez@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 701-0856"}],"phones_attributes":[{"number":"650 780-7525"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Older adults age 55 or over, ethnic minorities and low-income persons","eligibility":"Age 55 or over for most programs, age 60 or over for lunch program","fees":"$2.50 suggested donation for lunch for age 60 or over, donations for other services appreciated. Cash and checks accepted.","how_to_apply":"Walk in or apply by phone.","service_areas":["Redwood City"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","English Language","RECREATION/LEISURE SERVICES","Arts and Crafts","Sports/Games/Exercise","Brown Bag Food Programs","Congregate Meals/Nutrition Sites","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["City","County","Donations","Fees","Fundraising"]}]},{"name":"Second Career Employment Program","contacts_attributes":[{"name":"Brenda Brown","title":"Director, Second Career Services"}],"description":"Provides training and job placement to eligible people age 55 or over who meet certain income qualifications. An income of 125% of poverty level or less is required for subsidized employment and training. (No income requirements for job matchup program.) If a person seems likely to be qualified after a preliminary phone call or visit, he or she must complete an application at this office. Staff will locate appropriate placements for applicants, provide orientation and on-the-job training and assist with finding a job outside the program. Any county resident, regardless of income, age 55 or over has access to the program, job developers and the job bank. Also provides referrals to classroom training. Formerly known as Family Service Agency of San Mateo County, Senior Employment Services.","short_desc":"Provides training and job placement to eligible persons age 55 or over. All persons age 55 or over have access to information in the program's job bank.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"PFS Second Career Services","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":"Monday-Friday, 9-5","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Chinese (Mandarin)","Filipino (Tagalog)","Italian","Spanish"],"emails":["bbrown@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4302"}],"phones_attributes":[{"number":"650 403-4300","extension":" Ext. 4385"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Residents of San Mateo County age 55 or over","eligibility":"Age 55 or over, county resident and willing and able to work. Income requirements vary according to program","fees":"None. Donations requested of clients who can afford it.","how_to_apply":"Apply by phone for an appointment.","service_areas":["San Mateo County"],"keywords":["EMPLOYMENT/TRAINING SERVICES","Job Development","Job Information/Placement/Referral","Job Training","Job Training Formats","Job Search/Placement","Older Adults"],"wait":"Varies.","funding_sources":["County","Federal","State"]}]},{"name":"Senior Peer Counseling","contacts_attributes":[{"name":"Howard Lader, LCSW","title":"Manager, Senior Peer Counseling"}],"description":"Offers supportive counseling services to San Mateo County residents age 55 or over. Volunteer counselors are selected and professionally trained to help their peers face the challenges and concerns of growing older. Training provides topics that include understanding depression, cultural competence, sexuality, community resources, and other subjects that are relevant to working with older adults. After completion of training, weekly supervision groups are provided for the volunteer senior peer counselors, as well as quarterly in-service training seminars. Peer counseling services are provided in English, Spanish, Chinese (Cantonese and Mandarin), Tagalog, and to the lesbian, gay, bisexual, transgender older adult community. Formerly known as Family Service Agency of San Mateo County, Senior Peer Counseling.","short_desc":"Offers supportive counseling services to older persons.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address_attributes":{"attention":"PFS Senior Peer Counseling","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94403"},"hours":"Any time that is convenient for the client and peer counselor","transportation":"Service is provided in person's home or at a mutually agreed upon location.","accessibility":[],"languages":["Chinese (Cantonese)","Chinese (Mandarin)","Filipino (Tagalog)","Spanish"],"emails":null,"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","department":"English"}],"urls":null,"services_attributes":[{"audience":"Older adults age 55 or over who can benefit from counseling","eligibility":"Resident of San Mateo County age 55 or over","fees":"None.","how_to_apply":"Phone for information (403-4300 Ext. 4322).","service_areas":["San Mateo County"],"keywords":["Geriatric Counseling","Older Adults","Gay, Lesbian, Bisexual, Transgender Individuals"],"wait":"Varies.","funding_sources":["County","Donations","Grants"]}]},{"name":"Family Visitation Center","contacts_attributes":[{"name":"Kimberly Pesavento","title":"Director of Visitation"}],"description":"Provides supervised visitation services and a neutral site for parents to carry out supervised exchanges of children in a safe manner. Therapeutic visitation and other counseling services available. Kids in the Middle is an education class for separating or divorcing parents. The goal is to enable parents to focus on their children's needs while the family is going through separation, divorce or other transition. The class explores the psychological aspects of divorce and its impact on children, builds effective communication techniques and points out areas in which outside help may be indicated. The fee is $50 for working parents, $15 for unemployed people. Classes are scheduled on Saturdays and Sundays and held at various sites throughout the county. Call 650-403-4300 ext. 4500 to register. Formerly known as Family Service Agency of San Mateo County, Family Visitation Center.","short_desc":"Provides supervised visitation services and a neutral site for parents in extremely hostile divorces to carry out supervised exchanges of children.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"PFS Family Visitation Center","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":"Monday, 10-6; Tuesday-Friday, 10-8; Saturday, Sunday, 9:30-5:30","transportation":"SAMTRANS stops within 1 block, CALTRAIN stops within 4 blocks.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":["kpesavento@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","extension":"4500"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Parents, children, families with problems of custody disputes, domestic violence or substance abuse, families going through a separation or divorce","eligibility":"None","fees":"Vary according to income ($5-$90). Cash, checks and credit cards accepted.","how_to_apply":"Apply by phone.","service_areas":["San Mateo County"],"keywords":["INDIVIDUAL AND FAMILY DEVELOPMENT SERVICES","Growth and Adjustment","LEGAL AND CRIMINAL JUSTICE SERVICES","Mediation","Parental Visitation Monitoring","Divorce Counseling","Youth"],"wait":"No wait.","funding_sources":["County","Donations","Grants"]}]},{"name":"Economic Self - Sufficiency Program","contacts_attributes":[{"name":"Joe Bloom","title":"Financial Empowerment Programs Program Director"}],"description":"Provides fixed 8% short term loans to eligible applicants for the purchase of a reliable, used autmobile. Loans are up to $6,000 over 2 1/2 years (30 months). Funds must go towards the entire purchase of the automobile. Peninsula Family Service originates loans and collaborates with commercial partner banks to service the loans, helping clients build credit histories. Formerly known as Family Service Agency of San Mateo County, Ways to Work Family Loan Program.","short_desc":"Makes small loans to working families.","address_attributes":{"street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"mail_address_attributes":{"attention":"Economic Self - Sufficiency Program","street":"24 Second Avenue","city":"San Mateo","state":"CA","zip":"94401"},"hours":null,"transportation":"SAMTRANS stops within 1 block. CALTRAIN stops within 6 blocks.","accessibility":["wheelchair"],"languages":["Hindi","Spanish"],"emails":["waystowork@peninsulafamilyservice.org"],"faxes_attributes":[{"number":"650 403-4303"}],"phones_attributes":[{"number":"650 403-4300","extension":"4100"}],"urls":["http://www.peninsulafamilyservice.org"],"services_attributes":[{"audience":"Target group: Low-income working families with children transitioning from welfare to work and poor or who do not have access to conventional credit","eligibility":"Eligibility: Low-income family with legal custody of a minor child or an involved parent of a dependent minor child. Must reside and/or work in San Mateo County. Must be working and have verifiable income and ability to pay off loan. No bankruptcies in the past two years and unable to qualify for other funding sources. Loans approved by loan committee.","fees":"$60 application fee. Cash or checks accepted.","how_to_apply":"Phone for information.","service_areas":["San Mateo County"],"keywords":["COMMUNITY SERVICES","Speakers","Automobile Loans"],"wait":null,"funding_sources":["County","Grants","State"]}]}]} +{"name":"Peninsula Volunteers","locations":[{"name":"Little House","contacts_attributes":[{"name":"Peter Olson","title":"Little House Director"},{"name":" Bart Charlow","title":"Executive Director, Peninsula Volunteers"}],"description":"A multipurpose center offering a wide variety of recreational, education and cultural activities. Lifelong learning courses cover topics such as music, art, languages, etc., are hosted at this location. Little House offers a large variety of classes including arts and crafts, jewelry, languages, current events, lapidary, woodwork, painting, and fitness courses (yoga, strength straining, tai chi). There are monthly art and cultural lectures, movie showings, and a computer center. Recreation activities include mah jong, pinochle, ballroom dancing, bridge, trips and tours. Partners with the Sequoia Adult Education Program. The Alzheimer's Cafe, open the third Tuesday of every month from 2:00 - 4:00 pm, is a place that brings together people liviing with dementia, their families, and their caregivers. Free and no registration is needed. The Little House Community Service Desk offers information and referrals regarding social service issues, such as housing, food, transportation, health insurance counseling, and estate planning. Massage, podiatry, and acupuncture are available by appointment. Lunch is served Monday-Friday, 11:30 am-1:00 pm. Prices vary according to selection.","short_desc":"A multipurpose senior citizens' center.","address_attributes":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address_attributes":{"attention":"Little House","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"hours":"Monday-Thursday, 8 am-9 pm; Friday, 8-5","transportation":"SAMTRANS stops within 3 blocks, RediWheels and Menlo Park Shuttle stop at door.","accessibility":["disabled_parking","wheelchair"],"languages":["Filipino (Tagalog)","Spanish"],"emails":["polson@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 326-9547"}],"phones_attributes":[{"number":"650 326-2025"}],"urls":["http://www.penvol.org/littlehouse"],"services_attributes":[{"audience":"Any age","eligibility":"None","fees":"$55 per year membership dues. Classes have fees. Discounts are available for members. Cash, checks and credit cards accepted.","how_to_apply":"Walk in or apply by phone for membership application.","service_areas":["San Mateo County","Santa Clara County"],"keywords":["ADULT PROTECTION AND CARE SERVICES","In-Home Supportive","Meal Sites/Home-delivered Meals","COMMUNITY SERVICES","Group Support","Information and Referral","EDUCATION SERVICES","Adult","HEALTH SERVICES","Education/Information","Family Support","Individual/Group Counseling","Screening/Immunization","RECREATION/LEISURE SERVICES","Sports/Games/Exercise","Community Adult Schools","Senior Centers","Older Adults"],"wait":"No wait.","funding_sources":["Fees","Fundraising","Grants","Membership dues"]}]},{"name":"Rosener House Adult Day Services","contacts_attributes":[{"name":"Bart Charlow","title":"Executive Director, Peninsula Volunteers"},{"name":" Barbara Kalt","title":"Director"}],"description":"Rosener House is a day center for older adults who may be unable to live independently but do not require 24-hour nursing care, may be isolated and in need of a planned activity program, may need assistance with activities of daily living or are living in a family situation where the caregiver needs respite from giving full-time care. Assists elderly persons to continue to live with family or alone rather than moving to a skilled nursing facility. Activities are scheduled Monday-Friday, 10 am-2:30 pm, and participants may come two to five days per week. The facility is open from 8 am to 5:30 pm for participants who need to remain all day. Small group late afternoon activities are held from 3-5:30 pm. The program provides a noon meal including special diets as required. Services offered include social and therapeutic recreational activities, individual and family counseling and occupational, physical and speech therapy. A registered nurse is available daily. The Dementia and Alzheimer's Services Program provides specialized activities in a supportive environment for participants with Alzheimer's disease and other dementias. Holds a weekly support group for caregivers. An early memory loss class for independent adults, \"Minds in Motion\" meets weekly at Rosener House on Wednesday mornings. Call for more information.","short_desc":"A day center for adults age 50 or over.","address_attributes":{"street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"mail_address_attributes":{"attention":"Rosener House","street":"500 Arbor Road","city":"Menlo Park","state":"CA","zip":"94025"},"hours":"Monday-Friday, 8-5:30","transportation":"Transportation can be arranged via Redi-Wheels or Outreach.","accessibility":["ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish","Filipino (Tagalog)","Vietnamese"],"emails":["bkalt@peninsulavolunteers.org","fmarchick@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 322-4067"}],"phones_attributes":[{"number":"650 322-0126"}],"urls":["http://www.penvol.org/rosenerhouse"],"services_attributes":[{"audience":"Older adults who have memory or sensory loss, mobility limitations and may be lonely and in need of socialization","eligibility":"Age 18 or over","fees":"$85 per day. Vary according to income for those unable to pay full fee. Cash, checks, credit cards, private insurance and vouchers accepted.","how_to_apply":"Apply by phone or be referred by a doctor, social worker or other professional. All prospective participants are interviewed individually before starting the program. A recent physical examination is required, including a TB test.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Los Altos","Los Altos Hills","Menlo Park","Mountain View","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Sunnyvale","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Adult Day Health Care","Dementia Management","Adult Day Programs","Older Adults"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"Meals on Wheels - South County","contacts_attributes":[{"name":"Marilyn Baker-Venturini","title":"Director"},{"name":" Graciela Hernandez","title":"Assistant Manager"},{"name":" Julie Avelino","title":"Assessment Specialist"}],"description":"Delivers a hot meal to the home of persons age 60 or over who are primarily homebound and unable to prepare their own meals, and have no one to prepare meals. Also, delivers a hot meal to the home of disabled individuals ages 18-59. Meals are delivered between 9 am-1:30 pm, Monday-Friday. Special diets are accommodated: low fat, low sodium, and low sugar.","short_desc":"Will deliver a hot meal to the home of persons age 60 or over who are homebound and unable to prepare their own meals. Can provide special diets.","address_attributes":{"street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"mail_address_attributes":{"attention":"Meals on Wheels - South County","street":"800 Middle Avenue","city":"Menlo Park","state":"CA","zip":"94025-9881"},"hours":"Delivery times: Monday-Friday, 9-1:30","transportation":"Not necessary for service.","accessibility":null,"languages":["Spanish"],"emails":["mbaker-venturini@peninsulavolunteers.org"],"faxes_attributes":[{"number":"650 326-9547"}],"phones_attributes":[{"number":"650 323-2022"}],"urls":["http://www.peninsulavolunteers.org"],"services_attributes":[{"audience":"Senior citizens age 60 or over, disabled individuals age 18-59","eligibility":"Homebound person unable to cook or shop","fees":"Suggested donation of $4.25 per meal for seniors 60 or over. Mandatory charge of $2 per meal for disabled individuals ages 18-59.","how_to_apply":"Apply by phone.","service_areas":["Atherton","Belmont","East Palo Alto","Menlo Park","Portola Valley","Redwood City","San Carlos","Woodside"],"keywords":["ADULT PROTECTION AND CARE SERVICES","Meal Sites/Home-delivered Mea","HEALTH SERVICES","Nutrition","Home Delivered Meals","Older Adults","Disabilities Issues"],"wait":"No wait.","funding_sources":["County","Donations"]}]}]} +{"name":"Redwood City Public Library","locations":[{"name":"Fair Oaks Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Divisions Manager"}],"description":"Provides general reading material, including bilingual, multi-cultural books, CDs and cassettes, bilingual and Spanish language reference services. School, class and other group visits may be arranged by appointment. The library is a member of the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Fair Oaks Branch","street":"2510 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-7; Friday, 10-5","transportation":"SAMTRANS stops in front.","accessibility":["ramp","restroom","wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"650 569-3371"}],"phones_attributes":[{"number":"650 780-7261"}],"urls":null,"services_attributes":[{"audience":"Ethnic minorities, especially Spanish speaking","eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of residency in California required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City","County"]}]},{"name":"Main Library","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Maria Kramer","title":"Library Division Manager"}],"description":"Provides general reading and media materials, literacy and homework assistance, and programs for all ages. Provides public computers, wireless connectivity, a children's room, teen center, and a local history collection. The library is a member of the Peninsula Library System. The Fair Oaks Branch (650-780-7261) is located at 2510 Middlefield Road and is open Monday-Thursday, 10-7; Friday, 10-5. The Schaberg Branch (650-780-7010) is located at 2140 Euclid Avenue and is open Tuesday-Thursday, 1-6; Saturday, 10-3. The Redwood Shores Branch (650-780-5740) is located at 399 Marine Parkway and is open Monday-Thursday, 10-8; Saturday, 10-5; Sunday 12-5.","short_desc":"Provides general reference and reading materials to adults, teenagers and children.","address_attributes":{"street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Main Library","street":"1044 Middlefield Road","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-9; Friday, Saturday, 10-5; Sunday, 12-5","transportation":"SAMTRANS stops within 1 block; CALTRAIN stops within 1 block.","accessibility":["elevator","tape_braille","ramp","restroom","disabled_parking","wheelchair"],"languages":["Spanish"],"emails":["rclinfo@redwoodcity.org"],"faxes_attributes":[{"number":"650 780-7069"}],"phones_attributes":[{"number":"650 780-7018","department":"Circulation"}],"urls":["http://www.redwoodcity.org/library"],"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a card","fees":"None.","how_to_apply":"Walk in. Proof of California residency to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Schaberg Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"},{"name":" Elizabeth Meeks","title":"Branch Manager"}],"description":"Provides general reading materials, including large-type books, DVD's and CDs, books on CD and some Spanish language materials to children. Offers children's programs and a Summer Reading Club. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"2140 Euclid Avenue.","city":"Redwood City","state":"CA","zip":"94061"},"mail_address_attributes":{"attention":"Schaberg Branch","street":"2140 Euclid Avenue","city":"Redwood City","state":"CA","zip":"94061"},"hours":"Tuesday-Thursday, 1-6, Saturday, 10-3","transportation":"SAMTRANS stops within 1 block.","accessibility":["ramp"],"languages":null,"emails":null,"faxes_attributes":[{"number":"650 365-3738"}],"phones_attributes":[{"number":"650 780-7010"}],"urls":null,"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a library card for borrowing materials","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]},{"name":"Project Read","contacts_attributes":[{"name":"Kathy Endaya","title":"Director"}],"description":"Offers an intergenerational literacy program for youth and English-speaking adults seeking to improver literacy skills. Adult services include: adult one-to-one tutoring to improve basic skills in reading, writing and critical thinking; Families for Literacy (FFL), a home-based family literacy program for parents who want to be able to read to their young children; and small group/English as a Second Language (ESL). Youth services include: Youth Tutoring, Families in Partnership (FIP); Teen-to-Elementary Student Tutoring, Kids in Partnership (KIP); and computer-aided literacy. Redwood City Friends of Literacy is a fundraising board that helps to support and to fund Redwood City's Project Read. Call for more information about each service.","short_desc":"Offers an intergenerational literacy program for adults and youth seeking to improver literacy skills.","address_attributes":{"street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Project Read","street":"1044 Middlefield Road, 2nd Floor","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Thursday, 10-8:30; Friday, 10-5","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","restroom","disabled_parking"],"languages":null,"emails":["rclread@redwoodcity.org"],"faxes_attributes":[{"number":"650 780-7004"}],"phones_attributes":[{"number":"650 780-7077"}],"urls":["http://www.projectreadredwoodcity.org"],"services_attributes":[{"audience":"Adults, parents, children in 1st-12th grades in the Redwood City school districta","eligibility":"English-speaking adult reading at or below 7th grade level or child in 1st-12th grade in the Redwood City school districts","fees":"None.","how_to_apply":"Walk in or apply by phone, email or webpage registration.","service_areas":["Redwood City"],"keywords":["EDUCATION SERVICES","Adult","Alternative","Literacy","Literacy Programs","Libraries","Public Libraries","Youth"],"wait":"Depends on availability of tutors for small groups and one-on-one.","funding_sources":["City","Donations","Federal","Grants","State"]}]},{"name":"Redwood Shores Branch","contacts_attributes":[{"name":"Dave Genesy","title":"Library Director"}],"description":"Provides general reading materials, including large-type books, videos, music cassettes and CDs, and books on tape. Offers children's programs and a Summer Reading Club. Meeting room is available to nonprofit groups. Participates in the Peninsula Library System.","short_desc":"Provides general reading materials and reference services.","address_attributes":{"street":"399 Marine Parkway.","city":"Redwood City","state":"CA","zip":"94065"},"mail_address_attributes":{"attention":"Redwood Shores Branch","street":"399 Marine Parkway","city":"Redwood City","state":"CA","zip":"94065"},"hours":null,"transportation":null,"accessibility":null,"languages":null,"emails":null,"phones_attributes":[{"number":"650 780-5740"}],"urls":["http://www.redwoodcity.org/library"],"services_attributes":[{"audience":null,"eligibility":"Resident of California to obtain a library card","fees":"None.","how_to_apply":"Walk in. Proof of California residency required to receive a library card.","service_areas":["San Mateo County"],"keywords":["EDUCATION SERVICES","Library","Libraries","Public Libraries"],"wait":"No wait.","funding_sources":["City"]}]}]} +{"name":"Salvation Army","locations":[{"name":"Redwood City Corps","contacts_attributes":[{"name":"Andres Espinoza","title":"Captain, Commanding Officer"}],"description":"Provides food, clothing, bus tokens and shelter to individuals and families in times of crisis from the Redwood City Corps office and community centers throughout the county. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Counseling and translation services (English/Spanish) are available either on a walk-in basis or by appointment. Rental assistance with available funds. Another office (described separately) is located at 409 South Spruce Avenue, South San Francisco (650-266-4591).","short_desc":"Provides a variety of emergency services to low-income persons. Also sponsors recreational and educational activities.","address_attributes":{"street":"660 Veterans Blvd.","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Salvation Army","street":"P.O. Box 1147","city":"Redwood City","state":"CA","zip":"94064"},"hours":null,"transportation":"SAMTRANS stops nearby.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"650 364-1712"}],"phones_attributes":[{"number":"650 368-4643"}],"urls":["http://www.tsagoldenstate.org"],"services_attributes":[{"audience":"Individuals or families with low or no income and/or trying to obtain public assistance","eligibility":"None for most services. For emergency assistance, must have low or no income and be willing to apply for public assistance","fees":"None.","how_to_apply":"Call for appointment. Referral from human service professional preferred for emergency assistance.","service_areas":["Atherton","Belmont","Burlingame","East Palo Alto","Foster City","Menlo Park","Palo Alto","Portola Valley","Redwood City","San Carlos","San Mateo","Woodside"],"keywords":["COMMUNITY SERVICES","Interpretation/Translation","EMERGENCY SERVICES","Shelter/Refuge","FINANCIAL ASSISTANCE SERVICES","Utilities","MENTAL HEALTH SERVICES","Individual/Group Counseling","Food Pantries","Homeless Shelter","Rental Deposit Assistance","Utility Service Payment Assistance"],"wait":"Up to 20 minutes.","funding_sources":["Donations","Grants"]}]},{"name":"Adult Rehabilitation Center","contacts_attributes":[{"name":"Jack Phillips","title":"Administrator"}],"description":"Provides a long-term (6-12 month) residential rehabilitation program for men and women with substance abuse and other problems. Residents receive individual counseling, and drug and alcohol education. The spiritual side of recovery is address_attributesed through chapel services and Bible study as well as 12-step programs. Nicotine cessation is a part of the program. Residents must be physically able to work, seeking treatment for substance abuse, sober long enough to pass urine drug screen before entering and agreeable to participating in weekly 12-step programs such as Alcoholics Anonymous or Narcotics Anonymous. Pinehurst Lodge is a separate facility for women only. Transition houses for men and women graduates also available.","short_desc":"Long-term (6-12 month) residential treatment program for men/women age 21-60.","address_attributes":{"street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"mail_address_attributes":{"attention":"Adult Rehabilitation Center","street":"1500 Valencia Street","city":"San Francisco","state":"CA","zip":"94110"},"hours":"Monday-Friday, 8-4","transportation":"MUNI - 26 Valencia, Mission Street lines.","accessibility":["wheelchair"],"languages":["Spanish"],"emails":null,"faxes_attributes":[{"number":"415 285-1391"}],"phones_attributes":[{"number":"415 643-8000"}],"urls":null,"services_attributes":[{"audience":"Adult alcoholic/drug addictive men and women with social and spiritual problems","eligibility":"Age 21-60, detoxed, physically able and willing to participate in a work therapy program","fees":"None.","how_to_apply":"Walk in or through other agency referral.","service_areas":["Alameda County","Contra Costa County","Marin County","San Francisco County","San Mateo County","Santa Clara County","Northern California"],"keywords":["ALCOHOLISM SERVICES","Residential Care","DRUG ABUSE SERVICES"],"wait":"Varies according to available beds for men and women. Women have a longer wait due to small number of beds statewide.","funding_sources":["Donations","Sales"]}]},{"name":"Sunnyvale Corps","contacts_attributes":[{"name":"James Lee","title":"Commanding Officer"}],"description":"Provides emergency assistance including food and clothing for persons in immediate need. Provides PG&E assistance through REACH program. Youth programs offer tutoring, music and troops. Information on related resources is available. Also provides rental assistance when funds are available.","short_desc":"Provides emergency assistance to persons in immediate need and offers after school activities and summer day camp program.","address_attributes":{"street":"1161 South Bernardo","city":"Sunnyvale","state":"CA","zip":"94087"},"mail_address_attributes":{"attention":"Salvation Army","street":"P.O. Box 61868","city":"Sunnyvale","state":"CA","zip":"94088"},"hours":"Monday-Friday, 9-4","transportation":"VTA stops within 4 blocks.","accessibility":[],"languages":["Korean"],"emails":["william_nichols@usw.salvationarmy.org"],"faxes_attributes":[{"number":"408 720-8075"}],"phones_attributes":[{"number":"408 720-0420"}],"urls":null,"services_attributes":[{"audience":null,"eligibility":"None for emergency assistance","fees":"None for emergency services. Vary for after school activities. Cash and checks accepted.","how_to_apply":"Walk in. Written application, identification required for emergency assistance.","service_areas":["Los Altos","Mountain View","Sunnyvale"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","CHILD PROTECTION AND CARE SERVICES","Day Care","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","RECREATION/LEISURE SERVICES","Camping","Emergency Food","Clothing","Utility Assistance","Youth Development"],"wait":"No wait.","funding_sources":["Donations","Fees","Grants"]}]},{"name":"South San Francisco Citadel Corps","contacts_attributes":[{"name":"Kenneth Gibson","title":"Major"}],"description":"Provides emergency food, clothing and furniture vouchers to low-income families in times of crisis. Administers Project REACH (Relief for Energy Assistance through Community Help) funds to prevent energy shut-off through a one-time payment. Offers a Saturday morning Homeless Feeding Program at 10:30, as well as Sunday services and spiritual counseling. Provides Christmas toys and Back-to-School clothes and supplies. Offers case management, advocacy and referrals to other agencies.","short_desc":"Provides emergency food and clothing and furniture vouchers to low-income families in times of crisis.","address_attributes":{"street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"mail_address_attributes":{"attention":"Salvation Army","street":"409 South Spruce Avenue","city":"South San Francisco","state":"CA","zip":"94080"},"hours":"Monday-Thursday, 9-4:30","transportation":"SAMTRANS stops within 1 block, BART stops within 3 blocks.","accessibility":["wheelchair"],"languages":null,"emails":null,"faxes_attributes":[{"number":"650 266-2594"},{"number":"650 266-4594"}],"phones_attributes":[{"number":"650 266-4591"}],"urls":["http://www.tsagoldenstate.org"],"services_attributes":[{"audience":null,"eligibility":"Low-income families","fees":"None.","how_to_apply":"Call for information.","service_areas":["Brisbane","Colma","Daly City","Millbrae","Pacifica","San Bruno","South San Francisco"],"keywords":["COMMODITY SERVICES","Clothing/Personal Items","COMMUNITY SERVICES","Information and Referral","EMERGENCY SERVICES","Food Boxes/Food Vouchers","FINANCIAL ASSISTANCE SERVICES","Utilities","Emergency Food","Food Pantries","Furniture","Clothing","Utility Assistance","School Supplies","Case/Care Management","Holiday Programs","Pastoral Counseling","Low Income"],"wait":null,"funding_sources":["Donations"]}]}]} +{"name":"Samaritan House","locations":[{"name":"Redwood City Free Medical Clinic","contacts_attributes":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical care to those in need. Offers basic medical exams for adults and tuberculosis screening. Assists the individual to access other services in the community. By appointment only, Project Smile provides a free dental exam, dental cleaning and oral hygiene instruction for children, age 3-12, of Samaritan House clients.","short_desc":"Provides free medical care to those in need.","address_attributes":{"street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"mail_address_attributes":{"attention":"Redwood City Free Medical Clinic","street":"114 Fifth Avenue","city":"Redwood City","state":"CA","zip":"94063"},"hours":"Monday-Friday, 9-12, 2-5","transportation":"SAMTRANS stops within 2 blocks.","accessibility":["restroom","wheelchair"],"languages":["Spanish"],"emails":["gracie@samaritanhouse.com"],"faxes_attributes":[{"number":"650 839-1457"}],"phones_attributes":[{"number":"650 839-1447"}],"urls":["http://www.samaritanhouse.com"],"services_attributes":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment. Medical visits are by appointment only.","service_areas":["Atherton","East Palo Alto","Menlo Park","Redwood City","San Carlos"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]},{"name":"San Mateo Free Medical Clinic","contacts_attributes":[{"name":"Sharon Petersen","title":"Administrator"}],"description":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","short_desc":"Provides free medical and dental care to those in need. Offers basic medical care for adults.","address_attributes":{"street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"mail_address_attributes":{"attention":"San Mateo Free Medical/Dental","street":"19 West 39th Avenue","city":"San Mateo","state":"CA","zip":"94403"},"hours":"Monday-Friday, 9-12, 1-4","transportation":"SAMTRANS stops within 1 block.","accessibility":["elevator","ramp","wheelchair"],"languages":["Spanish"],"emails":["smcmed@samaritanhouse.com"],"faxes_attributes":[{"number":"650 578-0440"}],"phones_attributes":[{"number":"650 578-0400"}],"urls":["http://www.samaritanhouse.com"],"services_attributes":[{"audience":null,"eligibility":"Low-income person without access to health care","fees":"None.","how_to_apply":"Call for screening appointment (650-347-3648).","service_areas":["Belmont","Burlingame","Foster City","Millbrae","San Carlos","San Mateo"],"keywords":["HEALTH SERVICES","Outpatient Care","Community Clinics"],"wait":"Varies.","funding_sources":["Donations","Grants"]}]}]} {"name":"Location with no phone", "locations":[{"accessibility" : [], "description" : "no phone", "emails" : [], "faxes_attributes" : [], "hours" : null, "kind" : "test", "languages" : null, "mail_address_attributes" : { "attention" : "", "street" : "puma", "city" : "fairfax", "state" : "VA", "zip" : "22031" }, "name" : "Location with no phone", "phones_attributes" : [], "short_desc" : "no phone", "transportation" : null, "urls" : null, "services_attributes":[{"audience":""}] } ] } {"name":"Admin Test Org", "locations":[{"accessibility" : [ "elevator", "restroom" ], "address_attributes" : { "city" : "fairfax", "state" : "va", "street" : "bozo", "zip" : "12345" }, "contacts_attributes" : [ { "name" : "Moncef", "title" : "Director" } ], "latitude" : 42.8142432, "longitude": -73.9395687, "description" : "This is a description", "emails" : [ "eml@example.org" ], "faxes_attributes" : [ { "number" : "2025551212", "department" : "CalFresh" } ], "hours" : "Monday-Friday 10am-5pm", "kind" : "test", "languages" : null, "name" : "Admin Test Location", "phones_attributes" : [ { "number" : "7035551212", "vanity_number" : "703555-ABCD", "extension" : "x1223", "department" : "CalFresh" } ], "short_desc" : "This is a short description", "transportation" : "SAMTRANS stops within 1/2 mile.", "urls" : [ "http://codeforamerica.org" ], "services_attributes":[{"service_areas":["San Mateo County"]}] }] } From aabfa3d227e49f5997656c944eecc2e4655211ad Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Thu, 10 Apr 2014 18:35:59 -0400 Subject: [PATCH 15/17] Add specs for accessing locs, orgs & categories by their old slug --- spec/api/categories_spec.rb | 8 +++++++- spec/api/locations_spec.rb | 15 +++++++++++++++ spec/api/organizations_spec.rb | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/spec/api/categories_spec.rb b/spec/api/categories_spec.rb index 8a54c48ae..14464a2a7 100644 --- a/spec/api/categories_spec.rb +++ b/spec/api/categories_spec.rb @@ -59,10 +59,10 @@ before :each do @food = Category.create!(:name => "Food", :oe_id => "101") @food.update_attributes!(name: "Emergency Food") - get "/api/categories" end it "displays the category's latest slug" do + get "/api/categories" represented = [{ "id" => @food.id, "depth" => 0, @@ -73,6 +73,12 @@ }] json.should == represented end + + it "is accessible by its old slug" do + @food.children.create!(name: "Community Gardens", oe_id: "101-01") + get "/api/categories/food/children" + expect(json.first["name"]).to eq("Community Gardens") + end end end end \ No newline at end of file diff --git a/spec/api/locations_spec.rb b/spec/api/locations_spec.rb index ec4d0bc8d..9627e60e4 100644 --- a/spec/api/locations_spec.rb +++ b/spec/api/locations_spec.rb @@ -655,6 +655,21 @@ end end + describe "Update a location's slug" do + before(:each) do + @loc = create(:location) + @token = ENV["ADMIN_APP_TOKEN"] + end + + it "is accessible by its old slug" do + put "api/locations/#{@loc.id}", + { :name => "new name" }, + { 'HTTP_X_API_TOKEN' => @token } + get "api/locations/vrs-services" + expect(json["name"]).to eq("new name") + end + end + describe "Create a location (POST /api/locations/)" do before(:each) do org = create(:organization) diff --git a/spec/api/organizations_spec.rb b/spec/api/organizations_spec.rb index d307e599c..f53b2e9c3 100644 --- a/spec/api/organizations_spec.rb +++ b/spec/api/organizations_spec.rb @@ -157,6 +157,13 @@ json.first["organization"]["name"].should == "testorg" end + it "is accessible by its old slug" do + put "api/organizations/#{@org.id}", + { :name => "new name" }, + { 'HTTP_X_API_TOKEN' => @token } + get "api/organizations/parent-agency" + expect(json["name"]).to eq("new name") + end end end From eeac5f29ebb79ea44d3405ed890452260911b1b4 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Wed, 16 Apr 2014 12:05:48 -0400 Subject: [PATCH 16/17] Add endpoints for new models to support admin interface With the move from Mongo to Postgres, the admin interface had to change the way it communicates with the API to update the data, so the API had to add endpoints for all the new models. --- app/api/ohana.rb | 197 +++++++++++++++++++++++++++++-- app/models/address.rb | 9 +- app/models/fax.rb | 2 +- app/models/location.rb | 17 ++- app/models/mail_address.rb | 4 +- app/models/organization.rb | 2 +- app/models/phone.rb | 2 +- spec/api/address_spec.rb | 149 +++++++++++++++++++++++ spec/api/contacts_spec.rb | 131 ++++++++++++++++++++ spec/api/faxes_spec.rb | 107 +++++++++++++++++ spec/api/locations_spec.rb | 55 +-------- spec/api/mail_address_spec.rb | 153 ++++++++++++++++++++++++ spec/api/phones_spec.rb | 107 +++++++++++++++++ spec/factories/contacts.rb | 5 + spec/models/fax_spec.rb | 3 +- spec/models/organization_spec.rb | 8 +- spec/models/phone_spec.rb | 30 ++--- 17 files changed, 872 insertions(+), 109 deletions(-) create mode 100644 spec/api/address_spec.rb create mode 100644 spec/api/contacts_spec.rb create mode 100644 spec/api/faxes_spec.rb create mode 100644 spec/api/mail_address_spec.rb create mode 100644 spec/api/phones_spec.rb diff --git a/app/api/ohana.rb b/app/api/ohana.rb index d84a7caba..de0c32eb5 100644 --- a/app/api/ohana.rb +++ b/app/api/ohana.rb @@ -75,13 +75,13 @@ class API < Grape::API params[:emails] = params[:emails].delete_if { |email| email.blank? } end - loc.update_attributes!(params) + loc.update!(params) present loc, with: Entities::Location end desc "Delete a location" params do - requires :id, type: String, desc: "Location ID" + requires :id, type: Integer, desc: "Location ID" end delete ':id' do authenticate! @@ -96,7 +96,7 @@ class API < Grape::API present loc, with: Entities::Location end - segment '/:locations_id' do + segment '/:location_id' do resource '/nearby' do desc "Returns locations near the one queried." params do @@ -106,7 +106,7 @@ class API < Grape::API get do #garner.options(expires_in: 30.minutes) do - location = Location.find(params[:locations_id]) + location = Location.find(params[:location_id]) nearby = Location.nearby(location, params) set_link_header(nearby) nearby @@ -116,25 +116,196 @@ class API < Grape::API resource '/services' do desc "Create a new service for this location" + params do + requires :location_id, type: Integer + end post do authenticate! - location = Location.find(params[:locations_id]) + location = Location.find(params[:location_id]) location.services.create!(params) location.services.last end end + resource '/address' do + desc "Create a new address for this location" + params do + requires :location_id, type: Integer + end + post do + authenticate! + location = Location.find(params[:location_id]) + location.create_address!(params) + location.address + end - resource '/contacts' do - desc "Delete all contacts for a location" + desc "Update an address" + params do + requires :location_id, type: Integer, desc: "Address ID" + end + patch do + authenticate! + location = Location.find(params[:location_id]) + location.address.update!(params) + location.address + end + + desc "Delete an address" + params do + requires :location_id, type: Integer, desc: "Location ID" + end + delete do + authenticate! + location = Location.find(params[:location_id]) + address_id = location.address.id + location.address_attributes = { id: address_id, _destroy: "1" } + res = location.save + + if res == false + error!("A location must have at least one address type.", 400) + end + end + end + + resource '/mail_address' do + desc "Create a new mailing address for this location" + params do + requires :location_id, type: Integer + end + post do + authenticate! + location = Location.find(params[:location_id]) + location.create_mail_address!(params) + location.mail_address + end + + desc "Update a mailing address" + params do + requires :location_id, type: Integer, desc: "Mail Address ID" + end + patch do + authenticate! + location = Location.find(params[:location_id]) + location.mail_address.update!(params) + location.mail_address + end + + desc "Delete a mailing address" params do - requires :locations_id, type: String + requires :location_id, type: Integer, desc: "Location ID" end delete do authenticate! - loc = Location.find(params[:locations_id]) - loc.update_attributes!(contacts: []) - loc + location = Location.find(params[:location_id]) + mail_address_id = location.mail_address.id + location.mail_address_attributes = + { id: mail_address_id, _destroy: "1" } + res = location.save + if res == false + error!("A location must have at least one address type.", 400) + end + end + end + + resource '/contacts' do + desc "Create a new contact for this location" + params do + requires :location_id, type: Integer + end + post do + authenticate! + location = Location.find(params[:location_id]) + location.contacts.create!(params) + location.contacts.last + end + + desc "Update a contact" + params do + requires :id, type: Integer, desc: "Contact ID" + end + patch ":id" do + authenticate! + contact = Contact.find(params[:id]) + contact.update!(params) + present contact, with: Contact::Entity + end + + desc "Delete a contact" + params do + requires :id, type: Integer, desc: "Contact ID" + end + delete ':id' do + authenticate! + contact = Contact.find(params[:id]) + contact.delete + end + end + + resource '/faxes' do + desc "Create a new fax for this location" + params do + requires :location_id, type: Integer + end + post do + authenticate! + location = Location.find(params[:location_id]) + location.faxes.create!(params) + location.faxes.last + end + + desc "Update a fax" + params do + requires :id, type: Integer, desc: "fax ID" + end + patch ":id" do + authenticate! + fax = Fax.find(params[:id]) + fax.update!(params) + present fax, with: Fax::Entity + end + + desc "Delete a fax" + params do + requires :id, type: Integer, desc: "fax ID" + end + delete ':id' do + authenticate! + fax = Fax.find(params[:id]) + fax.delete + end + end + + resource '/phones' do + desc "Create a new phone for this location" + params do + requires :location_id, type: Integer + end + post do + authenticate! + location = Location.find(params[:location_id]) + location.phones.create!(params) + location.phones.last + end + + desc "Update a phone" + params do + requires :id, type: Integer, desc: "phone ID" + end + patch ":id" do + authenticate! + phone = Phone.find(params[:id]) + phone.update!(params) + present phone, with: Phone::Entity + end + + desc "Delete a phone" + params do + requires :id, type: Integer, desc: "phone ID" + end + delete ':id' do + authenticate! + phone = Phone.find(params[:id]) + phone.delete end end end @@ -201,7 +372,7 @@ class API < Grape::API put ':id' do authenticate! org = Organization.find(params[:id]) - org.update_attributes!(name: params[:name]) + org.update!(name: params[:name]) present org, with: Organization::Entity end @@ -235,7 +406,7 @@ class API < Grape::API params[:service_areas] = [] if params[:service_areas].blank? - service.update_attributes!(params) + service.update!(params) present service, with: Service::Entity end diff --git a/app/models/address.rb b/app/models/address.rb index 08816d400..d693a262c 100644 --- a/app/models/address.rb +++ b/app/models/address.rb @@ -1,10 +1,8 @@ class Address < ActiveRecord::Base - attr_accessible :city, :state, :street, :zip belongs_to :location, touch: true - #validates_presence_of :location validates_presence_of :street, :city, :state, :zip, message: "can't be blank for Address" @@ -19,9 +17,10 @@ class Address < ActiveRecord::Base include Grape::Entity::DSL entity do + expose :id expose :street - expose :city - expose :state - expose :zip + expose :city + expose :state + expose :zip end end \ No newline at end of file diff --git a/app/models/fax.rb b/app/models/fax.rb index 41b28e1e8..30da51e72 100644 --- a/app/models/fax.rb +++ b/app/models/fax.rb @@ -2,7 +2,7 @@ class Fax < ActiveRecord::Base belongs_to :location, touch: true attr_accessible :number, :department - validates_presence_of :number + validates_presence_of :number, message: "can't be blank for Fax" validates_formatting_of :number, :using => :us_phone, message: "%{value} is not a valid US fax number" diff --git a/app/models/location.rb b/app/models/location.rb index aa90e4d75..fa9ca1575 100644 --- a/app/models/location.rb +++ b/app/models/location.rb @@ -60,7 +60,10 @@ def mail_address_city belongs_to :organization, touch: true has_one :address, dependent: :destroy - accepts_nested_attributes_for :address, :reject_if => :all_blank + validates_presence_of :address, + :message => "A location must have at least one address type.", + :unless => Proc.new { |loc| loc.mail_address.present? } + accepts_nested_attributes_for :address, :allow_destroy => true has_many :contacts, dependent: :destroy accepts_nested_attributes_for :contacts @@ -69,7 +72,10 @@ def mail_address_city accepts_nested_attributes_for :faxes has_one :mail_address, dependent: :destroy - accepts_nested_attributes_for :mail_address, :reject_if => :all_blank + validates_presence_of :mail_address, + :message => "A location must have at least one address type.", + :unless => Proc.new { |loc| loc.address.present? } + accepts_nested_attributes_for :mail_address, :allow_destroy => true has_many :phones, dependent: :destroy accepts_nested_attributes_for :phones @@ -92,8 +98,6 @@ def mail_address_city validates_presence_of :description, :organization, :name, message: "can't be blank for Location" - validate :address_presence - ## Uncomment the line below if you want to require a short description. ## We recommend having a short description so that web clients can display ## an overview within the search results. See smc-connect.org as an example. @@ -477,11 +481,6 @@ def self.nearby(loc, params={}) end end - def address_presence - unless address or mail_address - errors[:base] << "A location must have at least one address type." - end - end def url "#{ENV["API_BASE_URL"]}locations/#{self.id}" diff --git a/app/models/mail_address.rb b/app/models/mail_address.rb index 57cd0cf2b..bba9bb655 100644 --- a/app/models/mail_address.rb +++ b/app/models/mail_address.rb @@ -2,7 +2,6 @@ class MailAddress < ActiveRecord::Base attr_accessible :attention, :city, :state, :street, :zip belongs_to :location, touch: true - #validates_presence_of :location normalize_attributes :street, :city, :state, :zip @@ -18,7 +17,8 @@ class MailAddress < ActiveRecord::Base include Grape::Entity::DSL entity do - expose :attention, :unless => lambda { |o,_| o.attention.blank? } + expose :id + expose :attention expose :street expose :city expose :state diff --git a/app/models/organization.rb b/app/models/organization.rb index 325264d2f..b3bb2699e 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -20,7 +20,7 @@ def slug_candidates serialize :urls, Array - validates_presence_of :name + validates_presence_of :name, message: "can't be blank for Organization" paginates_per Rails.env.test? ? 1 : 30 diff --git a/app/models/phone.rb b/app/models/phone.rb index f5462f889..ff433f908 100644 --- a/app/models/phone.rb +++ b/app/models/phone.rb @@ -3,7 +3,7 @@ class Phone < ActiveRecord::Base normalize_attributes :department, :extension, :number, :vanity_number - validates_presence_of :number + validates_presence_of :number, message: "can't be blank for Phone" validates_formatting_of :number, :using => :us_phone, message: "%{value} is not a valid US phone number" diff --git a/spec/api/address_spec.rb b/spec/api/address_spec.rb new file mode 100644 index 000000000..4d4e7d49c --- /dev/null +++ b/spec/api/address_spec.rb @@ -0,0 +1,149 @@ +require 'spec_helper' + +describe Ohana::API do + include DefaultUserAgent + include Features::SessionHelpers + + before(:each) do + @loc = create(:location) # the factory already creates an address + @address = @loc.address + @token = ENV["ADMIN_APP_TOKEN"] + @attrs = { street: "foo", city: "bar", state: "CA", zip: "90210" } + end + + describe "PATCH /api/locations/:location/address" do + it "doesn't allow setting non-whitelisted attributes" do + patch "api/locations/#{@loc.id}/address", + { :foo => "bar" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json).to_not include "foo" + end + + it "allows setting whitelisted attributes" do + patch "api/locations/#{@loc.id}/address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json["city"]).to eq "bar" + end + + it "doesn't add a new address" do + patch "api/locations/#{@loc.id}/address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + get "api/locations/#{@loc.id}" + expect(json["address"]["street"]).to eq "foo" + end + + it "requires valid location id" do + patch "api/locations/2/address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(404) + json["message"]. + should include "The requested resource could not be found." + end + + it "requires address street" do + patch "api/locations/#{@loc.id}/address", + @attrs.merge!(street: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Street can't be blank for Address" + end + + it "requires address city" do + patch "api/locations/#{@loc.id}/address", + @attrs.merge!(city: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "City can't be blank for Address" + end + + it "requires address state" do + patch "api/locations/#{@loc.id}/address", + @attrs.merge!(state: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "State can't be blank for Address" + end + + it "requires address zip" do + patch "api/locations/#{@loc.id}/address", + @attrs.merge!(zip: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Zip can't be blank for Address" + end + + it "validates length of state" do + patch "api/locations/#{@loc.id}/address", + @attrs.merge!(state: "C"), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + expect(json["message"]) + .to include "Please enter a valid 2-letter state abbreviation" + end + + it "validates zip format" do + patch "api/locations/#{@loc.id}/address", + @attrs.merge!(zip: "901"), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "901 is not a valid ZIP code" + end + + it "doesn't allow updating a address witout a valid token" do + patch "api/locations/#{@loc.id}/address", + @attrs, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "DELETE /api/locations/:location/address" do + it "deletes the address" do + @loc.create_mail_address!(attributes_for(:mail_address)) + delete "api/locations/#{@loc.id}/address", + {}, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json).to_not include "address" + end + + it "doesn't allow deleting a address witout a valid token" do + delete "api/locations/#{@loc.id}/address", + {}, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + + it "doesn't delete the address if a mailing address isn't present" do + delete "api/locations/#{@loc.id}/address", + {}, + { 'HTTP_X_API_TOKEN' => @token } + expect(json["error"]). + to eq "A location must have at least one address type." + end + end + + describe "POST /api/locations/:location/address" do + it "replaces the address for the specified location" do + post "api/locations/#{@loc.id}/address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json["address"]["street"]).to eq "foo" + end + + it "doesn't allow creating a address witout a valid token" do + post "api/locations/#{@loc.id}/address", + @attrs, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end +end \ No newline at end of file diff --git a/spec/api/contacts_spec.rb b/spec/api/contacts_spec.rb new file mode 100644 index 000000000..2579eb496 --- /dev/null +++ b/spec/api/contacts_spec.rb @@ -0,0 +1,131 @@ +require 'spec_helper' + +describe Ohana::API do + include DefaultUserAgent + include Features::SessionHelpers + + before(:each) do + @loc = create(:location) + @contact = @loc.contacts.create!(attributes_for(:contact)) + @token = ENV["ADMIN_APP_TOKEN"] + end + + describe "PATCH /api/locations/:location/contacts/:contact" do + it "doesn't allow setting non-whitelisted attributes" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { :foo => "bar" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json).to_not include "foo" + end + + it "allows setting whitelisted attributes" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { :name => "Moncef" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json["name"]).to eq "Moncef" + end + + it "doesn't add a new contact" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { :name => "Moncef" }, + { 'HTTP_X_API_TOKEN' => @token } + get "api/locations/#{@loc.id}" + expect(json["contacts"].length).to eq 1 + end + + it "requires valid contact id" do + patch "api/locations/#{@loc.id}/contacts/2", + { name: "", title: "cfo" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(404) + json["message"]. + should include "The requested resource could not be found." + end + + it "requires contact name" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { name: "", title: "cfo" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Name can't be blank" + end + + it "requires contact title" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { name: "cfo", title: "" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Title can't be blank" + end + + it "validates contact phone" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { name: "foo", title: "cfo", phone: "703" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "703 is not a valid US phone number" + end + + it "validates contact fax" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { name: "foo", title: "cfo", fax: "703" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "703 is not a valid US fax number" + end + + it "validates contact email" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { name: "foo", title: "cfo", email: "703" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "703 is not a valid email" + end + + it "doesn't allow updating a contact witout a valid token" do + patch "api/locations/#{@loc.id}/contacts/#{@contact.id}", + { name: "foo", title: "cfo" }, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "DELETE /api/locations/:location/contacts/:contact" do + it "deletes the contact" do + delete "api/locations/#{@loc.id}/contacts/#{@contact.id}", + {}, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json).to_not include "contacts" + end + + it "doesn't allow deleting a contact witout a valid token" do + delete "api/locations/#{@loc.id}/contacts/#{@contact.id}", + {}, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "POST /api/locations/:location/contacts" do + it "creates a second contact for the specified location" do + post "api/locations/#{@loc.id}/contacts", + { name: "foo", title: "cfo" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json["contacts"].length).to eq 2 + expect(json["contacts"][1]["name"]).to eq "foo" + end + + it "doesn't allow creating a contact witout a valid token" do + post "api/locations/#{@loc.id}/contacts", + { name: "foo", title: "cfo" }, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end +end \ No newline at end of file diff --git a/spec/api/faxes_spec.rb b/spec/api/faxes_spec.rb new file mode 100644 index 000000000..e3f4df42d --- /dev/null +++ b/spec/api/faxes_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper' + +describe Ohana::API do + include DefaultUserAgent + include Features::SessionHelpers + + before(:each) do + @loc = create(:location) + @fax = @loc.faxes.create!(attributes_for(:fax)) + @token = ENV["ADMIN_APP_TOKEN"] + end + + describe "PATCH /api/locations/:location/faxes/:fax" do + it "doesn't allow setting non-whitelisted attributes" do + patch "api/locations/#{@loc.id}/faxes/#{@fax.id}", + { :foo => "bar" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json).to_not include "foo" + end + + it "allows setting whitelisted attributes" do + patch "api/locations/#{@loc.id}/faxes/#{@fax.id}", + { :number => "703-555-1212" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json["number"]).to eq "703-555-1212" + end + + it "doesn't add a new fax" do + patch "api/locations/#{@loc.id}/faxes/#{@fax.id}", + { :number => "800-222-3333" }, + { 'HTTP_X_API_TOKEN' => @token } + get "api/locations/#{@loc.id}" + expect(json["faxes"].length).to eq 1 + end + + it "requires valid fax id" do + patch "api/locations/#{@loc.id}/faxes/2", + { number: "800-555-1212", department: "Youth Development" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(404) + json["message"]. + should include "The requested resource could not be found." + end + + it "requires fax number" do + patch "api/locations/#{@loc.id}/faxes/#{@fax.id}", + { number: "", department: "foo" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Number can't be blank" + end + + it "validates fax number" do + patch "api/locations/#{@loc.id}/faxes/#{@fax.id}", + { number: "703" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "703 is not a valid US fax number" + end + + it "doesn't allow updating a fax witout a valid token" do + patch "api/locations/#{@loc.id}/faxes/#{@fax.id}", + { number: "800-555-1212" }, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "DELETE /api/locations/:location/faxes/:fax" do + it "deletes the fax" do + delete "api/locations/#{@loc.id}/faxes/#{@fax.id}", + {}, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json).to_not include "faxes" + end + + it "doesn't allow deleting a fax witout a valid token" do + delete "api/locations/#{@loc.id}/faxes/#{@fax.id}", + {}, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "POST /api/locations/:location/faxes" do + it "creates a second fax for the specified location" do + post "api/locations/#{@loc.id}/faxes", + { number: "708-222-5555" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json["faxes"].length).to eq 2 + expect(json["faxes"][1]["number"]).to eq "708-222-5555" + end + + it "doesn't allow creating a fax witout a valid token" do + post "api/locations/#{@loc.id}/faxes", + { number: "800-123-4567" }, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end +end \ No newline at end of file diff --git a/spec/api/locations_spec.rb b/spec/api/locations_spec.rb index 9627e60e4..ac8c6e99a 100644 --- a/spec/api/locations_spec.rb +++ b/spec/api/locations_spec.rb @@ -160,10 +160,11 @@ "id" => @location.id, "accessibility"=>["Information on tape or in Braille", "Disabled Parking"], "address" => { + "id" => @location.address.id, "street" => @location.address.street, - "city" => @location.address.city, - "state" => @location.address.state, - "zip" => @location.address.zip + "city" => @location.address.city, + "state" => @location.address.state, + "zip" => @location.address.zip }, "coordinates" => @location.coordinates, "description" => @location.description, @@ -319,36 +320,6 @@ expect(response.status).to eq(200) end - it "validates contact phone" do - put "api/locations/#{@loc.id}", - { :contacts_attributes => [{ - name: "foo", title: "cfo", phone: "703" }] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"].should include "703 is not a valid US phone number" - end - - it "validates contact fax" do - put "api/locations/#{@loc.id}", - { :contacts_attributes => [{ - name: "foo", title: "cfo", fax: "703" }] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"].should include "703 is not a valid US fax number" - end - - it "validates contact email" do - put "api/locations/#{@loc.id}", - { :contacts_attributes => [{ - name: "foo", title: "cfo", email: "703" }] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"].should include "703 is not a valid email" - end - it "validates admin email" do put "api/locations/#{@loc.id}", { :admin_emails => ["moncef-at-ohanapi.org"] }, @@ -385,24 +356,6 @@ expect(response.status).to eq(200) end - it "requires contact name" do - put "api/locations/#{@loc.id}", - { :contacts_attributes => [{ title: "cfo" }] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"].should include "name can't be blank" - end - - it "requires contact title" do - put "api/locations/#{@loc.id}", - { :contacts_attributes => [{ name: "cfo" }] }, - { 'HTTP_X_API_TOKEN' => @token } - @loc.reload - expect(response.status).to eq(400) - json["message"].should include "title can't be blank" - end - it "requires description" do put "api/locations/#{@loc.id}", { :description => "" }, diff --git a/spec/api/mail_address_spec.rb b/spec/api/mail_address_spec.rb new file mode 100644 index 000000000..a4da09ce6 --- /dev/null +++ b/spec/api/mail_address_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe Ohana::API do + include DefaultUserAgent + include Features::SessionHelpers + + before(:each) do + @loc = create(:location) + @mail_address = @loc.create_mail_address!(attributes_for(:mail_address)) + @token = ENV["ADMIN_APP_TOKEN"] + @attrs = { street: "foo", city: "bar", state: "CA", zip: "90210" } + end + + describe "PATCH /api/locations/:location/mail_address" do + it "doesn't allow setting non-whitelisted attributes" do + patch "api/locations/#{@loc.id}/mail_address", + { :foo => "bar" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json).to_not include "foo" + end + + it "allows setting whitelisted attributes" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json["city"]).to eq "bar" + end + + it "doesn't add a new mail_address" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + get "api/locations/#{@loc.id}" + expect(json["mail_address"]["street"]).to eq "foo" + end + + it "requires valid location id" do + patch "api/locations/2/mail_address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(404) + json["message"]. + should include "The requested resource could not be found." + end + + it "requires mail_address street" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs.merge!(street: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Street can't be blank for Mail Address" + end + + it "requires mail_address city" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs.merge!(city: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "City can't be blank for Mail Address" + end + + it "requires mail_address state" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs.merge!(state: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "State can't be blank for Mail Address" + end + + it "requires mail_address zip" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs.merge!(zip: ""), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Zip can't be blank for Mail Address" + end + + it "validates length of state" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs.merge!(state: "C"), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + expect(json["message"]) + .to include "Please enter a valid 2-letter state abbreviation" + end + + it "validates zip format" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs.merge!(zip: "901"), + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "901 is not a valid ZIP code" + end + + it "doesn't allow updating a mail_address witout a valid token" do + patch "api/locations/#{@loc.id}/mail_address", + @attrs, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "DELETE /api/locations/:location/mail_address" do + it "deletes the mail_address" do + delete "api/locations/#{@loc.id}/mail_address", + {}, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json).to_not include "mail_address" + end + + it "doesn't allow deleting a mail_address witout a valid token" do + delete "api/locations/#{@loc.id}/mail_address", + {}, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + + it "doesn't delete the mail_address if an address isn't present" do + delete "api/locations/#{@loc.id}/address", + {}, + { 'HTTP_X_API_TOKEN' => @token } + + delete "api/locations/#{@loc.id}/mail_address", + {}, + { 'HTTP_X_API_TOKEN' => @token } + + expect(json["error"]). + to eq "A location must have at least one address type." + end + end + + describe "POST /api/locations/:location/mail_address" do + it "replaces the mail_address for the specified location" do + post "api/locations/#{@loc.id}/mail_address", + @attrs, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json["mail_address"]["street"]).to eq "foo" + end + + it "doesn't allow creating a mail_address witout a valid token" do + post "api/locations/#{@loc.id}/mail_address", + @attrs, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end +end \ No newline at end of file diff --git a/spec/api/phones_spec.rb b/spec/api/phones_spec.rb new file mode 100644 index 000000000..d945abb51 --- /dev/null +++ b/spec/api/phones_spec.rb @@ -0,0 +1,107 @@ +require 'spec_helper' + +describe Ohana::API do + include DefaultUserAgent + include Features::SessionHelpers + + before(:each) do + @loc = create(:location) + @phone = @loc.phones.create!(attributes_for(:phone)) + @token = ENV["ADMIN_APP_TOKEN"] + end + + describe "PATCH /api/locations/:location/phones/:phone" do + it "doesn't allow setting non-whitelisted attributes" do + patch "api/locations/#{@loc.id}/phones/#{@phone.id}", + { :foo => "bar" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json).to_not include "foo" + end + + it "allows setting whitelisted attributes" do + patch "api/locations/#{@loc.id}/phones/#{@phone.id}", + { :number => "703-555-1212" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + expect(json["number"]).to eq "703-555-1212" + end + + it "doesn't add a new phone" do + patch "api/locations/#{@loc.id}/phones/#{@phone.id}", + { :number => "800-222-3333" }, + { 'HTTP_X_API_TOKEN' => @token } + get "api/locations/#{@loc.id}" + expect(json["phones"].length).to eq 1 + end + + it "requires valid phone id" do + patch "api/locations/#{@loc.id}/phones/2", + { number: "800-555-1212", department: "Youth Development" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(404) + json["message"]. + should include "The requested resource could not be found." + end + + it "requires phone number" do + patch "api/locations/#{@loc.id}/phones/#{@phone.id}", + { number: "", department: "foo" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "Number can't be blank" + end + + it "validates phone number" do + patch "api/locations/#{@loc.id}/phones/#{@phone.id}", + { number: "703" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response.status).to eq(400) + json["message"].should include "703 is not a valid US phone number" + end + + it "doesn't allow updating a phone witout a valid token" do + patch "api/locations/#{@loc.id}/phones/#{@phone.id}", + { number: "800-555-1212" }, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "DELETE /api/locations/:location/phones/:phone" do + it "deletes the phone" do + delete "api/locations/#{@loc.id}/phones/#{@phone.id}", + {}, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json).to_not include "phones" + end + + it "doesn't allow deleting a phone witout a valid token" do + delete "api/locations/#{@loc.id}/phones/#{@phone.id}", + {}, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end + + describe "POST /api/locations/:location/phones" do + it "creates a second phone for the specified location" do + post "api/locations/#{@loc.id}/phones", + { number: "708-222-5555" }, + { 'HTTP_X_API_TOKEN' => @token } + expect(response).to be_success + get "api/locations/#{@loc.id}" + expect(json["phones"].length).to eq 2 + expect(json["phones"][1]["number"]).to eq "708-222-5555" + end + + it "doesn't allow creating a phone witout a valid token" do + post "api/locations/#{@loc.id}/phones", + { number: "800-123-4567" }, + { 'HTTP_X_API_TOKEN' => "invalid_token" } + expect(response.status).to eq(401) + end + end +end \ No newline at end of file diff --git a/spec/factories/contacts.rb b/spec/factories/contacts.rb index 0911330e0..4dccb2235 100644 --- a/spec/factories/contacts.rb +++ b/spec/factories/contacts.rb @@ -4,5 +4,10 @@ factory :contact do name "Moncef Belyamani" title "CTO" + + factory :foobar do + name "Foo" + title "Bar" + end end end \ No newline at end of file diff --git a/spec/models/fax_spec.rb b/spec/models/fax_spec.rb index 96feab46e..fa01d5b5d 100644 --- a/spec/models/fax_spec.rb +++ b/spec/models/fax_spec.rb @@ -10,7 +10,8 @@ it { should allow_mass_assignment_of(:number) } it { should allow_mass_assignment_of(:department) } - it { should validate_presence_of(:number) } + it { should validate_presence_of(:number). + with_message("can't be blank for Fax") } it { should normalize_attribute(:number). from(" 800-555-1212 "). diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index c6166cbb8..92a54face 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -6,11 +6,9 @@ it { should be_valid } - describe "invalidations" do - context "without a name" do - subject { build(:organization, name: nil)} - it { should_not be_valid } - end + it do + should validate_presence_of(:name). + with_message("can't be blank for Organization") end describe "slug candidates" do diff --git a/spec/models/phone_spec.rb b/spec/models/phone_spec.rb index 04e6169d9..3a978c6ea 100644 --- a/spec/models/phone_spec.rb +++ b/spec/models/phone_spec.rb @@ -25,27 +25,17 @@ it { should normalize_attribute(:vanity_number). from(" 800 YOU-NEED ").to("800 YOU-NEED") } - describe "with invalid data" do - context "without a number" do - subject { build(:phone, number: nil)} - it { should_not be_valid } - end - - context "with an empty number" do - subject { build(:phone, number: "")} - it { should_not be_valid } - end - - context "with a non-US phone" do - subject { build(:phone, number: "33 6 65 08 51 12") } - it { should_not be_valid } - end + it do + should validate_presence_of(:number). + with_message("can't be blank for Phone") end - describe "valid data" do - context "with US phone containing dots" do - subject { build(:phone, number: "123.456.7890") } - it { should be_valid } - end + it { should allow_value("703-555-1212", "800.123.4567"). + for(:number) } + + it do + should_not allow_value("703-"). + for(:number). + with_message('703- is not a valid US phone number') end end From e08b51752e97dcd686097d7adea7fc510cc64f23 Mon Sep 17 00:00:00 2001 From: Moncef Belyamani Date: Wed, 16 Apr 2014 18:58:33 -0400 Subject: [PATCH 17/17] Reload the location for Travis There is one test that depends on Elasticsearch updating its index, which sometimes doesn't happen fast enough. Maybe introducing this location reload will help. --- spec/api/locations_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/api/locations_spec.rb b/spec/api/locations_spec.rb index ac8c6e99a..6d5e070f8 100644 --- a/spec/api/locations_spec.rb +++ b/spec/api/locations_spec.rb @@ -592,6 +592,7 @@ put "api/locations/#{@loc.id}", { :name => "changeme" }, { 'HTTP_X_API_TOKEN' => @token } + @loc.reload sleep 1 # Elasticsearch needs time to update the index get "/api/search?keyword=changeme" json.first["name"].should == "changeme"