Fix const auto-loader problems, change belongs_to behavior to allow `nil` by default (sorry).

pull/1540/head
Rick Carlino 2019-10-24 12:44:11 -05:00
parent 3921a69cf2
commit 504ea18bcc
21 changed files with 176 additions and 173 deletions

View File

@ -136,7 +136,7 @@ module Api
strategy = Auth::DetermineAuthStrategy.run!(context)
case strategy
when :jwt
sign_in(Auth::FromJWT.run!(context).require_consent!)
sign_in(Auth::FromJwt.run!(context).require_consent!)
when :already_connected
# Probably provided a cookie.
# 9 times out of 10, it's a unit test.

View File

@ -1,4 +1,4 @@
require_relative "../../lib/hstore_filter"
# require_relative "../../lib/mutations/hstore_filter"
module Api
class PointsController < Api::AbstractController

View File

@ -272,7 +272,7 @@ module Api
end
def current_device
@current_device ||= Auth::FromJWT.run!(jwt: password_param).device
@current_device ||= Auth::FromJwt.run!(jwt: password_param).device
rescue Mutations::ValidationException => e
raise JWT::VerificationError, "RMQ Provided bad token"
end

View File

@ -9,8 +9,8 @@
# * You need to create "traces" of where you are in a sequence (using numbers)
# MORE INFO: https://github.com/FarmBot-Labs/Celery-Slicer
module CeleryScript
# Supporting class for CSHeap (below this class)
# PROBLEM: CSHeap uses numbers to address sibling/parent nodes.
# Supporting class for CsHeap (below this class)
# PROBLEM: CsHeap uses numbers to address sibling/parent nodes.
# PROBLEM: Numbers are very easy to mix up. Is it an array index? A SQL
# primary key? A primitive value? It's not always easy to say.
# SOLUTION: Create a `HeapAddress` value type to remove ambiguity.
@ -60,21 +60,22 @@ module CeleryScript
end
end
class CSHeap
class BadAddress < Exception; end;
class CsHeap
class BadAddress < Exception; end
BAD_ADDR = "Bad node address: "
# Nodes that point to other nodes rather than primitive data types (eg:
# `locals` and friends) will be prepended with a LINK.
LINK = "__"
LINK = "__"
# Points to the originator (parent) of an `arg` or `body` node.
PARENT = (LINK + "parent").to_sym
PARENT = (LINK + "parent").to_sym
# Points to the first element in the `body``
BODY = (LINK + "body").to_sym
BODY = (LINK + "body").to_sym
# Points to the next node in the body chain. Pointing to NOTHING indicates
# the end of the body linked list.
NEXT = (LINK + "next").to_sym
NEXT = (LINK + "next").to_sym
# Unique key name. See `celery_script_settings_bag.rb`
KIND = :__KIND__
KIND = :__KIND__
COMMENT = :__COMMENT__
# Keys that primary nodes must have
@ -82,17 +83,16 @@ module CeleryScript
# Index 0 of the heap represents a null pointer of sorts.
# If a field points to this address, it is considered empty.
NULL = HeapAddress[0]
NULL = HeapAddress[0]
# What you will find at index 0 of the heap:
NOTHING = {
KIND => "nothing",
KIND => "nothing",
PARENT => NULL,
BODY => NULL,
NEXT => NULL
BODY => NULL,
NEXT => NULL,
}
# A dictionary of nodes in the CeleryScript tree, as stored in the heap.
# Nodes will have:
# * A `KIND` field - What kind of node is it?
@ -113,7 +113,7 @@ module CeleryScript
# Set "here" to "null". Prepopulates "here" with an empty entry.
def initialize
@here = NULL
@here = NULL
@entries = { @here => NOTHING }
end

View File

@ -1,4 +1,4 @@
require_relative "./csheap"
require_relative "./cs_heap"
# Service object that:
# 1. Pulls out all PrimaryNodes and EdgeNodes for a sequence node (AST Flat IR form)
@ -8,7 +8,7 @@ require_relative "./csheap"
# DEFAULT.
module CeleryScript
class FetchCelery < Mutations::Command
private # = = = = = = =
private # = = = = = = =
# This class is too CPU intensive to make multiple SQL requests.
# To speed up querying, we create an in-memory index for frequently
# looked up attributes such as :id, :kind, :parent_id, :primary_node_id
@ -60,7 +60,7 @@ module CeleryScript
# that node's children (or an empty array, since body is always optional).
def get_body_elements(origin)
next_node = find_by_id_in_memory(origin.body_id)
results = []
results = []
until next_node.kind == "nothing"
results.push(next_node)
next_node = find_by_id_in_memory(next_node[:next_id])
@ -71,7 +71,7 @@ module CeleryScript
# Top level function call for converting a single EdgeNode into a JSON
# document. Returns Ruby hash that conforms to CeleryScript semantics.
def recurse_into_node(node)
out = { kind: node.kind, args: recurse_into_args(node) }
out = { kind: node.kind, args: recurse_into_args(node) }
body = get_body_elements(node)
if body.empty?
# Legacy sequences *must* have body on sequence. Others are fine.
@ -87,16 +87,17 @@ module CeleryScript
# Eg: color, id, etc.
def misc_fields
return {
id: sequence.id,
created_at: sequence.created_at,
updated_at: sequence.updated_at,
args: Sequence::DEFAULT_ARGS,
color: sequence.color,
name: sequence.name
}
id: sequence.id,
created_at: sequence.created_at,
updated_at: sequence.updated_at,
args: Sequence::DEFAULT_ARGS,
color: sequence.color,
name: sequence.name,
}
end
public # = = = = = = =
public # = = = = = = =
NO_SEQUENCE = "You must have a root node `sequence` at a minimum."
required do

View File

@ -1,7 +1,7 @@
require_relative "./csheap"
require_relative "./cs_heap"
# ABOUT THIS CLASS:
# CSHeap creates an in memory representation of a Flat IR tree using array
# CsHeap creates an in memory representation of a Flat IR tree using array
# indexes (HeapAddress instances, really). This class takes a flat IR tree
# from memory and converts `HeapAddress`es to SQL primary/foreign keys.
module CeleryScript
@ -10,14 +10,14 @@ module CeleryScript
# The following constants are abbreviations of the full name, since the
# full name is quite long and they are referenced frequently in the code.
# Just remember that "B" is "BODY", "K" is "KIND", etc...
B = CeleryScript::CSHeap::BODY
C = CeleryScript::CSHeap::COMMENT
K = CeleryScript::CSHeap::KIND
L = CeleryScript::CSHeap::LINK
N = CeleryScript::CSHeap::NEXT
P = CeleryScript::CSHeap::PARENT
NULL = CeleryScript::CSHeap::NULL
I = :instance
B = CeleryScript::CsHeap::BODY
C = CeleryScript::CsHeap::COMMENT
K = CeleryScript::CsHeap::KIND
L = CeleryScript::CsHeap::LINK
N = CeleryScript::CsHeap::NEXT
P = CeleryScript::CsHeap::PARENT
NULL = CeleryScript::CsHeap::NULL
I = :instance
required do
model :sequence, class: Sequence
@ -28,7 +28,7 @@ module CeleryScript
def validate
# IF YOU REMOVE THIS BAD STUFF WILL HAPPEN:
# version is never user definable!
sequence_hash[:args] = \
sequence_hash[:args] =
Sequence::DEFAULT_ARGS.merge(sequence_hash[:args] || {})
# See comment above ^ TODO: Investigate removal now that EdgeNodes exist.
end
@ -37,67 +37,67 @@ module CeleryScript
Sequence.transaction do
flat_ir
.each do |node|
# Step 1- instantiate records.
node[I] = PrimaryNode.create!(kind: node[K],
sequence: sequence,
comment: node[C] || nil)
end
# Step 1- instantiate records.
node[I] = PrimaryNode.create!(kind: node[K],
sequence: sequence,
comment: node[C] || nil)
end
.each_with_index do |node, index|
# Step 2- Assign SQL ids (not to be confused with array index IDs or
# instances of HeapAddress), also sets parent_arg_name
model = node[I]
model.parent_arg_name = parent_arg_name_for(node, index)
model.body_id = fetch_sql_id_for(B, node)
model.parent_id = fetch_sql_id_for(P, node)
model.next_id = fetch_sql_id_for(N, node)
node
end
# Step 2- Assign SQL ids (not to be confused with array index IDs or
# instances of HeapAddress), also sets parent_arg_name
model = node[I]
model.parent_arg_name = parent_arg_name_for(node, index)
model.body_id = fetch_sql_id_for(B, node)
model.parent_id = fetch_sql_id_for(P, node)
model.next_id = fetch_sql_id_for(N, node)
node
end
.map do |node|
# Step 3- Set edge nodes
pairs = node
.to_a
.select do |x|
key = x.first.to_s
(x.first != I) && !key.starts_with?(L)
end
.map do |(key, value)|
EdgeNode.create!(kind: key,
value: value,
sequence_id: sequence.id,
primary_node_id: node[:instance].id)
end
node[:instance]
# Step 3- Set edge nodes
pairs = node
.to_a
.select do |x|
key = x.first.to_s
(x.first != I) && !key.starts_with?(L)
end
.map do |(key, value)|
EdgeNode.create!(kind: key,
value: value,
sequence_id: sequence.id,
primary_node_id: node[:instance].id)
end
node[:instance]
end
.tap { |x| sequence.update(migrated_nodes: true) unless sequence.migrated_nodes }
.map { |x|
x.save! if x.changed?
x
}
x.save! if x.changed?
x
}
end
end
private
private
# Index every primary node in memory by its `HeapAddress`.
# We need this info in order to fill out the `parent_arg_name` of a node.
def every_primary_link
@every_primary_link ||= flat_ir
.map do |x|
x
.except(B,C,I,K,L,N,P)
.invert
.to_a
.select{|(k,v)| k.is_a?(HeapAddress)}
end
x
.except(B, C, I, K, L, N, P)
.invert
.to_a
.select { |(k, v)| k.is_a?(HeapAddress) }
end
.map(&:to_h)
.reduce({}, :merge)
end
def parent_arg_name_for(node, index)
resides_in_args = (node[N] == NULL) && (node[P] != NULL)
link_symbol = every_primary_link[HeapAddress[index]]
resides_in_args = (node[N] == NULL) && (node[P] != NULL)
link_symbol = every_primary_link[HeapAddress[index]]
needs_p_arg_name = (resides_in_args && link_symbol)
parent_arg_name = (needs_p_arg_name ? link_symbol.to_s.gsub(L, "") : nil)
parent_arg_name = (needs_p_arg_name ? link_symbol.to_s.gsub(L, "") : nil)
return parent_arg_name
end
@ -107,7 +107,7 @@ private
end
def sequence_hash
@sequence_hash ||= \
@sequence_hash ||=
HashWithIndifferentAccess.new(kind: "sequence", args: args, body: body)
end

View File

@ -2,18 +2,18 @@ module CeleryScript
# THIS IS A MORE MINIMAL VERSION OF CeleryScript::TreeClimber.
# It is a NON-VALIDATING tree climber.
# Don't use this on unverified data structures.
class JSONClimber
class JsonClimber
HASH_ONLY = "Expected a Hash."
NOT_NODE = "Expected hash with at least a `kind` and `args` prop."
NOT_NODE = "Expected hash with at least a `kind` and `args` prop."
def self.climb(thing, &callable)
raise HASH_ONLY unless thing.is_a?(Hash)
raise NOT_NODE unless is_node?(thing)
raise NOT_NODE unless is_node?(thing)
go(thing, callable)
thing
end
private
private
def self.is_node?(maybe)
maybe.is_a?(Hash) &&

View File

@ -1,4 +1,4 @@
require_relative "./csheap.rb"
require_relative "./cs_heap.rb"
# ORIGINAL IMPLEMENTATION HERE: https://github.com/FarmBot-Labs/Celery-Slicer
# Take a nested ("canonical") representation of a CeleryScript sequence and
# transforms it to a flat/homogenous intermediate representation which is better
@ -11,12 +11,12 @@ module CeleryScript
raise "Not a hash" unless node.is_a?(Hash)
@nesting_level = 0
@root_node = node
heap = CSHeap.new()
allocate(heap, node, CSHeap::NULL)
heap = CsHeap.new()
allocate(heap, node, CsHeap::NULL)
@heap_values = heap.values
@heap_values.map do |x|
x[CSHeap::BODY] ||= CSHeap::NULL
x[CSHeap::NEXT] ||= CSHeap::NULL
x[CsHeap::BODY] ||= CsHeap::NULL
x[CsHeap::NEXT] ||= CsHeap::NULL
end
heap.dump()
end
@ -31,8 +31,8 @@ module CeleryScript
def allocate(h, s, parentAddr)
addr = h.allot(s[:kind])
h.put(addr, CSHeap::PARENT, parentAddr)
h.put(addr, CSHeap::COMMENT, s[:comment]) if s[:comment]
h.put(addr, CsHeap::PARENT, parentAddr)
h.put(addr, CsHeap::COMMENT, s[:comment]) if s[:comment]
iterate_over_body(h, s, addr)
iterate_over_args(h, s, addr)
addr
@ -44,7 +44,7 @@ module CeleryScript
.map do |key|
v = s[:args][key]
if (is_celery_script(v))
k = CSHeap::LINK + key.to_s
k = CsHeap::LINK + key.to_s
h.put(parentAddr, k, allocate(h, v, parentAddr))
else
h.put(parentAddr, key, v)
@ -64,12 +64,12 @@ module CeleryScript
is_head = index == 0
# BE CAREFUL EDITING THIS LINE, YOU MIGHT BREAK `BODY` NODES:
heap # See note above!
.put(previous_address, CSHeap::BODY, previous_address + 1) if is_head
.put(previous_address, CsHeap::BODY, previous_address + 1) if is_head
my_heap_address = allocate(heap, canonical_list[index], previous_address)
prev_next_key = is_head ? CSHeap::NULL : my_heap_address
heap.put(previous_address, CSHeap::NEXT, prev_next_key)
prev_next_key = is_head ? CsHeap::NULL : my_heap_address
heap.put(previous_address, CsHeap::NEXT, prev_next_key)
recurse_into_body(heap, canonical_list, my_heap_address, index + 1)
end

View File

@ -1,7 +1,7 @@
module Auth
# The API supports a number of authentication strategies (Cookies, Bot token,
# JWT). This service helps determine which auth strategy to use.
class FromJWT < Mutations::Command
class FromJwt < Mutations::Command
required { string :jwt }
def execute

View File

@ -1,4 +1,4 @@
require_relative "../../lib/hstore_filter"
# require_relative "../../lib/hstore_filter"
# WHY??? ^
module Points
class Create < Mutations::Command

View File

@ -1,47 +1,47 @@
require_relative "../../lib/hstore_filter"
# require_relative "../../lib/hstore_filter"
# WHY??? ^
module Points
class Query < Mutations::Command
H_QUERY = "meta -> :key = :value"
class Query < Mutations::Command
H_QUERY = "meta -> :key = :value"
required do
duck :points, method: [:where]
end
required do
duck :points, method: [:where]
end
optional do
float :radius
float :x
float :y
float :z
hstore :meta
string :name
string :pointer_type, in: Point::POINTER_KINDS
string :plant_stage, in: CeleryScriptSettingsBag::PLANT_STAGES
string :openfarm_slug
end
optional do
float :radius
float :x
float :y
float :z
hstore :meta
string :name
string :pointer_type, in: Point::POINTER_KINDS
string :plant_stage, in: CeleryScriptSettingsBag::PLANT_STAGES
string :openfarm_slug
end
def execute
search_results
end
def execute
search_results
end
def search_results
@search_results ||= conditions.reduce(points) do |collection, query|
collection.where(query)
end
end
def conditions
@conditions ||= regular_conditions + meta_conditions
end
def meta_conditions
@meta_conditions ||= (meta || {}).map do |(k,v)|
[H_QUERY, {key: k, value: v}]
end
end
def regular_conditions
@regular_conditions ||= [inputs.except(:points, :meta)]
def search_results
@search_results ||= conditions.reduce(points) do |collection, query|
collection.where(query)
end
end
def conditions
@conditions ||= regular_conditions + meta_conditions
end
def meta_conditions
@meta_conditions ||= (meta || {}).map do |(k, v)|
[H_QUERY, { key: k, value: v }]
end
end
def regular_conditions
@regular_conditions ||= [inputs.except(:points, :meta)]
end
end
end

View File

@ -1,24 +1,24 @@
require_relative "../../lib/hstore_filter"
# require_relative "../../lib/hstore_filter"
module Points
class Update < Mutations::Command
required do
model :device, class: Device
model :point, class: Point
model :point, class: Point
end
optional do
integer :tool_id, nils: true, empty_is_nil: true
float :x
float :y
float :z
float :radius
string :name
string :openfarm_slug
float :x
float :y
float :z
float :radius
string :name
string :openfarm_slug
integer :pullout_direction, in: ToolSlot::PULLOUT_DIRECTIONS
string :plant_stage, in: CeleryScriptSettingsBag::PLANT_STAGES
time :planted_at
hstore :meta
string :plant_stage, in: CeleryScriptSettingsBag::PLANT_STAGES
time :planted_at
hstore :meta
boolean :gantry_mounted
end
@ -30,7 +30,7 @@ module Points
Point.transaction { point.update!(inputs.except(:point)) && point }
end
private
private
def new_tool_id?
raw_inputs.key?("tool_id")

View File

@ -1,6 +1,6 @@
require_relative "../app/models/transport.rb"
require File.expand_path("../boot", __FILE__)
require_relative "../app/lib/celery_script/csheap"
require_relative "../app/lib/celery_script/cs_heap"
require "rails/all"
# Require the gems listed in Gemfile, including any gems
@ -14,11 +14,13 @@ module FarmBot
REDIS_URL = ENV.fetch(REDIS_ENV_KEY, "redis://redis:6379/0")
gcs_enabled =
%w[ GOOGLE_CLOUD_KEYFILE_JSON GCS_PROJECT GCS_BUCKET ].all? { |s| ENV.key? s }
config.load_defaults 6.0
config.active_storage.service = gcs_enabled ?
:google : :local
config.cache_store = :redis_cache_store, { url: REDIS_URL }
config.middleware.use Rack::Attack
config.active_record.schema_format = :sql
config.active_record.belongs_to_required_by_default = false
config.active_job.queue_adapter = :delayed_job
config.action_dispatch.perform_deep_munge = false
I18n.enforce_available_locales = false

View File

@ -4,15 +4,15 @@ fixture = {
body: [
{
kind: "child",
args: { grandchild: { kind: "grandchild", args: {} } }
}
]
args: { grandchild: { kind: "grandchild", args: {} } },
},
],
}
describe "JSONClimber" do
it 'Climbs JSON' do
describe "JsonClimber" do
it "Climbs JSON" do
results = []
CeleryScript::JSONClimber.climb(fixture) do |hmm|
CeleryScript::JsonClimber.climb(fixture) do |hmm|
results.push(hmm[:kind])
end
expect(results).to eq(["parent", "child", "grandchild"])

View File

@ -43,7 +43,7 @@ describe SessionToken do
exp: 1,
iss: "//lycos.com:9867",
fbos_version: Gem::Version.new("9.9.9"))
result = Auth::FromJWT.run(jwt: token.encoded)
result = Auth::FromJwt.run(jwt: token.encoded)
expect(result.success?).to be(false)
expect(result.errors.values.first.message).to eq(Auth::ReloadToken::BAD_SUB)
end

View File

@ -1,6 +1,6 @@
require "spec_helper"
describe Auth::FromJWT do
describe Auth::FromJwt do
let(:user) { FactoryBot.create(:user) }
def fake_credentials(email, password)

View File

@ -1,6 +1,6 @@
require "spec_helper"
describe Auth::FromJWT do
describe Auth::FromJwt do
FAKE_VERS = Gem::Version.new("99.9.9")
let(:user) { FactoryBot.create(:user) }
let(:token) do
@ -19,7 +19,7 @@ describe Auth::FromJWT do
}
it "gets user from jwt" do
result = Auth::FromJWT.run!(jwt: token)
result = Auth::FromJwt.run!(jwt: token)
expect(result).to eq(user)
end
end

View File

@ -1,9 +1,9 @@
require "spec_helper"
describe CeleryScript::CSHeap do
describe CeleryScript::CsHeap do
it "raises if address is bad" do
expect do
CeleryScript::CSHeap.new.put(CeleryScript::HeapAddress[99], "no", "no")
end.to raise_error(CeleryScript::CSHeap::BadAddress)
CeleryScript::CsHeap.new.put(CeleryScript::HeapAddress[99], "no", "no")
end.to raise_error(CeleryScript::CsHeap::BadAddress)
end
end

View File

@ -11,10 +11,10 @@ describe CeleryScript::FirstPass do
CeleryScript::FlatIrHelpers.fake_first_pass.primary_nodes
end
kind = CeleryScript::CSHeap::KIND
parent = CeleryScript::CSHeap::PARENT
next_ = CeleryScript::CSHeap::NEXT
body = CeleryScript::CSHeap::BODY
kind = CeleryScript::CsHeap::KIND
parent = CeleryScript::CsHeap::PARENT
next_ = CeleryScript::CsHeap::NEXT
body = CeleryScript::CsHeap::BODY
EXPECTATIONS = { # Came from the JS implementation which is known good.
0 => { kind => "nothing", parent => 0, next_ => 0 },

View File

@ -2,11 +2,11 @@ require "spec_helper"
require_relative "./flat_ir_helpers"
describe CeleryScript::Slicer do
kind = CeleryScript::CSHeap::KIND
parent = CeleryScript::CSHeap::PARENT
next_ = CeleryScript::CSHeap::NEXT
body = CeleryScript::CSHeap::BODY
comment = CeleryScript::CSHeap::COMMENT
kind = CeleryScript::CsHeap::KIND
parent = CeleryScript::CsHeap::PARENT
next_ = CeleryScript::CsHeap::NEXT
body = CeleryScript::CsHeap::BODY
comment = CeleryScript::CsHeap::COMMENT
n = "nothing"