I am trying to add/use a variable inside the pipe to get the name of an object from a different object. Here is what I got so far:
I have an array of IDs allOutgoingNodes which I am using in the pipe.
Then I filter results using tableItemId property and then I am adding additional property externalStartingPoint and after that I would like to add name of tableItem from tableItems object to content -> html using concat.
const startingPointId = 395;
const allNodes = {
"818": {
"id": "818",
"content": {
"html": "<p>1</p>"
},
"outgoingNodes": [
"819"
],
"tableItemId": 395
},
"821": {
"id": "821",
"content": {
"html": "<p>4</p>"
},
"tableItemId": 396
}
}
const tableItems = {
"395": {
"id": "395",
"name": "SP1",
"code": "SP1"
},
"396": {
"id": "396",
"name": "SP2",
"code": "SP2"
}
}
const allOutgoingNodes = R.pipe(
R.values,
R.pluck('outgoingNodes'),
R.flatten
)(tableItemNodes);
const result = R.pipe(
R.pick(allOutgoingNodes),
R.reject(R.propEq('tableItemId', startingPointId)),
R.map(
R.compose(
R.assoc('externalStartingPoint', true),
SomeMagicFunction(node.tableItemId),
R.over(
R.lensPath(['content', 'html']),
R.concat(R.__, '<!-- Table item name should display here -->')
)
)
),
)(allNodes);
Here is a complete working example: ramda editor
Any help and suggestions on how to improve this piece of code will be appreciated.
Thank you.
Update
In the comments, OriDrori noted a problem with my first version. I didn't really understand one of the requirements. This version tries to address that issue.
const {compose, chain, prop, values, lensPath,
pipe, pick, reject, propEq, map, assoc, over} = R
const getOutgoing = compose (chain (prop('outgoingNodes')), values)
const htmlLens = lensPath (['content', 'html'])
const addName = (tableItems) => ({tableItemId}) => (html) =>
html + ` <!-- ${tableItems [tableItemId] ?.name} -->`
const convert = (tableItemNodes, tableItems, startingPointId) => pipe (
pick (getOutgoing (tableItemNodes)),
reject (propEq ('tableItemId', startingPointId)),
map (assoc ('externalStartingPoint', true)),
map (chain (over (htmlLens), addName (tableItems)))
)
const startingPointId = 395;
const tableItemNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}};
const tableItems = {395: {id: "395", name: "SP1", code: "SP1"}, 396: {id: "396", name: "SP2", code: "SP2"}}
const allNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}, 820: {id: "820", content: {html: "<p>3</p>"}, outgoingNodes: ["821"], tableItemId: 396}, 821: {id: "821", content: {html: "<p>4</p>"}, tableItemId: 396}}
console .log (
convert (tableItemNodes, tableItems, startingPointId) (allNodes)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
As well as most of the comments on the version below still applying, we should also note that chain, when applied to functions acts like this:
chain (f, g) (x) //~> f (g (x)) (x)
So chain (over (htmlLens), addName (tableItems))
ends up being something like
(node) => over (htmlLens) (addName (tableItems) (node)) (node)
which in Ramda is equivalent to
(node) => over (htmlLens, addName (tableItems) (node), node)
which we then map over the nodes coming to it. (You can also see this in the Ramda REPL.)
Original Answer
It's not trivial to weave extra arguments through a pipeline because pipelines are designed for the simple purpose of passing a single argument down the line, transforming it at every step. There are of course techniques we could figure out for that, but I would expect them not to be worth the effort. Because the only thing they gain us would be the ability to write our code point-free. And point-free should not be a goal on its own. Use it when it makes your code simpler and more readable; skip it when it doesn't.
Instead, I would break this apart with some helper functions, and then write a main function that took our arguments and passed them as necessary to helper functions inside our main pipeline. Expand this snippet to see one approach:
const {compose, chain, prop, values, lensPath, flip, concat,
pipe, pick, reject, propEq, map, assoc, over} = R
const getOutgoing = compose (chain (prop ('outgoingNodes')), values)
const htmlLens = lensPath (['content', 'html'])
const addName = flip (concat) ('Table item name goes here')
const convert = (tableItemNodes, startingPointId) => pipe (
pick (getOutgoing (tableItemNodes)),
reject (propEq ('tableItemId', startingPointId)),
map (assoc ('externalStartingPoint', true)),
map (over (htmlLens, addName))
)
const startingPointId = 395;
const tableItemNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}};
const allNodes = {818: {id: "818", content: {html: "<p>1</p>"}, outgoingNodes: ["819"], tableItemId: 395}, 819: {id: "819", content: {html: "<p>2</p>"}, outgoingNodes: ["820"], tableItemId: 395}, 820: {id: "820", content: {html: "<p>3</p>"}, outgoingNodes: ["821"], tableItemId: 396}, 821: {id: "821", content: {html: "<p>4</p>"}, tableItemId: 396}}
console .log (
convert (tableItemNodes, startingPointId) (allNodes)
)
.as-console-wrapper {max-height: 100% !important; top: 0}
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script>
(You can also see this on the Ramda REPL.)
Things to note
I find compose (chain (prop ('outgoingNodes')), values) to be slightly simpler than pipe (values, pluck('outgoingNodes'), flatten), but they work similarly.
I often separate out the lens definitions even if I'm only going to use them once to make the call site cleaner.
There is probably no good reason to use Ramda in addName. This would work just as well: const addName = (s) => s + 'Table item name goes here' and is cleaner. I just wanted to show flip as an alternative to using the placeholder.
There is an argument to be made for replacing
map (assoc ('externalStartingPoint', true)),
map (over (htmlLens, addName))
with
map (pipe (
assoc ('externalStartingPoint', true),
over (htmlLens, addName)
))
as was done in the original. The Functor composition law states that they have the same result. And that requires one fewer iterations through the data. But it adds some complexity to the code that I wouldn't bother with unless a performance test pointed to this as a problem.
Before I saw your answer I managed to do something like in the example below:
return R.pipe(
R.pick(allOutgoingNodes),
R.reject(R.propEq('tableItemId', startingPointId)),
R.map((node: Node) => {
const startingPointName = allTableItems[node.tableItemId].name;
return R.compose(
R.assoc('externalStartingPoint', true),
R.over(
R.lensPath(['content', 'html']),
R.concat(
R.__,
`<p class='test'>See node in ${startingPointName}</p>`
)
)
)(node);
}),
R.merge(newNodesObject)
)(allNodes);
What do you think?
Why is it when i display the jason for #portfolio_items I get the data only for Portfolio and not the technologies but when i do #portfolio_items.technologies I only get the records for the technologies
I want a jason object that contains both
#portfolio_items = Portfolio.includes(:technologies).find(27)
puts json: #portfolio_items
returns
Portfolio id: 27,
title: "Portfolio title: 0",
subtitle: "Angular",
body: "Lorem ipsum dolor sit amet, consectetur adipiscing...",
main_image: "https://via.placeholder.com/600x400",
thumb_image: "https://via.placeholder.com/350x200",
created_at:"2018-12-28 23:18:35",
updated_at: "2018-12-28 23:18:35"
and
puts json: #portfolio_items.technologies
returns
[#<Technology id: 7, name: "Technology 0", portfolio_id: 27, created_at:
"2018-12-28 23:18:35", updated_at: "2018-12-28 23:18:35">,
<Technology id: 8, name: "Technology 1", portfolio_id: 27, created_at
:"2018-12-28 23:18:35", updated_at: "2018-12-28 23:18:35">,
<Technology id: 9, name: "Technology 2", portfolio_id: 27, created_at:
"2018-
12-28 23:18:35", updated_at: "2018-12-28 23:18:35">]
So basically why is #portfolio_items not have the value thats in portfolio_items.technologies
It is because .technologies is a method being called on the #portfolio_items object which will return technologies related to it, but it does not mix them together which I think it what you are asking about. To display this as a nested JSON object you would want something like this.
puts #portfolio_items.as_json(include:{technologies:{}})
This might help if you are curious about it: https://apidock.com/rails/ActiveModel/Serializers/JSON/as_json
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,
I just started learning Elm and I have hit a roadblock. Looking for some help from this awesome community.
I'm looking to decode a nested json and pulling in a particular nested value into a elm record.
The json source looks like this:
{
"id": 672761,
"modified": "2018-02-12T00:53:04",
"slug": "Vivamus sagittis lacus vel augue laoreet rutrum faucibus dolor auctor.",
"type": "post",
"link": "https://awesomelinkhere.com",
"title": {
"rendered": "Sed posuere consectetur est at lobortis."
},
"content": {
"rendered": "Nulla vitae elit libero, a pharetra augue.",
},
"excerpt": {
"rendered": "Donec sed odio dui.",
}
}
and I want to pull apart the title.rendered and content.rendered into a field in my model , the model looks like so:
type alias Post =
{ id : Int
, published : String
, title : String
, link : String
, slug : String
, excerpt : String
}
my naive decoder looks like this
postDecoder : Decoder Post
postDecoder =
Decode.map6 Post
(field "id" Decode.int)
(field "modified" Decode.string)
(field "title" Decode.string)
(field "link" Decode.string)
(field "slug" Decode.string)
(field "excerpt" Decode.string)
Update
As it usually happens I found the answer as soon as I posted this. I reviewed the Json.Decode documentation and stumbled on the at function
my working decoder looks like this now
postDecoder : Decoder Post
postDecoder =
Decode.map6 Post
(field "id" Decode.int)
(field "modified" Decode.string)
(at [ "title", "rendered" ] Decode.string)
(field "link" Decode.string)
(field "slug" Decode.string)
(at [ "excerpt", "rendered" ] Decode.string)
How to I get a strongly-typed list of objects back when doing a search that uses Fields()? For example:
var searchResult = client.Search<Person>(s => s
.Fields("title", "name")
.Query(q => q.Match(...etc...)
.Highlight(...etc...)
);
It seems like the generic type parameter is useless when .Fields() is used because the Hits that are returned have a null .Source property.
(I'm hoping there's a way to do it without having to manually map the search results back to my original Person POCO.)
When you use fields parameter in your query, elasticsearch returns specified fields in fields section of response.
{
"took" : 36,
"timed_out" : false,
"_shards" : {
"total" : 2,
"successful" : 2,
"failed" : 0
},
"hits" : {
"total" : 18,
"max_score" : 1.0,
"hits" : [{
"_index" : "nest_test_data-2672",
"_type" : "elasticsearchprojects",
"_id" : "1",
"_score" : 1.0,
"fields" : {
"pingIP" : ["127.0.0.1"],
"country" : ["Faroese"],
"intValues" : [1464623485],
"locScriptField" : [0],
"stupidIntIWantAsLong" : [0],
"floatValues" : [84.96025, 95.19422],
"floatValue" : [31.93136],
"myAttachment" : [""],
"doubleValue" : [31.931359384176954],
"suggest" : [""],
"version" : [""],
"content" : ["Bacon ipsum dolor sit amet tail non prosciutto shankle turducken, officia bresaola aute filet mignon pork belly do ex tenderloin. Ut laboris quis spare ribs est prosciutto, non short ribs voluptate fugiat. Adipisicing ex ad jowl short ribs corned beef. Commodo cillum aute, sint dolore ribeye ham hock bresaola id jowl ut. Velit mollit tenderloin non, biltong officia et venison irure chuck filet mignon. Meatloaf veniam sausage prosciutto qui cow. Spare ribs non bresaola, in venison sint short loin deserunt magna laborum pork loin cillum."],
"longValue" : [-7046341211867792384],
"myBinaryField" : [""],
"name" : ["pyelasticsearch"],
"boolValue" : [false],
"id" : [1],
"startedOn" : ["1994-02-28T12:24:26.9977119+01:00"]
}
}
]
}
}
You can retrieve them from searchResult.FieldSelections or searchResult.Hits[...].Fields.
In your case Source filtering should be much more convenient.
[Test]
public void MatchAllShortcut()
{
var results = this.Client.Search<ElasticsearchProject>(s => s
.From(0)
.Size(10)
.Source(source=>source.Include(f => f.Id, f => f.Country))
.SortAscending(f => f.LOC)
.SortDescending(f => f.Country)
.MatchAll()
);
Assert.NotNull(results);
Assert.True(results.IsValid);
Assert.NotNull(results.Hits);
Assert.GreaterOrEqual(results.Hits.Count(), 10);
Assert.True(results.Hits.All(h => !string.IsNullOrEmpty(h.Source.Country)));
Assert.NotNull(results.Documents);
Assert.GreaterOrEqual(results.Documents.Count(), 10);
Assert.True(results.Documents.All(d => !string.IsNullOrEmpty(d.Country)));
}