From fd5aea8013390d7508eb3379fe8128a0335c69c8 Mon Sep 17 00:00:00 2001 From: Rick Carlino Date: Tue, 27 Feb 2018 13:15:02 -0600 Subject: [PATCH] Add DeviceConfigs. NEXT: Tests, FE Integration --- .../api/device_configs_controller.rb | 35 +++++++++++++++++++ app/models/device.rb | 15 ++++---- app/models/device_config.rb | 15 ++++++++ app/mutations/device_configs/create.rb | 24 +++++++++++++ app/mutations/device_configs/update.rb | 16 +++++++++ config/routes.rb | 30 ++++++++-------- .../20180227172811_create_device_configs.rb | 11 ++++++ db/schema.rb | 12 ++++++- spec/factories/device_configs.rb | 7 ++++ spec/models/device_configs_spec.rb | 13 +++++++ spec/models/tool_spec.rb | 1 + 11 files changed, 156 insertions(+), 23 deletions(-) create mode 100644 app/controllers/api/device_configs_controller.rb create mode 100644 app/models/device_config.rb create mode 100644 app/mutations/device_configs/create.rb create mode 100644 app/mutations/device_configs/update.rb create mode 100644 db/migrate/20180227172811_create_device_configs.rb create mode 100644 spec/factories/device_configs.rb create mode 100644 spec/models/device_configs_spec.rb diff --git a/app/controllers/api/device_configs_controller.rb b/app/controllers/api/device_configs_controller.rb new file mode 100644 index 000000000..b050aadea --- /dev/null +++ b/app/controllers/api/device_configs_controller.rb @@ -0,0 +1,35 @@ +# Api::DeviceConfigController is the RESTful endpoint for managing key/value +# configuration pairs. +module Api + class DeviceConfigsController < Api::AbstractController + def create + mutate DeviceConfigs::Create.run(params.as_json, device: current_device) + end + + def index + render json: configs + end + + def show + render json: config + end + + def update + mutate DeviceConfigs::Update.run(params.as_json, config: config) + end + + def destroy + render json: config.destroy! && "" + end + + private + + def config + configs.find(params[:id]) + end + + def configs + current_device.device_configs + end + end +end diff --git a/app/models/device.rb b/app/models/device.rb index a87d89118..4285efbbd 100644 --- a/app/models/device.rb +++ b/app/models/device.rb @@ -1,9 +1,11 @@ # Farmbot Device models all data related to an actual FarmBot in the real world. class Device < ApplicationRecord - DEFAULT_MAX_LOGS = 100 - DEFAULT_MAX_IMAGES = 100 - TIMEZONES = TZInfo::Timezone.all_identifiers - BAD_TZ = "%{value} is not a valid timezone" + DEFAULT_MAX_LOGS = 100 + DEFAULT_MAX_IMAGES = 100 + DEFAULT_MAX_CONFIGS = 100 + + TIMEZONES = TZInfo::Timezone.all_identifiers + BAD_TZ = "%{value} is not a valid timezone" has_many :users has_many :farm_events, dependent: :destroy @@ -17,10 +19,9 @@ class Device < ApplicationRecord has_many :images, dependent: :destroy has_many :webcam_feeds, dependent: :destroy has_many :sensor_readings, dependent: :destroy - validates :timezone, inclusion: { in: TIMEZONES, - message: BAD_TZ, - allow_nil: true } validates_presence_of :name + validates :timezone, + inclusion: { in: TIMEZONES, message: BAD_TZ, allow_nil: true } [FbosConfig, FirmwareConfig, WebAppConfig].map do |klass| name = klass.table_name.singularize.to_sym has_one name, dependent: :destroy diff --git a/app/models/device_config.rb b/app/models/device_config.rb new file mode 100644 index 000000000..dea23b3c3 --- /dev/null +++ b/app/models/device_config.rb @@ -0,0 +1,15 @@ +class DeviceConfig < ApplicationRecord + belongs_to :device + serialize :value + validate :primitives_only + + PRIMITIVES_ONLY = "`value` must be a string, number or boolean" + + def primitives_only + errors.add(:value, PRIMITIVES_ONLY) unless is_primitve + end + + def is_primitve + [String, Integer, Float, TrueClass, FalseClass].include?(value.class) + end +end diff --git a/app/mutations/device_configs/create.rb b/app/mutations/device_configs/create.rb new file mode 100644 index 000000000..2f56bafda --- /dev/null +++ b/app/mutations/device_configs/create.rb @@ -0,0 +1,24 @@ +module DeviceConfigs + class Create < Mutations::Command + LIMIT = Device::DEFAULT_MAX_CONFIGS + + required do + model :device, class: Device + string :key + duck :value, methods: [:to_json] + end + + def validate + # Ensure you're not over the limit + if device.device_configs.length > LIMIT + add_error :configs, + :configs, + "You are over the limit of #{LIMIT} configs." + end + end + + def execute + DeviceConfig.create!(inputs) + end + end +end diff --git a/app/mutations/device_configs/update.rb b/app/mutations/device_configs/update.rb new file mode 100644 index 000000000..68fe0bf2b --- /dev/null +++ b/app/mutations/device_configs/update.rb @@ -0,0 +1,16 @@ +module DeviceConfigs + class Update < Mutations::Command + required { + model :config, class: DeviceConfig + } + + optional do + string :key + duck :value, methods: [:to_json] + end + + def execute + config.update_attributes!(inputs.except(:config)) && config + end + end +end diff --git a/config/routes.rb b/config/routes.rb index 14ebca63d..d8d608451 100755 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,21 +2,21 @@ FarmBot::Application.routes.draw do namespace :api, defaults: {format: :json}, constraints: { format: "json" } do # Standard API Resources: { - corpuses: [:index, :show], - farm_events: [:create, :destroy, :index, :update], - farmware_installations: - [:create, :destroy, :index], - images: [:create, :destroy, :index, :show], - logs: [:create, :destroy, :index], - password_resets: [:create, :update], - peripherals: [:create, :destroy, :index, :update], - sensors: [:create, :destroy, :index, :update], - regimens: [:create, :destroy, :index, :update], - sensor_readings: [:create, :destroy, :index, :show], - sequences: [:create, :destroy, :index, :show, :update], - tools: [:create, :destroy, :index, :show, :update], - webcam_feeds: [:create, :destroy, :index, :show, :update], - }.to_a.map{|(name, only)| resources name, only: only} + corpuses: [:index, :show], + farm_events: [:create, :destroy, :index, :update], + farmware_installations: [:create, :destroy, :index], + images: [:create, :destroy, :index, :show], + logs: [:create, :destroy, :index], + password_resets: [:create, :update], + peripherals: [:create, :destroy, :index, :update], + sensors: [:create, :destroy, :index, :update], + regimens: [:create, :destroy, :index, :update], + sensor_readings: [:create, :destroy, :index, :show], + sequences: [:create, :destroy, :index, :show, :update], + tools: [:create, :destroy, :index, :show, :update], + webcam_feeds: [:create, :destroy, :index, :show, :update], + device_configs: [:create, :destroy, :index, :show], + }.to_a.map { |(name, only)| resources name, only: only } # Singular API Resources: { diff --git a/db/migrate/20180227172811_create_device_configs.rb b/db/migrate/20180227172811_create_device_configs.rb new file mode 100644 index 000000000..c1c89e681 --- /dev/null +++ b/db/migrate/20180227172811_create_device_configs.rb @@ -0,0 +1,11 @@ +class CreateDeviceConfigs < ActiveRecord::Migration[5.1] + def change + create_table :device_configs do |t| + t.references :device, foreign_key: true + t.string :key, limit: 100 + t.string :value, limit: 300 + + t.timestamps + end + end +end diff --git a/db/schema.rb b/db/schema.rb index 87739d497..463c6401d 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: 20180226164100) do +ActiveRecord::Schema.define(version: 20180227172811) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -31,6 +31,15 @@ ActiveRecord::Schema.define(version: 20180226164100) do t.index ["priority", "run_at"], name: "delayed_jobs_priority" end + create_table "device_configs", force: :cascade do |t| + t.bigint "device_id" + t.string "key", limit: 100 + t.string "value", limit: 300 + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["device_id"], name: "index_device_configs_on_device_id" + end + create_table "devices", id: :serial, force: :cascade do |t| t.string "name" t.integer "max_log_count", default: 100 @@ -434,6 +443,7 @@ ActiveRecord::Schema.define(version: 20180226164100) do t.index ["device_id"], name: "index_webcam_feeds_on_device_id" end + add_foreign_key "device_configs", "devices" add_foreign_key "edge_nodes", "sequences" add_foreign_key "farmware_installations", "devices" add_foreign_key "log_dispatches", "devices" diff --git a/spec/factories/device_configs.rb b/spec/factories/device_configs.rb new file mode 100644 index 000000000..ecbfa15be --- /dev/null +++ b/spec/factories/device_configs.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :device_config do + device + key { Faker::Pokemon.move } + value { Faker::Pokemon.move } + end +end diff --git a/spec/models/device_configs_spec.rb b/spec/models/device_configs_spec.rb new file mode 100644 index 000000000..689e26062 --- /dev/null +++ b/spec/models/device_configs_spec.rb @@ -0,0 +1,13 @@ +require "spec_helper" + +describe DeviceConfig do + it 'has a length limit' do + p = { + device: FactoryBot.create(:device), + key: Faker::Pokemon.name, + value: "===" * 300 + } + expect { DeviceConfig.create!(p) } + .to raise_error(ActiveRecord::ValueTooLong) + end +end diff --git a/spec/models/tool_spec.rb b/spec/models/tool_spec.rb index 7cd27654d..ed1cdf4b9 100644 --- a/spec/models/tool_spec.rb +++ b/spec/models/tool_spec.rb @@ -1,3 +1,4 @@ +require "spec_helper" describe Tool do describe 'names' do