├── log └── .keep ├── tmp └── .keep ├── vendor └── .keep ├── .prettierrc ├── .ruby-version ├── spec ├── services │ └── .keep ├── support │ ├── .rubocop.yml │ └── request_spec_helper.rb ├── .rubocop.yml ├── controllers │ └── application_controller_spec.rb ├── factories │ └── breweries.rb ├── models │ └── brewery_spec.rb ├── rails_helper.rb ├── spec_helper.rb └── requests │ ├── breweries_spec.rb │ └── v1_breweries_spec.rb ├── app ├── models │ ├── concerns │ │ └── .keep │ ├── application_record.rb │ └── brewery.rb ├── views │ └── layouts │ │ ├── mailer.text.erb │ │ └── mailer.html.erb ├── services │ ├── message.rb │ ├── update_geocodes.rb │ ├── update_state_abbreviations.rb │ └── import │ │ └── breweries.rb ├── controllers │ ├── breweries │ │ └── breweries_controller.rb │ ├── application_controller.rb │ ├── concerns │ │ ├── response.rb │ │ └── exception_handler.rb │ └── v1 │ │ └── breweries │ │ └── breweries_controller.rb └── serializers │ └── brewery_serializer.rb ├── .ruby-gemset ├── public ├── robots.txt └── favicon.ico ├── .rspec ├── obdb-logo-md.jpg ├── bin ├── bundle ├── rake ├── rails ├── spring ├── update └── setup ├── config ├── spring.rb ├── environment.rb ├── initializers │ ├── kaminari_config.rb │ ├── mime_types.rb │ ├── sentry.rb │ ├── filter_parameter_logging.rb │ ├── constants.rb │ ├── application_controller_renderer.rb │ ├── backtrace_silencers.rb │ ├── wrap_parameters.rb │ ├── cors.rb │ └── inflections.rb ├── boot.rb ├── cable.yml ├── credentials.yml.enc ├── database.yml ├── routes.rb ├── locales │ └── en.yml ├── storage.yml ├── puma.rb ├── application.rb └── environments │ ├── test.rb │ ├── development.rb │ ├── production.rb │ └── staging.rb ├── config.ru ├── Rakefile ├── db ├── seeds.rb └── schema.rb ├── lib ├── uuid_validator.rb ├── tasks │ └── breweries │ │ └── breweries_tasks.rake └── import │ └── brewers_association │ ├── mississippi.html │ ├── north_dakota.html │ ├── district_of_columbia.html │ └── hawaii.html ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── workflows │ └── main.yml └── PULL_REQUEST_TEMPLATE │ └── pull_request_template.md ├── Guardfile ├── LICENSE ├── .gitignore ├── Gemfile ├── .rubocop.yml ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── README.md └── Gemfile.lock /log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /vendor/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.2.0 2 | -------------------------------------------------------------------------------- /spec/services/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/models/concerns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.ruby-gemset: -------------------------------------------------------------------------------- 1 | openbrewery 2 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.text.erb: -------------------------------------------------------------------------------- 1 | <%= yield %> 2 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: / 3 | -------------------------------------------------------------------------------- /.rspec: -------------------------------------------------------------------------------- 1 | --require spec_helper 2 | --format documentation -------------------------------------------------------------------------------- /spec/support/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - ../../.rubocop.yml 3 | -------------------------------------------------------------------------------- /obdb-logo-md.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbrewerydb/openbrewerydb-rails-api/HEAD/obdb-logo-md.jpg -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openbrewerydb/openbrewerydb-rails-api/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /config/spring.rb: -------------------------------------------------------------------------------- 1 | %w[ 2 | .ruby-version 3 | .rbenv-vars 4 | tmp/restart.txt 5 | tmp/caching-dev.txt 6 | ].each { |path| Spring.watch(path) } 7 | -------------------------------------------------------------------------------- /spec/.rubocop.yml: -------------------------------------------------------------------------------- 1 | inherit_from: 2 | - ../.rubocop.yml 3 | 4 | Style/StringLiterals: 5 | EnforcedStyle: double_quotes 6 | Exclude: 7 | - './*_helper.rb' 8 | -------------------------------------------------------------------------------- /config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require_relative 'application' 3 | 4 | # Initialize the Rails application. 5 | Rails.application.initialize! 6 | -------------------------------------------------------------------------------- /spec/support/request_spec_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module RequestSpecHelper 4 | def json 5 | JSON.parse(response.body) 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/kaminari_config.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Kaminari.configure do |config| 4 | config.default_per_page = 50 5 | config.max_per_page = 200 6 | end 7 | -------------------------------------------------------------------------------- /app/services/message.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Message class 4 | class Message 5 | def self.account_created 6 | 'Account created successfully!' 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /spec/controllers/application_controller_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe ApplicationController, type: :controller do 6 | end 7 | -------------------------------------------------------------------------------- /app/models/application_record.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # ApplicationRecord model class 4 | class ApplicationRecord < ActiveRecord::Base 5 | self.abstract_class = true 6 | end 7 | -------------------------------------------------------------------------------- /config.ru: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # This file is used by Rack-based servers to start the application. 4 | 5 | require_relative 'config/environment' 6 | 7 | run Rails.application 8 | -------------------------------------------------------------------------------- /config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | -------------------------------------------------------------------------------- /app/controllers/breweries/breweries_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Breweries 4 | # BreweryController class 5 | class BreweriesController < V1::Breweries::BreweriesController 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # ApplicationController class 4 | class ApplicationController < ActionController::API 5 | include Response 6 | include ExceptionHandler 7 | end 8 | -------------------------------------------------------------------------------- /app/controllers/concerns/response.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Response helper module 4 | module Response 5 | def json_response(object, status = :ok) 6 | render json: object, status: status 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /config/boot.rb: -------------------------------------------------------------------------------- 1 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../Gemfile', __dir__) 2 | 3 | require 'bundler/setup' # Set up gems listed in the Gemfile. 4 | require 'bootsnap/setup' # Speed up boot time by caching expensive operations. 5 | -------------------------------------------------------------------------------- /config/initializers/sentry.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Sentry.init do |config| 4 | config.dsn = ENV['SENTRY_DSN'] 5 | config.rails.report_rescued_exceptions = true 6 | config.breadcrumbs_logger = [:active_support_logger] 7 | end 8 | -------------------------------------------------------------------------------- /config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | require_relative '../config/boot' 8 | require 'rake' 9 | Rake.application.run 10 | -------------------------------------------------------------------------------- /config/cable.yml: -------------------------------------------------------------------------------- 1 | development: 2 | adapter: async 3 | 4 | test: 5 | adapter: async 6 | 7 | production: 8 | adapter: redis 9 | url: <%= ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" } %> 10 | channel_prefix: openbrewerydb-rest-api_production 11 | -------------------------------------------------------------------------------- /config/initializers/constants.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | BREWERY_TYPES = %w[ 4 | micro 5 | nano 6 | regional 7 | brewpub 8 | large 9 | planning 10 | bar 11 | contract 12 | proprietor 13 | closed 14 | ].freeze 15 | 16 | MAX_PER_PAGE = 50 17 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Add your own tasks in files placed in lib/tasks ending in .rake, 4 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 5 | 6 | require_relative 'config/application' 7 | 8 | Rails.application.load_tasks 9 | -------------------------------------------------------------------------------- /config/initializers/application_controller_renderer.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # ActiveSupport::Reloader.to_prepare do 4 | # ApplicationController.renderer.defaults.merge!( 5 | # http_host: 'example.org', 6 | # https: false 7 | # ) 8 | # end 9 | -------------------------------------------------------------------------------- /bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | begin 3 | load File.expand_path('../spring', __FILE__) 4 | rescue LoadError => e 5 | raise unless e.message.include?('spring') 6 | end 7 | APP_PATH = File.expand_path('../config/application', __dir__) 8 | require_relative '../config/boot' 9 | require 'rails/commands' 10 | -------------------------------------------------------------------------------- /app/views/layouts/mailer.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 8 | 9 | 10 | 11 | <%= yield %> 12 | 13 | 14 | -------------------------------------------------------------------------------- /db/seeds.rb: -------------------------------------------------------------------------------- 1 | # This file should contain all the record creation needed to seed the database with its default values. 2 | # The data can then be loaded with the rails db:seed command (or created alongside the database with db:setup). 3 | # 4 | # Examples: 5 | # 6 | # movies = Movie.create([{ name: 'Star Wars' }, { name: 'Lord of the Rings' }]) 7 | # Character.create(name: 'Luke', movie: movies.first) 8 | -------------------------------------------------------------------------------- /lib/uuid_validator.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # UUID Validator for Models 4 | class UuidValidator < ActiveModel::EachValidator 5 | def validate_each(record, attribute, value) 6 | return if value =~ /\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/i 7 | 8 | msg = options[:message] || 'is not a valid UUID' 9 | record.errors.add(attribute, msg) 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /config/credentials.yml.enc: -------------------------------------------------------------------------------- 1 | /jyO6pad7hqifL8IAq3RPZvwiHWXQ/K8qwJ9T9UWPIGdrujThLHnh1JWwYd8FigeTil3nDYhnwwEUEk8hxcjTAu/9xfRT/bkc+Ki/c54vIcRSknMfaxsjp5xnH82Jn6vOpz+rIK2KZ8+YQ6Dv+siqffzkx9bjuhngwNLMv1Qi93cQienUwsIA27WCbH2ngpnkM4EHlH7QAKeQ6mUb4XAjBYMdOaHxqVWzzbNsbbDi2hZemhh+WVTpKW5oL3nV580Te8rYAMtIAYebSbZi9FD2h1g6ToKEtZS2EoAup6HGVZBuu3/um5XG1apPObEsEEti4uqzzZEphB4oXZPfxdVHY++CmDmoEiuluB4z/6AwKVCVBYD3UBocI2tHXHAMsSCJLeEI0LlzVIYKaRvBOg0SODiQk0wZZwqMoI4--cUkcFed8KWIMIDaC--f/phs+koBfulzTFW7q4vfA== -------------------------------------------------------------------------------- /config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /bin/spring: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | # This file loads spring without using Bundler, in order to be fast. 4 | # It gets overwritten when you run the `spring binstub` command. 5 | 6 | unless defined?(Spring) 7 | require 'rubygems' 8 | require 'bundler' 9 | 10 | lockfile = Bundler::LockfileParser.new(Bundler.default_lockfile.read) 11 | spring = lockfile.specs.detect { |spec| spec.name == "spring" } 12 | if spring 13 | Gem.use_paths Gem.dir, Bundler.bundle_path.to_s, *Gem.path 14 | gem 'spring', spring.version 15 | require 'spring/binstub' 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /config/initializers/cors.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Avoid CORS issues when API is called from the frontend app. 4 | # Handle Cross-Origin Resource Sharing (CORS) in order to accept cross-origin AJAX requests. 5 | 6 | # Read more: https://github.com/cyu/rack-cors 7 | 8 | # Rails.application.config.middleware.insert_before 0, Rack::Cors do 9 | # allow do 10 | # origins 'example.com' 11 | # 12 | # resource '*', 13 | # headers: :any, 14 | # methods: [:get, :post, :put, :patch, :delete, :options, :head] 15 | # end 16 | # end 17 | -------------------------------------------------------------------------------- /lib/tasks/breweries/breweries_tasks.rake: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | namespace :breweries do 4 | namespace :import do 5 | desc 'Import OpenBreweryDb data' 6 | task breweries: :environment do 7 | Import::Breweries.perform 8 | end 9 | end 10 | 11 | desc 'Convert any state abbreviations into full state (one-time task)' 12 | task update_state_abbreviations: :environment do 13 | UpdateStateAbbreviations.perform 14 | end 15 | 16 | desc 'Process Geocodes for all Breweries' 17 | task update_geocodes: :environment do 18 | UpdateGeocodes.perform 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /config/database.yml: -------------------------------------------------------------------------------- 1 | default: &default 2 | adapter: postgresql 3 | encoding: unicode 4 | pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %> 5 | host: <%= ENV.fetch("POSTGRES_HOST") { "localhost" } %> 6 | user: <%= ENV.fetch("POSTGRES_USER") { "" } %> 7 | password: <%= ENV.fetch("POSTGRES_PASSWORD") { "" } %> 8 | timeout: 5000 9 | 10 | development: 11 | <<: *default 12 | database: openbrewerydb_development 13 | 14 | test: 15 | <<: *default 16 | database: openbrewerydb_test 17 | 18 | staging: 19 | url: <%= ENV['DATABASE_URL'] %> 20 | 21 | production: 22 | url: <%= ENV['DATABASE_URL'] %> 23 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | --- 5 | 6 | # Bug Report 7 | 8 | ## Describe the bug 9 | 10 | A clear and concise description of what the bug is. 11 | 12 | ## Steps to reproduce bug 13 | 14 | 1. Go to '...' 15 | 2. Click on '....' 16 | 3. Scroll down to '....' 17 | 4. See error 18 | 19 | ### Expected behavior 20 | 21 | A clear and concise description of what you expected to happen. 22 | 23 | ## Screenshots 24 | 25 | If applicable, add screenshots to help explain your problem. 26 | 27 | ## Additional context 28 | 29 | Add any other context about the problem here. 30 | -------------------------------------------------------------------------------- /app/serializers/brewery_serializer.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # JSON Serializer for a Brewery model 4 | class BrewerySerializer < ActiveModel::Serializer 5 | attribute :id 6 | attribute :name 7 | attribute :brewery_type 8 | attribute :address_1 9 | attribute :address_2 10 | attribute :address_3 11 | attribute :city 12 | attribute :state_province 13 | attribute :postal_code 14 | attribute :country 15 | attribute :longitude 16 | attribute :latitude 17 | attribute :phone 18 | attribute :website_url 19 | 20 | # DEPRECATED - Will be removed at a TBD date 21 | attribute :state 22 | attribute :street 23 | end 24 | -------------------------------------------------------------------------------- /spec/factories/breweries.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | FactoryBot.define do 4 | factory :brewery do 5 | id { Faker::Internet.uuid } 6 | name { "#{Faker::Company.name} Brewery" } 7 | brewery_type { %w[micro planning brewpub].sample } 8 | address_1 { Faker::Address.street_address } 9 | address_2 { Faker::Address.secondary_address } 10 | address_3 { nil } 11 | city { Faker::Address.city } 12 | state_province { Faker::Address.state } 13 | phone { Faker::PhoneNumber.phone_number } 14 | postal_code { Faker::Address.postcode } 15 | country { Faker::Address.country } 16 | website_url { Faker::Internet.url } 17 | end 18 | end 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | --- 5 | 6 | # Feature request 7 | 8 | ## Overview 9 | 10 | A clear and concise description of what the feature or problem addresses. 11 | 12 | Include: 13 | 14 | - Solution you want 15 | - Alternatives you have considered 16 | 17 | ## Tasks 18 | 19 | - [ ] Task 1 20 | - [ ] Task 2 21 | - [ ] Task 3 22 | 23 | ## Screenshots & Diagrams 24 | 25 | Add any screenshots or diagrams here to help with implementation. 26 | 27 | ## Additional context 28 | 29 | Add any other context. 30 | 31 | ## Implementation notes 32 | 33 | This section will be used by the assigned developer for any additional notes. 34 | -------------------------------------------------------------------------------- /config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /bin/update: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a way to update your development environment automatically. 14 | # Add necessary update steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | puts "\n== Updating database ==" 21 | system! 'bin/rails db:migrate' 22 | 23 | puts "\n== Removing old logs and tempfiles ==" 24 | system! 'bin/rails log:clear tmp:clear' 25 | 26 | puts "\n== Restarting application server ==" 27 | system! 'bin/rails restart' 28 | end 29 | -------------------------------------------------------------------------------- /Guardfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | guard :rspec, cmd: 'bundle exec rspec' do 4 | watch('spec/spec_helper.rb') { 'spec' } 5 | watch('spec/rails_helper.rb') { 'spec' } 6 | watch('config/routes.rb') { 'spec/routing' } 7 | watch('app/controllers/application_controller.rb') { 'spec/controllers' } 8 | watch(%r{^spec/.+_spec\.rb$}) 9 | watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } 10 | watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } 11 | watch(%r{^app/(.*)(\.erb|\.haml|\.slim)$}) do |m| 12 | "spec/#{m[1]}#{m[2]}_spec.rb" 13 | end 14 | watch(%r{^app/controllers/(.+)_(controller)\.rb$}) do |m| 15 | [ 16 | "spec/routing/#{m[1]}_routing_spec.rb", 17 | "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", 18 | "spec/acceptance/#{m[1]}_spec.rb", 19 | "spec/requests/#{m[1]}_spec.rb" 20 | ] 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.routes.draw do 4 | namespace :breweries do 5 | get '/', to: 'breweries#index' 6 | get '/autocomplete', to: 'breweries#autocomplete' 7 | get '/meta', to: 'breweries#meta' 8 | get '/random', to: 'breweries#random' 9 | get '/search', to: 'breweries#search' 10 | get '/:id', to: 'breweries#show' 11 | end 12 | 13 | namespace :v1 do 14 | namespace :breweries do 15 | get '/', to: 'breweries#index' 16 | get '/autocomplete', to: 'breweries#autocomplete' 17 | get '/meta', to: 'breweries#meta' 18 | get '/random', to: 'breweries#random' 19 | get '/search', to: 'breweries#search' 20 | get '/:id', to: 'breweries#show' 21 | end 22 | end 23 | 24 | # Otherwise, redirect to www 25 | root to: redirect('https://www.openbrewerydb.org/') 26 | end 27 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | # Files in the config/locales directory are used for internationalization 2 | # and are automatically loaded by Rails. If you want to use locales other 3 | # than English, add the necessary files in this directory. 4 | # 5 | # To use the locales, use `I18n.t`: 6 | # 7 | # I18n.t 'hello' 8 | # 9 | # In views, this is aliased to just `t`: 10 | # 11 | # <%= t('hello') %> 12 | # 13 | # To use a different locale, set it with `I18n.locale`: 14 | # 15 | # I18n.locale = :es 16 | # 17 | # This would use the information in config/locales/es.yml. 18 | # 19 | # The following keys must be escaped otherwise they will not be retrieved by 20 | # the default I18n backend: 21 | # 22 | # true, false, on, off, yes, no 23 | # 24 | # Instead, surround them with single quotes. 25 | # 26 | # en: 27 | # 'true': 'foo' 28 | # 29 | # To learn more, please read the Rails Internationalization guide 30 | # available at http://guides.rubyonrails.org/i18n.html. 31 | 32 | en: 33 | hello: "Hello world" 34 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'fileutils' 3 | include FileUtils 4 | 5 | # path to your application root. 6 | APP_ROOT = File.expand_path('..', __dir__) 7 | 8 | def system!(*args) 9 | system(*args) || abort("\n== Command #{args} failed ==") 10 | end 11 | 12 | chdir APP_ROOT do 13 | # This script is a starting point to setup your application. 14 | # Add necessary setup steps to this file. 15 | 16 | puts '== Installing dependencies ==' 17 | system! 'gem install bundler --conservative' 18 | system('bundle check') || system!('bundle install') 19 | 20 | # puts "\n== Copying sample files ==" 21 | # unless File.exist?('config/database.yml') 22 | # cp 'config/database.yml.sample', 'config/database.yml' 23 | # end 24 | 25 | puts "\n== Preparing database ==" 26 | system! 'bin/rails db:setup' 27 | 28 | puts "\n== Removing old logs and tempfiles ==" 29 | system! 'bin/rails log:clear tmp:clear' 30 | 31 | puts "\n== Restarting application server ==" 32 | system! 'bin/rails restart' 33 | end 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Chris Mears 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /spec/models/brewery_spec.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "rails_helper" 4 | 5 | RSpec.describe Brewery do 6 | describe "#model_validation" do 7 | subject(:brewery) { described_class.new(id:, name:, city:, state_province:, country:) } 8 | 9 | let(:id) { SecureRandom.uuid } 10 | let(:name) { "brewery-name" } 11 | let(:city) { "brewery-city" } 12 | let(:state_province) { nil } 13 | let(:country) { nil } 14 | 15 | context "when state is present" do 16 | let(:state_province) { "brewery-state" } 17 | let(:country) { "United States" } 18 | 19 | it "validates successfully" do 20 | expect(brewery.valid?).to be true 21 | end 22 | end 23 | 24 | context "when state is nil" do 25 | let(:country) { "United States" } 26 | 27 | it "fails validation" do 28 | expect(brewery.valid?).to be false 29 | end 30 | end 31 | end 32 | 33 | describe "#address" do 34 | subject(:brewery) { create(:brewery) } 35 | 36 | it "returns a full address" do 37 | expect(brewery.address).to eq( 38 | "#{brewery.address_1}, #{brewery.city}, #{brewery.state_province}, #{brewery.country}" 39 | ) 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /config/storage.yml: -------------------------------------------------------------------------------- 1 | test: 2 | service: Disk 3 | root: <%= Rails.root.join("tmp/storage") %> 4 | 5 | local: 6 | service: Disk 7 | root: <%= Rails.root.join("storage") %> 8 | 9 | # Use rails credentials:edit to set the AWS secrets (as aws:access_key_id|secret_access_key) 10 | # amazon: 11 | # service: S3 12 | # access_key_id: <%= Rails.application.credentials.dig(:aws, :access_key_id) %> 13 | # secret_access_key: <%= Rails.application.credentials.dig(:aws, :secret_access_key) %> 14 | # region: us-east-1 15 | # bucket: your_own_bucket 16 | 17 | # Remember not to checkin your GCS keyfile to a repository 18 | # google: 19 | # service: GCS 20 | # project: your_project 21 | # credentials: <%= Rails.root.join("path/to/gcs.keyfile") %> 22 | # bucket: your_own_bucket 23 | 24 | # Use rails credentials:edit to set the Azure Storage secret (as azure_storage:storage_access_key) 25 | # microsoft: 26 | # service: AzureStorage 27 | # storage_account_name: your_account_name 28 | # storage_access_key: <%= Rails.application.credentials.dig(:azure_storage, :storage_access_key) %> 29 | # container: your_container_name 30 | 31 | # mirror: 32 | # service: Mirror 33 | # primary: local 34 | # mirrors: [ amazon, google, microsoft ] 35 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build & Test Suite 2 | 3 | on: [push] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | 9 | container: 10 | image: ruby:3.2.0 11 | 12 | services: 13 | postgres: 14 | image: postgres:14.2 15 | env: 16 | POSTGRES_USER: postgres 17 | POSTGRES_PASSWORD: postgres 18 | POSTGRES_DB: postgres 19 | ports: 20 | - 5432:5432 21 | options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 22 | 23 | steps: 24 | - uses: actions/checkout@v1 25 | - name: Build 26 | env: 27 | POSTGRES_HOST: postgres 28 | POSTGRES_USER: postgres 29 | POSTGRES_PASSWORD: postgres 30 | POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} 31 | RAILS_ENV: test 32 | run: | 33 | gem install bundler 34 | bundle install --jobs 4 --retry 3 35 | bundle exec rails db:setup 36 | - name: Run Tests 37 | env: 38 | POSTGRES_HOST: postgres 39 | POSTGRES_USER: postgres 40 | POSTGRES_PASSWORD: postgres 41 | POSTGRES_PORT: ${{ job.services.postgres.ports[5432] }} 42 | RAILS_ENV: test 43 | run: bundle exec rake 44 | -------------------------------------------------------------------------------- /app/controllers/concerns/exception_handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Exception Handler module 4 | module ExceptionHandler 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity 9 | rescue_from ActiveRecord::RecordNotFound, with: :not_found 10 | rescue_from Faraday::ConnectionFailed, with: :service_unavailable 11 | rescue_from Elasticsearch::Transport::Transport::ServerError, with: :too_many_requests 12 | end 13 | 14 | private 15 | 16 | def not_found(exception) 17 | json_response({ message: exception.message }, :not_found) 18 | end 19 | 20 | def service_unavailable 21 | json_response( 22 | { 23 | message: 'There is an issue connecting to the ElasticSearch server. ' \ 24 | 'Please try again or use other filter options. Example: ' \ 25 | 'https://api.openbrewerydb.org/breweries?by_state=OH&sort=city' 26 | }, 27 | :service_unavailable 28 | ) 29 | end 30 | 31 | def too_many_requests 32 | json_response( 33 | { 34 | message: 'Concurrent request limit exceeded. Please delay concurrent calls using debounce or throttle.' 35 | }, 36 | :too_many_requests 37 | ) 38 | end 39 | 40 | def unprocessable_entity(exception) 41 | json_response({ message: exception.message }, :unprocessable_entity) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE/pull_request_template.md: -------------------------------------------------------------------------------- 1 | # Pull request checklist 2 | 3 | Please check if your PR fulfills the following requirements: 4 | 5 | - [ ] Docs have been reviewed and added / updated if needed (for bug fixes / features) 6 | - [ ] Tests pass (`bundle exec rake`) 7 | - [ ] CHANGELOG.md updated 8 | 9 | ## Pull request type 10 | 11 | 12 | 13 | 14 | Please check the type of change your PR introduces: 15 | 16 | - [ ] Bugfix 17 | - [ ] Feature 18 | - [ ] Code style update (formatting, renaming) 19 | - [ ] Refactoring (no functional changes, no api changes) 20 | - [ ] Build related changes 21 | - [ ] Documentation content changes 22 | - [ ] Other (please describe): 23 | 24 | ## Current behavior 25 | 26 | 27 | 28 | Issue Number: N/A 29 | 30 | ## New behavior 31 | 32 | 33 | 34 | ## Does this introduce a breaking change 35 | 36 | - [ ] Yes 37 | - [ ] No 38 | 39 | 40 | 41 | ## Other information 42 | 43 | 44 | -------------------------------------------------------------------------------- /config/puma.rb: -------------------------------------------------------------------------------- 1 | # Puma can serve each request in a thread from an internal thread pool. 2 | # The `threads` method setting takes two numbers: a minimum and maximum. 3 | # Any libraries that use thread pools should be configured to match 4 | # the maximum value specified for Puma. Default is set to 5 threads for minimum 5 | # and maximum; this matches the default thread size of Active Record. 6 | # 7 | threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 } 8 | threads threads_count, threads_count 9 | 10 | # Specifies the `port` that Puma will listen on to receive requests; default is 3000. 11 | # 12 | port ENV.fetch("PORT") { 3000 } 13 | 14 | # Specifies the `environment` that Puma will run in. 15 | # 16 | environment ENV.fetch("RAILS_ENV") { "development" } 17 | 18 | # Specifies the number of `workers` to boot in clustered mode. 19 | # Workers are forked webserver processes. If using threads and workers together 20 | # the concurrency of the application would be max `threads` * `workers`. 21 | # Workers do not work on JRuby or Windows (both of which do not support 22 | # processes). 23 | # 24 | # workers ENV.fetch("WEB_CONCURRENCY") { 2 } 25 | 26 | # Use the `preload_app!` method when specifying a `workers` number. 27 | # This directive tells Puma to first boot the application and load code 28 | # before forking the application. This takes advantage of Copy On Write 29 | # process behavior so workers use less memory. 30 | # 31 | # preload_app! 32 | 33 | # Allow puma to be restarted by `rails restart` command. 34 | plugin :tmp_restart 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore bundler config. 8 | /.bundle 9 | 10 | # Ignore uploaded files in development 11 | /storage/* 12 | 13 | *.rbc 14 | capybara-*.html 15 | .rspec 16 | /log 17 | /tmp 18 | /db/*.sqlite3 19 | /db/*.sqlite3-journal 20 | /public/system 21 | /coverage/ 22 | /spec/tmp 23 | /spec/examples.txt 24 | *.orig 25 | rerun.txt 26 | pickle-email-*.html 27 | 28 | /config/initializers/secret_token.rb 29 | /config/master.key 30 | /config/secrets.yml 31 | 32 | # dotenv 33 | # TODO Comment out this rule if environment variables can be committed 34 | .env* 35 | 36 | ## Environment normalization: 37 | /.bundle 38 | /vendor/bundle 39 | 40 | # these should all be checked in to normalize the environment: 41 | # Gemfile.lock, .ruby-version, .ruby-gemset 42 | 43 | # unless supporting rvm < 1.11.0 or doing something fancy, ignore this: 44 | .rvmrc 45 | 46 | # if using bower-rails ignore default bower_components path bower.json files 47 | /vendor/assets/bower_components 48 | *.bowerrc 49 | bower.json 50 | 51 | # Ignore pow environment settings 52 | .powenv 53 | 54 | # Ignore Byebug command history file. 55 | .byebug_history 56 | 57 | # Ignore node_modules 58 | node_modules/ 59 | 60 | # Ignore vscode directories 61 | .vscode/ 62 | 63 | .DS_Store 64 | -------------------------------------------------------------------------------- /db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # This file is the source Rails uses to define your schema when running `bin/rails 6 | # db:schema:load`. When creating a new database, `bin/rails db:schema:load` tends to 7 | # be faster and is potentially less error prone than running all of your 8 | # migrations from scratch. Old migrations may fail to apply correctly if those 9 | # migrations use external dependencies or application code. 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 2023_03_21_000000) do 14 | 15 | # These are extensions that must be enabled in order to support this database 16 | enable_extension "fuzzystrmatch" 17 | enable_extension "pg_trgm" 18 | enable_extension "pgcrypto" 19 | enable_extension "plpgsql" 20 | 21 | create_table "breweries", id: :uuid, default: -> { "gen_random_uuid()" }, force: :cascade do |t| 22 | t.string "name", null: false 23 | t.string "brewery_type", null: false 24 | t.string "address_1" 25 | t.string "address_2" 26 | t.string "address_3" 27 | t.string "city", null: false 28 | t.string "state_province", null: false 29 | t.string "country", null: false 30 | t.string "postal_code", null: false 31 | t.string "website_url" 32 | t.string "phone" 33 | t.decimal "longitude" 34 | t.decimal "latitude" 35 | t.index ["id"], name: "index_breweries_on_id" 36 | end 37 | 38 | end 39 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source 'https://rubygems.org' 4 | git_source(:github) { |repo| "https://github.com/#{repo}.git" } 5 | 6 | ruby '3.2.0' 7 | 8 | # Backend 9 | gem 'bcrypt', '~> 3.1' 10 | gem 'faraday', '~> 1.4' 11 | gem 'geocoder', '~> 1.6' 12 | gem 'geokit-rails', '~> 2.3' 13 | gem 'has_scope', '~> 0.8' 14 | gem 'nokogiri', '~> 1.15' 15 | gem 'puma', '~> 5.6' 16 | gem 'rack', '>= 2.2.6' 17 | gem 'rack-cors', '~> 1.1' 18 | gem 'rails', '~> 6.1' 19 | gem 'rexml' 20 | gem 'sentry-rails' 21 | 22 | # Elastic Search 23 | gem 'searchkick', '~> 4.6' 24 | 25 | # Pagination 26 | gem 'kaminari', '~> 1.2' 27 | 28 | # Database 29 | gem 'pg', '~> 1.4' 30 | 31 | # Frontend 32 | gem 'active_model_serializers', '~> 0.10' 33 | gem 'bootsnap', '>= 1.1', require: false 34 | gem 'colorize', '~> 0.8' 35 | 36 | group :development, :test do 37 | gem 'byebug', platforms: %i[mri mingw x64_mingw] 38 | gem 'dotenv-rails', '~> 2.7' 39 | gem 'guard-rspec', '~> 4.7' 40 | gem 'rspec-rails', '~> 5.0' 41 | gem 'rubocop-rails', '~> 2.10', require: false 42 | gem 'rubocop-rspec', '~> 2.3', require: false 43 | end 44 | 45 | group :test do 46 | gem 'database_cleaner', '~> 2.0' 47 | gem 'factory_bot_rails', '~> 6.2' 48 | gem 'faker', '~> 2.17' 49 | gem 'rspec_junit_formatter', '~> 0.4' 50 | gem 'shoulda-matchers', '~> 3.1' 51 | gem 'simplecov', '~> 0.21', require: false 52 | end 53 | 54 | group :development do 55 | gem 'listen', '~> 3.2' 56 | gem 'spring', '~> 2.1' 57 | gem 'spring-watcher-listen', '~> 2.0' 58 | end 59 | 60 | group :production do 61 | gem 'cloudflare-rails', '~> 2.0' 62 | end 63 | 64 | # Windows does not include zoneinfo files, so bundle the tzinfo-data gem 65 | gem 'tzinfo-data', platforms: %i[mingw mswin x64_mingw jruby] 66 | -------------------------------------------------------------------------------- /config/application.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'boot' 4 | 5 | require 'rails' 6 | require 'active_model/railtie' 7 | require 'active_job/railtie' 8 | require 'active_record/railtie' 9 | require 'active_storage/engine' 10 | require 'action_controller/railtie' 11 | # require 'action_mailer/railtie' 12 | require 'action_view/railtie' 13 | require 'action_cable/engine' 14 | 15 | # Require the gems listed in Gemfile, including any gems 16 | # you've limited to :test, :development, or :production. 17 | Bundler.require(*Rails.groups) 18 | 19 | module OpenbrewerydbRestApi 20 | class Application < Rails::Application 21 | # Initialize configuration defaults for originally generated Rails version. 22 | config.load_defaults 5.2 23 | 24 | # Settings in config/environments/* take precedence over those specified here. 25 | # Application configuration can go into files in config/initializers 26 | # -- all .rb files in that directory are automatically loaded after loading 27 | # the framework and any gems in your application. 28 | 29 | # Only loads a smaller set of middleware suitable for API only apps. 30 | # Middleware like session, flash, cookies can be added back manually. 31 | # Skip views, helpers and assets when generating a new resource. 32 | config.api_only = true 33 | 34 | # Since this is a public API, we don't really care about IP spoofing. 35 | config.action_dispatch.ip_spoofing_check = false 36 | 37 | # Cross Origin Resource Sharing (CORS) settings 38 | # Allow GET requests from any origin on any resource 39 | config.middleware.insert_before 0, Rack::Cors do 40 | allow do 41 | origins '*' 42 | resource '*', headers: :any, methods: %i[get] 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Rails.application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure public file server for tests with Cache-Control for performance. 16 | config.public_file_server.enabled = true 17 | config.public_file_server.headers = { 18 | 'Cache-Control' => "public, max-age=#{1.hour.to_i}" 19 | } 20 | 21 | # Show full error reports and disable caching. 22 | config.consider_all_requests_local = true 23 | config.action_controller.perform_caching = false 24 | 25 | # Raise exceptions instead of rendering exception templates. 26 | config.action_dispatch.show_exceptions = false 27 | 28 | # Disable request forgery protection in test environment. 29 | config.action_controller.allow_forgery_protection = false 30 | 31 | # Store uploaded files on the local file system in a temporary directory 32 | config.active_storage.service = :test 33 | 34 | # config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 35 | # config.action_mailer.perform_caching = false 36 | 37 | # Tell Action Mailer not to deliver emails to the real world. 38 | # The :test delivery method accumulates sent emails in the 39 | # ActionMailer::Base.deliveries array. 40 | # config.action_mailer.delivery_method = :test 41 | 42 | # Print deprecation notices to the stderr. 43 | config.active_support.deprecation = :stderr 44 | 45 | # Raises error for missing translations 46 | # config.action_view.raise_on_missing_translations = true 47 | end 48 | -------------------------------------------------------------------------------- /config/environments/development.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # In the development environment your application's code is reloaded on 7 | # every request. This slows down response time but is perfect for development 8 | # since you don't have to restart the web server when you make code changes. 9 | config.cache_classes = false 10 | 11 | # Do not eager load code on boot. 12 | config.eager_load = false 13 | 14 | # Show full error reports. 15 | config.consider_all_requests_local = true 16 | 17 | # Enable/disable caching. By default caching is disabled. 18 | # Run rails dev:cache to toggle caching. 19 | if Rails.root.join('tmp', 'caching-dev.txt').exist? 20 | config.action_controller.perform_caching = true 21 | 22 | config.cache_store = :memory_store 23 | config.public_file_server.headers = { 24 | 'Cache-Control' => "public, max-age=#{2.days.to_i}" 25 | } 26 | else 27 | config.action_controller.perform_caching = false 28 | 29 | config.cache_store = :null_store 30 | end 31 | 32 | # Store uploaded files on the local file system (see config/storage.yml for options) 33 | config.active_storage.service = :local 34 | 35 | # config.action_mailer.default_url_options = { host: 'localhost', port: 3000 } 36 | # Don't care if the mailer can't send. 37 | # config.action_mailer.raise_delivery_errors = false 38 | # config.action_mailer.perform_caching = false 39 | 40 | # Print deprecation notices to the Rails logger. 41 | config.active_support.deprecation = :log 42 | 43 | # Raise an error on page load if there are pending migrations. 44 | config.active_record.migration_error = :page_load 45 | 46 | # Highlight code that triggered database queries in logs. 47 | config.active_record.verbose_query_logs = true 48 | 49 | # Raises error for missing translations 50 | # config.action_view.raise_on_missing_translations = true 51 | 52 | # Use an evented file watcher to asynchronously detect changes in source code, 53 | # routes, locales, etc. This feature depends on the listen gem. 54 | config.file_watcher = ActiveSupport::EventedFileUpdateChecker 55 | 56 | Rails.application.routes.default_url_options = { host: 'localhost' } 57 | end 58 | -------------------------------------------------------------------------------- /app/services/update_geocodes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Task to update the geocoding on breweries in DB 4 | class UpdateGeocodes 5 | def initialize 6 | @log = ActiveSupport::Logger.new('log/update_geocodes.log') 7 | @dry_run = ENV['DRY_RUN'].present? ? ENV['DRY_RUN'].casecmp('true').zero? : false 8 | @counter = { updated: 0, skipped: 0, failed: 0, total: 0 } 9 | @offset = ENV['OFFSET'].present? ? ENV['OFFSET'] : nil 10 | end 11 | 12 | def self.perform 13 | new.perform 14 | end 15 | 16 | def perform 17 | start_time = Time.now 18 | @log.info "Task started at #{start_time}" 19 | 20 | puts "\n!!!!! DRY RUN !!!!!\nNO DATA WILL BE CHANGED\n".yellow if @dry_run 21 | 22 | process_brewery_batches 23 | 24 | output_summary 25 | 26 | end_time = Time.now 27 | duration = (start_time - end_time).round(2).abs 28 | log_and_print("\nTask finished at #{end_time} and lasted #{duration}s.") 29 | @log.close 30 | end 31 | 32 | private 33 | 34 | def log_and_print(message) 35 | puts(message) 36 | @log.info(message.uncolorize) 37 | end 38 | 39 | def output_summary 40 | log_and_print("\n---------------\nTotal: #{@counter[:total]}".white) 41 | log_and_print("Updated: #{@counter[:updated]}".green) 42 | log_and_print("Skipped: #{@counter[:skipped]}".blue) 43 | log_and_print("Failed: #{@counter[:failed]}".red) 44 | log_and_print('----------------'.white) 45 | end 46 | 47 | def process_brewery_batches 48 | Brewery.find_in_batches(start: @offset) do |group| 49 | process_breweries(group) 50 | end 51 | end 52 | 53 | def process_breweries(breweries = []) 54 | breweries.each do |brewery| 55 | @counter[:total] += 1 56 | 57 | puts "#{brewery.id}. #{brewery.name} - #{brewery.address}".blue 58 | 59 | if brewery.address_1.present? && 60 | brewery.latitude.blank? && 61 | brewery.address_1.match?(/[Ste]/) 62 | 63 | unless @dry_run 64 | if brewery.save 65 | puts " #{brewery.latitude}, #{brewery.longitude}".green 66 | @counter[:updated] += 1 67 | sleep 1 68 | else 69 | puts " #{brewery.name} failed to update!".red 70 | @counter[:failed] += 1 71 | end 72 | end 73 | else 74 | @counter[:skipped] += 1 75 | puts ' Skipped!'.red 76 | end 77 | end 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /app/models/brewery.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'uuid_validator' 4 | 5 | # Brewery Model 6 | class Brewery < ApplicationRecord 7 | self.table_name = ENV.fetch('BREWERY_TABLE', 'breweries') 8 | 9 | # Because the table is usually a view (views don't specify a primary key) 10 | self.primary_key = 'id' 11 | 12 | # Elastic Search via Searchkick 13 | searchkick 14 | 15 | geocoded_by :address 16 | 17 | acts_as_mappable lat_column_name: :latitude, lng_column_name: :longitude 18 | 19 | validates :id, presence: true, uniqueness: true, uuid: true 20 | validates :name, presence: true 21 | validates :city, presence: true 22 | validates :state_province, presence: true 23 | validates :country, presence: true 24 | 25 | # Filter by `brewery_type` 26 | scope :by_type, ->(type) { where('lower(brewery_type) = ?', type.downcase) } 27 | # Filter by multiple, comma-separated `id`s 28 | scope :by_ids, ->(ids) { where(id: ids.split(',').first(50)) } 29 | # Filter by `city` 30 | scope :by_city, lambda { |city| 31 | where( 32 | 'lower(city) LIKE ?', 33 | "%#{sanitize_sql_like(city.gsub('+', ' ').downcase)}%" 34 | ) 35 | } 36 | # Filter by `country` 37 | scope :by_country, lambda { |country| 38 | where( 39 | 'lower(country) LIKE ?', 40 | "%#{sanitize_sql_like(country.gsub('+', ' ').downcase)}%" 41 | ) 42 | } 43 | # Sort by the distance from a `latitude`,`longitude` geo point 44 | scope :by_dist, lambda { |coords| 45 | by_distance(origin: coords.split(',').map(&:to_f).first(2)) 46 | } 47 | # Filter by `name` 48 | scope :by_name, lambda { |name| 49 | where( 50 | 'lower(name) LIKE ?', 51 | "%#{sanitize_sql_like(name.gsub('+', ' ').downcase)}%" 52 | ) 53 | } 54 | # Filter by `postal_code` 55 | scope :by_postal, lambda { |postal| 56 | where('postal_code LIKE ?', "%#{sanitize_sql_like(postal)}%") 57 | } 58 | # Filter by `state_province` 59 | scope :by_state, lambda { |state| 60 | where( 61 | 'lower(state_province) LIKE ?', 62 | "%#{sanitize_sql_like(state.gsub('+', ' ').downcase)}%" 63 | ) 64 | } 65 | # Filter by exluding the comma-separated `brewery_types` 66 | scope :exclude_types, lambda { |types| 67 | where('lower(brewery_type) NOT IN (?)', types.split(',')) 68 | } 69 | 70 | def address 71 | [address_1, city, state_province, country].join(', ') 72 | end 73 | 74 | # DEPRECATED - Will be removed at a TBD date 75 | def state 76 | state_province 77 | end 78 | 79 | # DEPRECATED - Will be removed at a TBD date 80 | def street 81 | address_1 82 | end 83 | 84 | # For Searchkick 85 | def search_data 86 | { 87 | id:, 88 | name:, 89 | city:, 90 | state_province:, 91 | postal_code:, 92 | country:, 93 | latitude:, 94 | longitude: 95 | } 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | 2 | require: rubocop-rspec 3 | 4 | AllCops: 5 | NewCops: enable 6 | TargetRubyVersion: 3.1 7 | Include: 8 | - "app/**/*" 9 | - "config.ru" 10 | - "Gemfile" 11 | - "Guardfile" 12 | - "lib/**/*" 13 | - "Rakefile" 14 | - 'spec**/*.rb' 15 | 16 | Exclude: 17 | - "app/views/**/*" 18 | - "bin/*" 19 | - "db/schema.rb" 20 | - "db/migrate/*" 21 | - "log/**/*" 22 | - "node_modules/**/*" 23 | - "public/**/*" 24 | - "scripts/**/*" 25 | - "vendor/**/*" 26 | - "tmp/**/*" 27 | - ".git/**/*" 28 | - "lib/import/**/*" 29 | 30 | Naming/FileName: 31 | Exclude: 32 | - "Gemfile" 33 | - "Guardfile" 34 | - "Rakefile" 35 | Layout/HashAlignment: 36 | EnforcedHashRocketStyle: table 37 | Layout/LineLength: 38 | Max: 120 39 | Layout/SpaceBeforeBrackets: 40 | Enabled: true 41 | Lint/AmbiguousAssignment: 42 | Enabled: true 43 | Lint/DeprecatedConstants: 44 | Enabled: true 45 | Lint/DuplicateBranch: 46 | Enabled: true 47 | Lint/DuplicateRegexpCharacterClassElement: 48 | Enabled: true 49 | Lint/EmptyBlock: 50 | Enabled: true 51 | Lint/EmptyClass: 52 | Enabled: true 53 | Lint/EmptyFile: 54 | Enabled: false 55 | Lint/LambdaWithoutLiteralBlock: 56 | Enabled: true 57 | Lint/NoReturnInBeginEndBlocks: 58 | Enabled: true 59 | Lint/NumberedParameterAssignment: 60 | Enabled: true 61 | Lint/OrAssignmentToConstant: 62 | Enabled: true 63 | Lint/RedundantDirGlobSort: 64 | Enabled: true 65 | Lint/SymbolConversion: 66 | Enabled: true 67 | Lint/ToEnumArguments: 68 | Enabled: true 69 | Lint/TripleQuotes: 70 | Enabled: true 71 | Lint/UnexpectedBlockArity: 72 | Enabled: true 73 | Lint/UnmodifiedReduceAccumulator: 74 | Enabled: true 75 | Metrics/AbcSize: 76 | Enabled: false 77 | Metrics/BlockLength: 78 | Enabled: false 79 | Exclude: 80 | - "Guardfile" 81 | Metrics/ClassLength: 82 | Enabled: false 83 | Metrics/CyclomaticComplexity: 84 | Enabled: false 85 | Metrics/MethodLength: 86 | Enabled: false 87 | Metrics/PerceivedComplexity: 88 | Enabled: false 89 | Naming/VariableNumber: 90 | EnforcedStyle: snake_case 91 | RSpec/HookArgument: 92 | Enabled: false 93 | Style/ArgumentsForwarding: 94 | Enabled: true 95 | Style/CollectionCompact: 96 | Enabled: true 97 | Style/Documentation: 98 | Enabled: true 99 | Style/DocumentDynamicEvalDefinition: 100 | Enabled: true 101 | Style/EndlessMethod: 102 | Enabled: true 103 | Style/HashConversion: 104 | Enabled: true 105 | Style/HashExcept: 106 | Enabled: true 107 | Style/IfWithBooleanLiteralBranches: 108 | Enabled: true 109 | Style/NegatedIfElseCondition: 110 | Enabled: true 111 | Style/NilLambda: 112 | Enabled: true 113 | Style/RedundantArgument: 114 | Enabled: true 115 | Style/StringChars: 116 | Enabled: true 117 | Style/SwapValues: 118 | Enabled: true 119 | -------------------------------------------------------------------------------- /spec/rails_helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'simplecov' 4 | SimpleCov.start 5 | 6 | # This file is copied to spec/ when you run 'rails generate rspec:install' 7 | require 'spec_helper' 8 | 9 | ENV['RAILS_ENV'] ||= 'test' 10 | require File.expand_path('../config/environment', __dir__) 11 | # Prevent database truncation if the environment is production 12 | abort('The Rails environment is running in production mode!') if Rails.env.production? 13 | require 'rspec/rails' 14 | 15 | # Add additional requires below this line. Rails is not loaded until this point! 16 | require 'database_cleaner' 17 | 18 | # Requires supporting ruby files with custom matchers and macros, etc, in 19 | # spec/support/ and its subdirectories. Files matching `spec/**/*_spec.rb` are 20 | # run as spec files by default. This means that files in spec/support that end 21 | # in _spec.rb will both be required and run as specs, causing the specs to be 22 | # run twice. It is recommended that you do not name files matching this glob to 23 | # end with _spec.rb. You can configure this pattern with the --pattern 24 | # option on the command line or in ~/.rspec, .rspec or `.rspec-local`. 25 | # 26 | # The following line is provided for convenience purposes. It has the downside 27 | # of increasing the boot-up time by auto-requiring all files in the support 28 | # directory. Alternatively, in the individual `*_spec.rb` files, manually 29 | # require only the support files necessary. 30 | # 31 | Dir[Rails.root.join('spec/support/**/*.rb')].sort.each { |f| require f } 32 | 33 | # Checks for pending migrations and applies them before tests are run. 34 | # If you are not using ActiveRecord, you can remove this line. 35 | ActiveRecord::Migration.maintain_test_schema! 36 | 37 | RSpec.configure do |config| 38 | config.include FactoryBot::Syntax::Methods 39 | config.include RequestSpecHelper 40 | 41 | config.before(:suite) do 42 | DatabaseCleaner.clean_with(:truncation) 43 | DatabaseCleaner.strategy = :transaction 44 | 45 | # Disable Elastic Search testing for now (too slow even with test clusters) 46 | Searchkick.disable_callbacks 47 | end 48 | 49 | config.around(:each) do |example| 50 | DatabaseCleaner.cleaning do 51 | example.run 52 | end 53 | end 54 | 55 | # For when we want to test search 56 | config.around(:each, search: true) do |example| 57 | Searchkick.callbacks(true) do 58 | example.run 59 | end 60 | end 61 | 62 | config.use_transactional_fixtures = true 63 | config.infer_spec_type_from_file_location! 64 | 65 | # Filter lines from Rails gems in backtraces. 66 | config.filter_rails_from_backtrace! 67 | # arbitrary gems may also be filtered via: 68 | # config.filter_gems_from_backtrace("gem name") 69 | end 70 | 71 | Shoulda::Matchers.configure do |config| 72 | config.integrate do |with| 73 | with.test_framework :rspec 74 | with.library :rails 75 | end 76 | end 77 | 78 | # Disable Geocoder API calls 79 | Geocoder.configure(lookup: :test) 80 | Geocoder::Lookup::Test.set_default_stub( 81 | [ 82 | { 83 | 'coordinates' => [40.7143528, -74.0059731], 84 | 'address' => 'New York, NY, USA', 85 | 'state' => 'New York', 86 | 'state_code' => 'NY', 87 | 'country' => 'United States', 88 | 'country_code' => 'US' 89 | } 90 | ] 91 | ) 92 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | - Using welcoming and inclusive language 12 | - Being respectful of differing viewpoints and experiences 13 | - Gracefully accepting constructive criticism 14 | - Focusing on what is best for the community 15 | - Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | - Trolling, insulting/derogatory comments, and personal or political attacks 21 | - Public or private harassment 22 | - Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | - Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at info@openbrewerydb.org. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. 4 | 5 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 6 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 7 | 8 | ## [Unreleased] 9 | ### Added 10 | 11 | - Slugs 12 | - Authentication 13 | - Authorization 14 | - Brewery update suggestions feature 15 | - Added `exclude_types` brewery filter parameter 16 | - Updated sort parameter to change from +/- to asc/desc 17 | 18 | ## [0.4.2] - 2019-12-28 19 | 20 | ### Added 21 | 22 | - Breweries filtering by postal code: 'by_postal' 23 | 24 | ## [0.4.1] - 2019-03-07 25 | 26 | ### Fixed 27 | 28 | - Fix Brewery `by_state` filter to be more exact. Note: State abbreviations no longer work 29 | 30 | ### Removed 31 | 32 | - Ahoy analytics tracking 33 | - Unnecessary files 34 | 35 | ### Changed 36 | 37 | - Turned off IP spoofing check 38 | 39 | ## [0.4.0] - 2018-11-25 40 | 41 | ### Added 42 | 43 | - Brewery tags via ActsAsTaggableOn gem 44 | - This CHANGELOG 🎉 45 | 46 | ## [0.3.5] - 2018-11-24 47 | 48 | ### Changed 49 | 50 | - Security Issue: rack and loofah 51 | - Updated all gems to the most recent version 52 | - README build icon 53 | 54 | ## [0.3.4] - 2018-10-18 55 | 56 | ### Added 57 | 58 | - Community documentation for contribution. 59 | 60 | ### Removed 61 | 62 | - CircleCI config 63 | 64 | ## [0.3.3] - 2018-09-20 65 | 66 | ### Added 67 | 68 | - TravisCI config 69 | - README 70 | 71 | ## [0.3.2] - 2018-09-08 72 | 73 | ### Added 74 | 75 | - Sentry for error and bug tracking 76 | 77 | ### Changed 78 | 79 | - Redirect homepage to [documentation page](https://www.openbrewerydb.org) 80 | 81 | ### Removed 82 | 83 | - Rollbar because it was going to be \\$\\$\$ 84 | 85 | ## [0.3.1] - 2018-08-24 86 | 87 | ### Added 88 | 89 | - Rollbar for error and bug tracking 90 | 91 | ## [0.3.0] - 2018-08-23 92 | 93 | ### Added 94 | 95 | - Columns `country`, `longitude`, `latitude` to breweries table 96 | - Attach Geocoder gem to Brewery model 97 | - Task to update all brewery geocodes 98 | 99 | ### Changed 100 | 101 | - Rename breweries table `address` column to `street` 102 | 103 | ## [0.2.0] - 2018-08-11 104 | 105 | ### Added 106 | 107 | - Search implemented via ElasticSearch and Searchkick gem 108 | - Search endpoint 109 | - Autocomplete endpoint 110 | - Add event tracking via Ahoy Matey gem 111 | 112 | ### Changed 113 | 114 | - Disable Brewery POST, PUT, and DELETE endpoints for now 115 | 116 | ## 0.1.0 - 2018-06-29 117 | 118 | ### Added 119 | 120 | - Brewery and User models 121 | - Brewery and User CRUD endpoints 122 | - Breweries list endpoint with filtering by city, state, name, type 123 | - Single brewery endpoint 124 | - Pagination and sorting on breweries list 125 | - Take to import breweries based on Brewers Association website scrape 126 | - CircleCI configuration, Rubocop config, robots.txt 127 | 128 | [unreleased]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.4.1...HEAD 129 | [0.4.1]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.4.0...v0.4.1 130 | [0.4.0]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.3.5...v0.4.0 131 | [0.3.5]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.3.4...v0.3.5 132 | [0.3.4]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.3.3...v0.3.4 133 | [0.3.3]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.3.2...v0.3.3 134 | [0.3.2]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.3.1...v0.3.2 135 | [0.3.1]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.3.0...v0.3.1 136 | [0.3.0]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.2.0...v0.3.0 137 | [0.2.0]: https://github.com/chrisjm/openbrewerydb-api-server/compare/v0.1.0...v0.2.0 138 | -------------------------------------------------------------------------------- /app/services/update_state_abbreviations.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | # Task to update the state abbreviations 4 | class UpdateStateAbbreviations 5 | def initialize 6 | @log = ActiveSupport::Logger.new('log/update_state_abbreviations.log') 7 | @dry_run = ENV['DRY_RUN'].present? ? ENV['DRY_RUN'].casecmp('true').zero? : false 8 | @counter = { updated: 0, skipped: 0, failed: 0, total: 0 } 9 | end 10 | 11 | def self.perform 12 | new.perform 13 | end 14 | 15 | def perform 16 | start_time = Time.now 17 | @log.info "Task started at #{start_time}" 18 | 19 | puts "\n!!!!! DRY RUN !!!!!\nNO DATA WILL BE CHANGED\n".yellow if @dry_run 20 | 21 | process_brewery_batches 22 | 23 | output_summary 24 | 25 | end_time = Time.now 26 | duration = (start_time - end_time).round(2).abs 27 | log_and_print("\nTask finished at #{end_time} and lasted #{duration}s.") 28 | @log.close 29 | end 30 | 31 | private 32 | 33 | def log_and_print(message) 34 | puts(message) 35 | @log.info(message.uncolorize) 36 | end 37 | 38 | def output_summary 39 | log_and_print("\n---------------\nTotal: #{@counter[:total]}".white) 40 | log_and_print("Updated: #{@counter[:updated]}".green) 41 | log_and_print("Skipped: #{@counter[:skipped]}".blue) 42 | log_and_print("Failed: #{@counter[:failed]}".red) 43 | log_and_print('----------------'.white) 44 | end 45 | 46 | def process_brewery_batches 47 | Brewery.find_in_batches.with_index do |group, batch| 48 | log_and_print("Processing group ##{batch + 1}") 49 | process_breweries(group) 50 | end 51 | end 52 | 53 | def process_breweries(breweries = []) 54 | breweries.each do |brewery| 55 | update_state_abbreviation(brewery) 56 | end 57 | end 58 | 59 | def update_state_abbreviation(brewery) 60 | @counter[:total] += 1 61 | 62 | if brewery.state.blank? 63 | @counter[:failed] += 1 64 | return 65 | end 66 | 67 | # Check if brewery state is only two characters (ie, an abbreviation) 68 | if brewery.state.match?(/^[A-Za-z]{2}$/) 69 | unless @dry_run 70 | brewery.update_attribute( 71 | :state_province, 72 | STATE_ABBR_TO_NAME[brewery.state_province.upcase] 73 | ) 74 | end 75 | @counter[:updated] += 1 76 | else 77 | @counter[:skipped] += 1 78 | end 79 | end 80 | end 81 | 82 | STATE_ABBR_TO_NAME = { 83 | 'AL' => 'Alabama', 84 | 'AK' => 'Alaska', 85 | 'AS' => 'America Samoa', 86 | 'AZ' => 'Arizona', 87 | 'AR' => 'Arkansas', 88 | 'CA' => 'California', 89 | 'CO' => 'Colorado', 90 | 'CT' => 'Connecticut', 91 | 'DE' => 'Delaware', 92 | 'DC' => 'District of Columbia', 93 | 'FM' => 'Federated States Of Micronesia', 94 | 'FL' => 'Florida', 95 | 'GA' => 'Georgia', 96 | 'GU' => 'Guam', 97 | 'HI' => 'Hawaii', 98 | 'ID' => 'Idaho', 99 | 'IL' => 'Illinois', 100 | 'IN' => 'Indiana', 101 | 'IA' => 'Iowa', 102 | 'KS' => 'Kansas', 103 | 'KY' => 'Kentucky', 104 | 'LA' => 'Louisiana', 105 | 'ME' => 'Maine', 106 | 'MH' => 'Marshall Islands', 107 | 'MD' => 'Maryland', 108 | 'MA' => 'Massachusetts', 109 | 'MI' => 'Michigan', 110 | 'MN' => 'Minnesota', 111 | 'MS' => 'Mississippi', 112 | 'MO' => 'Missouri', 113 | 'MT' => 'Montana', 114 | 'NE' => 'Nebraska', 115 | 'NV' => 'Nevada', 116 | 'NH' => 'New Hampshire', 117 | 'NJ' => 'New Jersey', 118 | 'NM' => 'New Mexico', 119 | 'NY' => 'New York', 120 | 'NC' => 'North Carolina', 121 | 'ND' => 'North Dakota', 122 | 'OH' => 'Ohio', 123 | 'OK' => 'Oklahoma', 124 | 'OR' => 'Oregon', 125 | 'PW' => 'Palau', 126 | 'PA' => 'Pennsylvania', 127 | 'PR' => 'Puerto Rico', 128 | 'RI' => 'Rhode Island', 129 | 'SC' => 'South Carolina', 130 | 'SD' => 'South Dakota', 131 | 'TN' => 'Tennessee', 132 | 'TX' => 'Texas', 133 | 'UT' => 'Utah', 134 | 'VT' => 'Vermont', 135 | 'VI' => 'Virgin Island', 136 | 'VA' => 'Virginia', 137 | 'WA' => 'Washington', 138 | 'WV' => 'West Virginia', 139 | 'WI' => 'Wisconsin', 140 | 'WY' => 'Wyoming' 141 | }.freeze 142 | -------------------------------------------------------------------------------- /config/environments/production.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 28 | # config.action_controller.asset_host = 'http://assets.example.com' 29 | 30 | # Specifies the header that your server uses for sending files. 31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 33 | 34 | # Store uploaded files on the local file system (see config/storage.yml for options) 35 | config.active_storage.service = :local 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | # config.action_cable.url = 'wss://example.com/cable' 40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | config.force_ssl = true 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | config.log_tags = [:request_id] 51 | 52 | # Use a different cache store in production. 53 | # config.cache_store = :mem_cache_store 54 | 55 | # Use a real queuing backend for Active Job (and separate queues per environment) 56 | # config.active_job.queue_adapter = :resque 57 | # config.active_job.queue_name_prefix = "openbrewerydb-rest-api_#{Rails.env}" 58 | 59 | # config.action_mailer.default_url_options = { host: 'api.openbrewerydb.org' } 60 | # config.action_mailer.perform_caching = false 61 | 62 | # Ignore bad email addresses and do not raise email delivery errors. 63 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 64 | # config.action_mailer.raise_delivery_errors = false 65 | 66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 67 | # the I18n.default_locale when a translation cannot be found). 68 | config.i18n.fallbacks = true 69 | 70 | # Send deprecation notices to registered listeners. 71 | config.active_support.deprecation = :notify 72 | 73 | # Use default logging formatter so that PID and timestamp are not suppressed. 74 | config.log_formatter = ::Logger::Formatter.new 75 | 76 | # Use a different logger for distributed setups. 77 | # require 'syslog/logger' 78 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 79 | 80 | if ENV['RAILS_LOG_TO_STDOUT'].present? 81 | logger = ActiveSupport::Logger.new(STDOUT) 82 | logger.formatter = config.log_formatter 83 | config.logger = ActiveSupport::TaggedLogging.new(logger) 84 | end 85 | 86 | # Do not dump schema after migrations. 87 | config.active_record.dump_schema_after_migration = false 88 | 89 | Rails.application.routes.default_url_options = { 90 | host: 'api.openbrewerydb.org' 91 | } 92 | end 93 | -------------------------------------------------------------------------------- /config/environments/staging.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | Rails.application.configure do 4 | # Settings specified here will take precedence over those in config/application.rb. 5 | 6 | # Code is not reloaded between requests. 7 | config.cache_classes = true 8 | 9 | # Eager load code on boot. This eager loads most of Rails and 10 | # your application in memory, allowing both threaded web servers 11 | # and those relying on copy on write to perform better. 12 | # Rake tasks automatically ignore this option for performance. 13 | config.eager_load = true 14 | 15 | # Full error reports are disabled and caching is turned on. 16 | config.consider_all_requests_local = false 17 | config.action_controller.perform_caching = true 18 | 19 | # Ensures that a master key has been made available in either ENV["RAILS_MASTER_KEY"] 20 | # or in config/master.key. This key is used to decrypt credentials (and other encrypted files). 21 | config.require_master_key = true 22 | 23 | # Disable serving static files from the `/public` folder by default since 24 | # Apache or NGINX already handles this. 25 | config.public_file_server.enabled = ENV['RAILS_SERVE_STATIC_FILES'].present? 26 | 27 | # Enable serving of images, stylesheets, and JavaScripts from an asset server. 28 | # config.action_controller.asset_host = 'http://assets.example.com' 29 | 30 | # Specifies the header that your server uses for sending files. 31 | # config.action_dispatch.x_sendfile_header = 'X-Sendfile' # for Apache 32 | # config.action_dispatch.x_sendfile_header = 'X-Accel-Redirect' # for NGINX 33 | 34 | # Store uploaded files on the local file system (see config/storage.yml for options) 35 | config.active_storage.service = :local 36 | 37 | # Mount Action Cable outside main process or domain 38 | # config.action_cable.mount_path = nil 39 | # config.action_cable.url = 'wss://example.com/cable' 40 | # config.action_cable.allowed_request_origins = [ 'http://example.com', /http:\/\/example.*/ ] 41 | 42 | # Force all access to the app over SSL, use Strict-Transport-Security, and use secure cookies. 43 | config.force_ssl = false 44 | 45 | # Use the lowest log level to ensure availability of diagnostic information 46 | # when problems arise. 47 | config.log_level = :debug 48 | 49 | # Prepend all log lines with the following tags. 50 | config.log_tags = [:request_id] 51 | 52 | # Use a different cache store in production. 53 | # config.cache_store = :mem_cache_store 54 | 55 | # Use a real queuing backend for Active Job (and separate queues per environment) 56 | # config.active_job.queue_adapter = :resque 57 | # config.active_job.queue_name_prefix = "openbrewerydb-rest-api_#{Rails.env}" 58 | 59 | # config.action_mailer.default_url_options = { host: 'staging-api.openbrewerydb.org' } 60 | # config.action_mailer.perform_caching = false 61 | 62 | # Ignore bad email addresses and do not raise email delivery errors. 63 | # Set this to true and configure the email server for immediate delivery to raise delivery errors. 64 | # config.action_mailer.raise_delivery_errors = false 65 | 66 | # Enable locale fallbacks for I18n (makes lookups for any locale fall back to 67 | # the I18n.default_locale when a translation cannot be found). 68 | config.i18n.fallbacks = true 69 | 70 | # Send deprecation notices to registered listeners. 71 | config.active_support.deprecation = :notify 72 | 73 | # Use default logging formatter so that PID and timestamp are not suppressed. 74 | config.log_formatter = ::Logger::Formatter.new 75 | 76 | # Use a different logger for distributed setups. 77 | # require 'syslog/logger' 78 | # config.logger = ActiveSupport::TaggedLogging.new(Syslog::Logger.new 'app-name') 79 | 80 | if ENV['RAILS_LOG_TO_STDOUT'].present? 81 | logger = ActiveSupport::Logger.new(STDOUT) 82 | logger.formatter = config.log_formatter 83 | config.logger = ActiveSupport::TaggedLogging.new(logger) 84 | end 85 | 86 | # Do not dump schema after migrations. 87 | config.active_record.dump_schema_after_migration = false 88 | 89 | Rails.application.routes.default_url_options = { 90 | host: 'staging-api.openbrewerydb.org' 91 | } 92 | end 93 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Open Brewery DB 2 | 3 | ## Welcome to the Open Brewery DB community 4 | 5 | Thank you for contributing to Open Brewery DB! It's fellow beer lovers like you that make Open Brewery DB such a great resource. 🍻 6 | 7 | ## Why the guidelines 8 | 9 | Following these guidelines helps to communicate that you respect the time of the developers managing and developing this open source project. In return, they should reciprocate that respect in addressing your issue, assessing changes, and helping you finalize your pull requests. 10 | 11 | ## What to contribute 12 | 13 | Open Brewery DB is a fully transparent, open source project and we love to receive any contributions from our community — you! There are many ways to contribute, from suggesting brewery updates, writing tutorials or blog posts, improving the documentation, submitting bug reports and feature requests or writing code which can be incorporated into Open Brewery DB itself. 14 | 15 | ## Responsibilities 16 | 17 | * Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community feedback. 18 | * Keep feature versions as small as possible, preferably one new feature per version. 19 | * Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. See the [Code of Conduct](CODE_OF_CONDUCT.md). 20 | 21 | Here are a couple of helpful tutorials: 22 | 23 | * [Make a Pull Request](http://makeapullrequest.com/) 24 | * [First Timers Only](http://www.firsttimersonly.com/) 25 | * [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). 26 | 27 | ## Pull Request 28 | 29 | After you've created a branch on your fork with your changes, it's time to [make a pull request][pr-link]! 30 | 31 | Once you’ve submitted a pull request, the collaborators can review your proposed changes and decide whether or not to incorporate (pull in) your changes. 32 | 33 | ### Pull Request Pro Tips 34 | 35 | * [Fork][fork-link] the repository and [clone][clone-link] it locally. 36 | Connect your local repository to the original `upstream` repository by adding it as a [remote][remote-link]. 37 | Pull in changes from `upstream` often so that you stay up to date and so when you submit your pull request, 38 | merge conflicts will be less likely. See more detailed instructions [here][syncing-link]. 39 | * Create a [branch][branch-link] for your edits. 40 | * Contribute in the style of the project. This makes it easier for the collaborators to merge 41 | and for others to understand and maintain in the future. 42 | * Please try to squash all commits together before opening a pull request, but it's not currently required. If your pull request requires changes upon review, and you're already in the habit, please squash all additional commits as well. [This wiki page][squash-link] outlines the squash process. 43 | 44 | ### Open Pull Requests 45 | 46 | Once you’ve opened a pull request, a discussion will start around your proposed changes. 47 | 48 | Other contributors and users may chime in, but ultimately the decision is made by the collaborators. 49 | 50 | During the discussion, you may be asked to make some changes to your pull request. 51 | 52 | If so, add more commits to your branch and push them – they will automatically go into the existing pull request! 53 | 54 | Opening a pull request will trigger a Github Action build to check the validity of all links in the project. After the build completes, **please ensure that the build has passed**. If the build did not pass, please view the Github Action log and correct any errors that were found in your contribution. 55 | 56 | Thanks for being a part of this project, and we look forward to hearing from you soon! 🍻 57 | 58 | [branch-link]:We found 16 Breweries in Mississippi


We found 17 Breweries in North Dakota


We found 20 Breweries in District of Columbia






We found 24 Breweries in Hawaii




