Passing nested dict payloads from `config` to macros in DBT - dbt

Problem
I would like to pass a nested dict-of-dicts payload from a model's config to be used in a macro for processing. The payload should be a dictionary with an arbitrary number of keys, and parameters for each of these keys, e.g.:
{{ config(
payload = {
'inputs': {
'input1': {'param1': false, 'param2': 'test'},
'input2': {'param1': false, 'param2': 'test'},
},
'other_params': {
'interval': 1,
'unit': 'hour',
}
})
}}
However I am encountering an error in passing nested dictionary args in the config variable to a macro that uses the payload.
When I try to access the items in the input payload, with e.g. payload.get('inputs'), I get the following error:
Running with dbt=0.20.0
Encountered an error:
Compilation Error in model tf_sample (models/intermediate/development/tf_sample.sql)
'str object' has no attribute 'get'
> in macro test_payload (macros/test_payload.sql)
> called by model tf_sample (models/intermediate/development/tf_sample.sql
However, I believe this issue is specific to how the config variables are being handled, because if I pass the payload dict into the macro directly, the macro will parse successfully.
Question
How can I create a payload dict in this format to be passed to a macro?
Minimal Working Example of the Problem
Below I have created a MWE to demonstrate the issue:
Example macro: loop over the provided payload.get('inputs')
-- macros/test_payload.sql
{% macro test_payload(payload=config.get('payload')) -%}
{% for input, params in payload.get('inputs').items() %}
-- {{loop.index }}: {{input}}, {{params}}
{% endfor %}
{% endmacro %}
~
However this macro will not succeed to pull in the config.get('payload') in the way that I am expecting:
Example model calling test_payload() -- this will FAIL to parse
The below SQL calls the macro in the way I would like (and would expect to work):
-- model_1.sql --- This will FAIL to parse
{{
config(
payload = {
'inputs': {
'input1': {'param1': false, 'param2': 'test'},
'input2': {'param1': false, 'param2': 'test'},
},
'other_params': {
'interval': 1,
'unit': 'hour',
}
}
)
}}
-- this fails
{{ test_payload(config.get('payload')) }}
-- this also fails
{{ test_payload() }}
Example model calling test_payload() with an embedded dict; SUCCEEDS
However if I pass the payload in directly to the macro test_payload, I successfully get the expected output:
-- model_2.sql --- This will SUCCEED! But it doesn't store the payload in the config :(
{{ test_payload(payload={
'inputs': {
'input1': {'param1': false, 'param2': 'test'},
'input2': {'param1': false, 'param2': 'test'},
},
'other_params': {
'interval': 1,
'unit': 'hour',
}
}) }}
Result:
-- 1: input1, {'param1': False, 'param2': 'test'}
-- 2: input2, {'param1': False, 'param2': 'test'}

Do you need to have the payload variable inside config for any reason? If that is not the case, you can create variables outside of it and it will work, something like this:
{
set payload = {
'inputs': {
'input1': {'param1': false, 'param2': 'test'},
'input2': {'param1': false, 'param2': 'test'},
},
'other_params': {
'interval': 1,
'unit': 'hour',
}
}
}
{{ test_payload(payload) }}
Then, the macro definition won't need the default config.get('payload'):
{% macro test_payload(payload) -%}

Related

Multi-line regex only matches if begin & end are on the same line

I'm trying to add a bit of grammar for the Liquid language to my custom extension. Here is the bit of code in question:
{% if type == "record"
or type == "record2"
%}
records
{% else %}
no_records
{% endif %}
Then I have the following rule in my language repository:
"liquid_tags": {
"name": "source.qqql.liquid",
"begin": "\\{(\\%)",
"end": "(\\%)\\}",
"beginCaptures": {
"1": {"name": "source.qqql.liquid.tag"}
},
"endCaptures": {
"1": {"name": "source.qqql.liquid.tag"}
}
},
The problem is that the %} of the if statement is not captured. Only the other closing tags are captured because they're on the same line.

Get multiple values from array fetch

I am sending a get request to a api where the values are inside a array.
I want to get multiple values simultaneously.
How can I do this other than using index to get specific key values?
For example below, I want every result of results.data.message.body.artist_list.[4].artist.artist_name but don't want to have to use index [4].
methods: {
fetchMusic(e) {
if (e.key === 'Enter') {
// eslint-disable-next-line no-undef
axios.get(' ... ', {
headers: {
'Access-Control-Allow-Origin': '*',
},
})
.then((response) => response).then(this.setResults);
}
},
setResults(results) {
this.artists = results.data.message.body.artist_list.[4].artist.artist_name;
this.artistname = results.data.message.body.artist_list[0].artist.artist_name;
},
},
};
</script>
Thanks for any inputs on my code
Here is an example on how to fetch an API and display it's info by looping on the items: https://codesandbox.io/s/lucid-currying-nsoo3?file=/src/App.vue
Basically, get the results (an array so), then loop on each iteration of this array and display the wished data accordingly with something like
<div v-for="result in fetchedResults" :key="result.id">
<p>
<span>Name: {{ result.username }}</span> ~
<span>email: {{ result.email }}</span>
</p>
</div>
Btw, don't forget the key, it's important. More info here about this point.
Official documentation's examples on how to loop on an array.

Accessing nested values from YAML data in a Twig template

I am trying to access nested YML data in a Twig template. My YML data is structured like this:
card_default:
card_title: 'Card Title'
card_header: 'This is the card header on a multi-part card'
card_text: "Some quick example text to build on the card title and make up the bulk of the card's content."
card_link: 'https://github.com/phase2/particle'
card_background: primary
card_image_location: top
card_footer: "This is the card footer"
text_color: uk-dark
card_body: "This is some card body text"
card_style: default
card_image:
card_more_stuff in here....
... and then I call some of the data in a Twig template like this:
{% include '#molecules/card/_card.twig' with {
card_default: {
card_title: card_title,
card_text: card_text,
card_background: 'primary',
card_link: card_link,
card_link_text: card_link_text,
card_link_class: card_link_class,
}
} only %}
But that does not seem to work. I have a feeling the way I am trying to do this is not quite right but a search didn't give me any more insight. Essentially I want to access the values within card_default.
I can see all the data in the array if I dump with {{ dump(card_default) }}
array(14) { ["card_title"]=> string(10) "Card Title" ["card_header"]=> string(44) "This is the card header on a multi-part card" ["card_text"]=> string(94) "Some quick example text to build on the card title and make up the bulk of the card's content." ["card_link"]=> string(34) "https://github.com/phase2/particle" ["card_link_text"]=> string(9) "Read more" ["card_link_class"]=> string(27) "uk-button uk-button-default" ["card_background"]=> string(7) "primary" ["card_width"]=> int(25) ["card_image_location"]=> string(3) "top" ["card_footer"]=> string(23) "This is the card footer" ["list"]=> array(2) { ["list_flush"]=> bool(true) ["items"]=> array(3) { [0]=> array(1) { ["item_text"]=> string(15) "Cras justo odio" } [1]=> array(1) { ["item_text"]=> string(23) "Dapibus ac facilisis in" } [2]=> array(1) { ["item_text"]=> string(18) "Vestibulum at eros" } } } ["text_color"]=> string(7) "uk-dark" ["card_body"]=> string(27) "This is some card body text" ["card_style"]=> string(7) "default" }
The data is in the variable card_default, so it should be e.g. card_default.card_title
but instead of creating a whole new object you just could do this in 2 ways:
YAML
foo:
bar: 'foobar'
number: 42
main.twig
{% include "full.twig" with { 'foo' : foo } only %}
{% include "slim.twig" with foo only %}
full.twig
{{ foo.bar }}
slim.twig
{{ number }}
I figured this out, I just needed to map the nested items properly like so:
{% include '#molecules/card/_card.twig' with {
card_title: card_default.card_title,
card_text: card_default.card_text,
card_background: 'primary',
card_link: card_default.card_link,
card_link_text: card_default.card_link_text,
card_link_class: card_default.card_link_class,
} %}
In the above code, card_default is mapped in the variable portion of the array, i.e., after the colon. card_link: card_default.card_link,

Why is my POST faling in this simple VueJS form?

This is for a vueJS form. I have a nested value named "medications" I'm trying to submit for a form....I have this code in my template and data area that is related to medications. after I select the medication from the select box and enter the remaining fields and submit I get an error telling me I'm not submitting all my values...here are snips from my code...
NOTE: I'm not showing the entire form...only the part related with medication form field.
<template>
...
<div class="col-sm-2">
<b-form-select v-model="medication">
<option selected :value="null">Medication</option>
<option value="name" v-for="catMed in catMedications">{{catMed.medication.name}}</option>
</b-form-select>
</div>
...
</template>
data(){
...
duration: '',
frequency: '',
name: '',
medication: {name: '', duration: '', frequency: '', dosage: '', notes: ''},
...
(also, here is my POST function..if it helps)
postFeedings(catID, catName) {
const vm = this;
axios.post(`/api/v1/carelogs/`,{
cat: {id: catID, name: catName},
weight_unit_measure: 'G',
weight_before_food: this.weight_before_food,
food_unit_measure: 'G',
amount_of_food_taken: this.amount_of_food_taken,
food_type: this.food_type,
weight_after_food: this.weight_after_food,
stimulated: this.stimulated,
stimulation_type: this.stimulation_type,
medication: {name: vm.name, duration: vm.duration, frequency: vm.frequency, dosage: vm.dosage, notes: vm.notes},
medication_dosage_unit: 'ML',
medication_dosage_given: this.medication_dosage_given,
notes: this.notes
})
.then(response => {
console.log(response);
response.status === 201 ? this.showSwal('success-message','Carelog added') : null;
this.getFeedings(catName);
})
.catch(error => {
console.log(catID, catName);
console.log(error);
this.showSwal('auto-close', error);
})
}
ERROR: This is the error I get back ....
{"medication":{"frequency":["This field may not be blank."],"name":["This field may not be blank."]}}
ALL THE OTHER PARAMS ARE BEING SENT...but the ones for medication are not...
What am I doing wrong?
EDIT: updated axios post as Husam Ibrahim suggested
Like Husam says, In a function definition, this refers to the "owner" of the function. So when u access this in the axios function, this refers to the axios function, not to the vue instance.
Also - what i like to do is, create the object in the data of the vue instance, and use that for your post. Makes much cleaner code, and vue can access the object and properties.
Like this:
data () {
myObject: {
data1: 'abc',
data2: 'def',
data3: 123,
data4: false
}
}
and the axxios function like this:
const vm = this;
axios
.post('url.here', vm.myObject)
.then(response => {
// Handle response..
});
In vue you can use v-model="myObject.data1" to access the properties. This way you can use axxios get and assign the result to vm.myObject and vue will render the new data.
The key was in how I was getting "name" in my template. So I changed it up to this...
<div class="col-sm-2">
<b-form-select v-model="medication">
<option selected :value="null">Medication</option>
<option :value=catMed.medication.name v-for="catMed in catMedications">{{catMed.medication.name}}</option>
</b-form-select>
</div>
NOTE: see how :value=catMed.medication.name is configured? That's the key. now when I inspect my params in the browser I can see that I'm setting Medication.name to the value I intend.
And inside my axios.post I change the medication line to this...
...
medication: {name: this.medication, duration: this.duration, frequency: this.medication_dosage_given, dosage: this.dosage, notes: this.notes},
...
Now the two values are posting params ^_^

RoR model.where() using pg array type

I stumbled on the idea of limiting my tables and associations by using arrays in my models like so.
I'm working on a task assignment app where the user will, in essence, construct a sentence to perform an action. I'm using keywords to help structure the boundaries of the sentence.
Examples include (keywords in brackets):
"[I will] paint the fence" <- makes a new task, assigned to current_user
"[Ask] Huck [to] paint the fence" <- find_or_create task, assign to find_or_create user
"[Archive] painting the fence" <- soft-delete task
So here's my migration:
class CreateKeywords < ActiveRecord::Migration[5.1]
def change
create_table :keywords do |t|
t.string :name, null: false
t.text :pre, array: true, default: []
t.text :post, array: true, default: []
t.string :method, null: false, default: "read" # a CRUD indicator
end
end
end
keyword.post indicates what models could follow the keyword
keyword.pre indicates what models could preceed the keyword
My seed data looks like this:
Keyword.create([
{ name: "i will", post: ["task"] },
{ name: "ask", post: ["user"] },
{ name: "assign", post: ["user", "task"] },
{ name: "find a", post: ["user", "task"] },
{ name: "make a new", post: ["user", "task"], method: "create" },
{ name: "finalize", post: ["task"] },
{ name: "archive", post: ["user", "task"], method: "delete" },
{ name: "update", post: ["user", "task"], method: "update" },
{ name: "for", post: ["user", "task"], pre: ["user", "task"] },
{ name: "to", post: ["user", "task"], pre: ["user", "task"] },
{ name: "and repeat", pre: ["task"] },
{ name: "before", pre: ["task"] },
{ name: "after", pre: ["task"] },
{ name: "on", pre: ["task"] }
])
Now I want to do things like:
key = Keyword.third
Keyword.where(pre: key.post)
But this returns exact matches and I want to do something like:
"Return all keywords where any value of key.post can be found in Keyword.pre"
I haven't had any luck along these lines:
Keyword.where(pre.include? key.post)
I can iterate over all the Keywords and use AND:
results = []
Keyword.all.each do |k|
comb = k.pre & key.post
if comb.present?
results << k
end
end
results.map { |k| k.name }
But this feels bad.
And I'm a bit out of my depth on the SQL necessary to do this.
Any pointers?
There are two things you want to know about:
PostgreSQL's "array constructor" syntax which looks like array['a', 'b', 'c'] rather than the more common '{a,b,c}' syntax.
PostgreSQL's array operators.
Array constructor syntax is convenient because when ActiveRecord sees an array as the value for a placeholder, it expands the array as a comma delimited list which works equally well with x in (?) and x && array[?].
For the operator to use, you want:
all keywords where any value of key.post can be found in Keyword.pre
which is another way of saying that key.post and Keyword.pre overlap and the operator for that is &&. There are also subset (<#) and superset (#>) operators if you need slightly different logic.
Putting that together:
Keyword.where('pre && array[?]', key.post)
or:
Keyword.where('pre && array[:post]', post: key.post)
lacostenycoder, in the comments, is right to be concerned empty arrays. ActiveRecord expands empty arrays to a single null when filling in a placeholder so you could end up doing SQL like:
pre && array[null]
and PostgreSQL won't be able to determine the type of array[null]. You can include a cast:
Keyword.where('pre && array[:post]::text[]', post: key.post)
# --------------------------------^^^^^^^^ tell PostgreSQL that this is an array of text
But, since pre && array[null]::text[] will never be true, you could just skip the whole thing if key.post.empty?.
Empty arrays don't overlap with any other array (not even another empty array) so you don't need to worry about empty arrays beyond what ActiveRecord will do with them. Similarly for pre is null, null && any_array will never be true (it will actually evaluate to null) so there won't be any overlaps with such things.
This is actually much easier that you would expect:
Keyword.where("pre && post")
# testing vs your seed data plus a few I added returns:
#Keyword Load (1.0ms) SELECT "keywords".* FROM "keywords" WHERE (pre && post)
#[<Keyword:0x007fc03c0438b0 id: 9, name: "for", pre: ["user", "task"], post: ["user", "task"], method: "read">,
#<Keyword:0x007fc03c0435b8 id: 10, name: "to", pre: ["user", "task"], post: ["user", "task"], method: "read">,
#<Keyword:0x007fc03c043248 id: 15, name: "foo", pre: ["bar", "baz"], post: ["baz", "boo"], method: "read">,
#<Keyword:0x007fc03c042f28 id: 16, name: "rock", pre: ["anthem"], post: ["ballad", "anthem"], method: "read">,
#<Keyword:0x007fc03c042cf8 id: 17, name: "disco", pre: ["mood"], post: ["ballad", "anthem", "mood"], method: "read">]