diff --git a/app/controllers/admin/waterbodies_controller.rb b/app/controllers/admin/waterbodies_controller.rb new file mode 100644 index 0000000..4451456 --- /dev/null +++ b/app/controllers/admin/waterbodies_controller.rb @@ -0,0 +1,46 @@ +module Admin + class WaterbodiesController < Admin::ApplicationController + # Overwrite any of the RESTful controller actions to implement custom behavior + # For example, you may want to send an email after a foo is updated. + # + # def update + # super + # send_foo_updated_email(requested_resource) + # end + + # Override this method to specify custom lookup behavior. + # This will be used to set the resource for the `show`, `edit`, and `update` + # actions. + # + # def find_resource(param) + # Foo.find_by!(slug: param) + # end + + # The result of this lookup will be available as `requested_resource` + + # Override this if you have certain roles that require a subset + # this will be used to set the records shown on the `index` action. + # + # def scoped_resource + # if current_user.super_admin? + # resource_class + # else + # resource_class.with_less_stuff + # end + # end + + # Override `resource_params` if you want to transform the submitted + # data before it's persisted. For example, the following would turn all + # empty values into nil values. It uses other APIs such as `resource_class` + # and `dashboard`: + # + # def resource_params + # params.require(resource_class.model_name.param_key). + # permit(dashboard.permitted_attributes). + # transform_values { |value| value == "" ? nil : value } + # end + + # See https://administrate-prototype.herokuapp.com/customizing_controller_actions + # for more information + end +end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 9b18f12..661fd7a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -9,7 +9,7 @@ def authorize! return if controller_name == 'home' && action_name == 'index' # TODO: This is cheap, reorganize properly - if %w[measurements sensors sponsors].include?(controller_name) && + if %w[waterbodies measurements sensors sponsors].include?(controller_name) && %w[index show aggregated daily_temperatures hourly_temperatures sponsor].include?(action_name) return require_public_access! end diff --git a/app/controllers/waterbodies_controller.rb b/app/controllers/waterbodies_controller.rb new file mode 100644 index 0000000..a117bd8 --- /dev/null +++ b/app/controllers/waterbodies_controller.rb @@ -0,0 +1,9 @@ +class WaterbodiesController < ApplicationController + def index + @waterbodies = Waterbody.all.order(created_at: :asc) + end + + def show + @waterbody = Waterbody.find(params[:id]) + end +end diff --git a/app/dashboards/sensor_dashboard.rb b/app/dashboards/sensor_dashboard.rb index 421d226..9eda981 100644 --- a/app/dashboards/sensor_dashboard.rb +++ b/app/dashboards/sensor_dashboard.rb @@ -10,6 +10,7 @@ class SensorDashboard < Administrate::BaseDashboard ATTRIBUTE_TYPES = { measurements: Field::HasMany, sponsor: Field::BelongsTo, + waterbody: Field::BelongsTo, id: Field::Number, device_name: Field::String, caption: Field::String, @@ -27,6 +28,7 @@ class SensorDashboard < Administrate::BaseDashboard COLLECTION_ATTRIBUTES = %i[ measurements sponsor + waterbody id device_name ].freeze @@ -36,6 +38,7 @@ class SensorDashboard < Administrate::BaseDashboard SHOW_PAGE_ATTRIBUTES = %i[ measurements sponsor + waterbody id device_name caption @@ -50,6 +53,7 @@ class SensorDashboard < Administrate::BaseDashboard # on the model's form (`new` and `edit`) pages. FORM_ATTRIBUTES = %i[ sponsor + waterbody device_name caption latitude diff --git a/app/dashboards/waterbody_dashboard.rb b/app/dashboards/waterbody_dashboard.rb new file mode 100644 index 0000000..493cc7b --- /dev/null +++ b/app/dashboards/waterbody_dashboard.rb @@ -0,0 +1,76 @@ +require 'administrate/base_dashboard' + +class WaterbodyDashboard < Administrate::BaseDashboard + # ATTRIBUTE_TYPES + # a hash that describes the type of each of the model's fields. + # + # Each different type represents an Administrate::Field object, + # which determines how the attribute is displayed + # on pages throughout the dashboard. + ATTRIBUTE_TYPES = { + id: Field::Number, + description: Field::String, + latitude: Field::Number.with_options(decimals: 2), + longitude: Field::Number.with_options(decimals: 2), + name: Field::String, + sensors: Field::HasMany, + created_at: Field::DateTime, + updated_at: Field::DateTime + }.freeze + + # COLLECTION_ATTRIBUTES + # an array of attributes that will be displayed on the model's index page. + # + # By default, it's limited to four items to reduce clutter on index pages. + # Feel free to add, remove, or rearrange items. + COLLECTION_ATTRIBUTES = %i[ + id + name + description + sensors + latitude + longitude + ].freeze + + # SHOW_PAGE_ATTRIBUTES + # an array of attributes that will be displayed on the model's show page. + SHOW_PAGE_ATTRIBUTES = %i[ + id + name + description + sensors + latitude + longitude + created_at + updated_at + ].freeze + + # FORM_ATTRIBUTES + # an array of attributes that will be displayed + # on the model's form (`new` and `edit`) pages. + FORM_ATTRIBUTES = %i[ + name + description + latitude + longitude + ].freeze + + # COLLECTION_FILTERS + # a hash that defines filters that can be used while searching via the search + # field of the dashboard. + # + # For example to add an option to search for open resources by typing "open:" + # in the search field: + # + # COLLECTION_FILTERS = { + # open: ->(resources) { resources.where(open: true) } + # }.freeze + COLLECTION_FILTERS = {}.freeze + + # Overwrite this method to customize how waterbodies are displayed + # across all pages of the admin dashboard. + # + # def display_resource(waterbody) + # "Waterbody ##{waterbody.id}" + # end +end diff --git a/app/models/sensor.rb b/app/models/sensor.rb index 807996a..979d93f 100644 --- a/app/models/sensor.rb +++ b/app/models/sensor.rb @@ -1,6 +1,7 @@ class Sensor < ApplicationRecord has_many :measurements, dependent: :destroy belongs_to :sponsor, optional: true + belongs_to :waterbody, optional: true validates :device_name, presence: true validates :caption, presence: true diff --git a/app/models/waterbody.rb b/app/models/waterbody.rb new file mode 100644 index 0000000..fd3dd99 --- /dev/null +++ b/app/models/waterbody.rb @@ -0,0 +1,5 @@ +class Waterbody < ApplicationRecord + has_many :sensors, dependent: :nullify + + validates :name, :latitude, :longitude, presence: true +end diff --git a/app/views/waterbodies/index.json.jbuilder b/app/views/waterbodies/index.json.jbuilder new file mode 100644 index 0000000..94213ff --- /dev/null +++ b/app/views/waterbodies/index.json.jbuilder @@ -0,0 +1,4 @@ +json.array!(@waterbodies) do |waterbody| + json.extract! waterbody, :id, :name, :description, :latitude, :longitude, :sensor_ids + json.url waterbody_url(waterbody, format: :json) +end diff --git a/app/views/waterbodies/show.json.jbuilder b/app/views/waterbodies/show.json.jbuilder new file mode 100644 index 0000000..067a68b --- /dev/null +++ b/app/views/waterbodies/show.json.jbuilder @@ -0,0 +1,2 @@ +json.extract! @waterbody, :id, :name, :description, :latitude, :longitude, + :sensor_ids, :created_at, :updated_at diff --git a/config/routes.rb b/config/routes.rb index 8048c9c..fe56204 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -6,6 +6,7 @@ resources :measurements resources :sensors resources :sponsors + resources :waterbodies root to: "sensors#index" end @@ -16,6 +17,7 @@ end resources :sensors resources :sponsors + resources :waterbodies, only: [:index, :show] namespace :mobile_app do resources :sensors, only: [:index, :show] do diff --git a/db/migrate/20230205145109_create_waterbodies.rb b/db/migrate/20230205145109_create_waterbodies.rb new file mode 100644 index 0000000..59dda49 --- /dev/null +++ b/db/migrate/20230205145109_create_waterbodies.rb @@ -0,0 +1,12 @@ +class CreateWaterbodies < ActiveRecord::Migration[6.1] + def change + create_table :waterbodies do |t| + t.string :name, null: false + t.string :description, null: false + t.float :latitude, null: false + t.float :longitude, null: false + + t.timestamps + end + end +end diff --git a/db/migrate/20230205152214_add_waterbody_ref_to_sensors.rb b/db/migrate/20230205152214_add_waterbody_ref_to_sensors.rb new file mode 100644 index 0000000..ae1b5cf --- /dev/null +++ b/db/migrate/20230205152214_add_waterbody_ref_to_sensors.rb @@ -0,0 +1,5 @@ +class AddWaterbodyRefToSensors < ActiveRecord::Migration[6.1] + def change + add_reference :sensors, :waterbody, foreign_key: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 71c133d..1a590ff 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2023_01_20_212413) do +ActiveRecord::Schema.define(version: 2023_02_05_152214) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -42,7 +42,9 @@ t.datetime "updated_at", null: false t.float "latitude", null: false t.float "longitude", null: false + t.bigint "waterbody_id" t.index ["sponsor_id"], name: "index_sensors_on_sponsor_id" + t.index ["waterbody_id"], name: "index_sensors_on_waterbody_id" end create_table "sponsors", id: :serial, force: :cascade do |t| @@ -54,5 +56,15 @@ t.string "logo_source", comment: "filename (in ./public/images/), relative path or URL" end + create_table "waterbodies", force: :cascade do |t| + t.string "name", null: false + t.string "description", null: false + t.float "latitude", null: false + t.float "longitude", null: false + t.datetime "created_at", precision: 6, null: false + t.datetime "updated_at", precision: 6, null: false + end + add_foreign_key "measurements", "sensors" + add_foreign_key "sensors", "waterbodies" end diff --git a/test/controllers/admin/waterbodies_controller_test.rb b/test/controllers/admin/waterbodies_controller_test.rb new file mode 100644 index 0000000..60ebef7 --- /dev/null +++ b/test/controllers/admin/waterbodies_controller_test.rb @@ -0,0 +1,17 @@ +require 'test_helper' + +class WaterbodiesControllerTest < ActionDispatch::IntegrationTest + setup do + @headers = admin_auth_header + end + + test 'should get unauthorized without authentication' do + get admin_waterbodies_url + assert_response :unauthorized + end + + test 'should get success with authentication' do + get admin_waterbodies_url, headers: @headers + assert_response :success + end +end diff --git a/test/controllers/waterbodies_controller_test.rb b/test/controllers/waterbodies_controller_test.rb new file mode 100644 index 0000000..2804017 --- /dev/null +++ b/test/controllers/waterbodies_controller_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' + +class WaterbodiesControllerTest < ActionDispatch::IntegrationTest + setup do + @waterbody = create(:waterbody) + end + + test 'should get unauthorized without auth header' do + get waterbodies_url + assert_response :unauthorized + end + + test 'should get index' do + get waterbodies_url, env: public_auth_header + assert_response :success + response_object = JSON.parse(response.body) + assert response_object.length == 1 + assert response_object[0]['name'] == @waterbody.name + end + + test 'get an individual waterbody' do + get waterbody_url(@waterbody), env: public_auth_header + assert_response :success + response_object = JSON.parse(response.body) + assert response_object['name'] == @waterbody.name + end +end diff --git a/test/factories/waterbodies.rb b/test/factories/waterbodies.rb new file mode 100644 index 0000000..a6edafc --- /dev/null +++ b/test/factories/waterbodies.rb @@ -0,0 +1,8 @@ +FactoryBot.define do + factory :waterbody do + name { 'Zürichsee' } + description { 'Our favorite lake' } + latitude { 47.222578 } + longitude { 8.814744 } + end +end diff --git a/test/models/waterbody_test.rb b/test/models/waterbody_test.rb new file mode 100644 index 0000000..ece5af9 --- /dev/null +++ b/test/models/waterbody_test.rb @@ -0,0 +1,11 @@ +require 'test_helper' + +class WaterbodyTest < ActiveSupport::TestCase + test 'should nullify sensor waterbody references on destroy' do + sensor = create(:sensor) + waterbody = create(:waterbody, sensors: [sensor]) + assert_equal sensor.waterbody, waterbody + waterbody.destroy + assert_nil sensor.reload.waterbody + end +end