Filter array of objects in liquid - ruby

I'm new to Ruby. I want to filter array of objects in liquid. Based on docs I created this code.
Example:
require 'liquid'
Liquid::Template.error_mode = :strict
context = {'fruits' => [{'name' => 'apple', 'good' => false}, {'name' => 'orange', 'good' => true}]}
template = '
{% assign good_fruits = fruits | where: "good" %}
{{ good_fruits }}'
puts Liquid::Template.parse(template).render(context)
Expected output:
{"name"=>"orange", "good"=>true}
Real output:
{"name"=>"apple", "good"=>false}{"name"=>"orange", "good"=>true}
What am I doing wrong?

Related

How to find the most frequent value in array of hashes

I have the array of objects "orders". I want to obtain a first most frequent value in the my array, who often takes a book:
orders = [
{'book' => '1', 'reader' => 'Denis' },
{'book' => '2', 'reader' => 'Mike' },
{'book' => '3', 'reader' => 'Denis' },
{'book' => '3', 'reader' => 'Mike' },
{'book' => '5', 'reader' => '2' }
]
I tried this method, but it's good only for arrays of strings: ['string', 'string'...]:
def most_common_value(a)
a.group_by(&:itself).values.max_by(&:size).first
end
Expected result:
=> "Denis"
I'd do it like this:
orders = [
{'book' => '1', 'reader' => 'Denis' },
{'book' => '2', 'reader' => 'Mike' },
{'book' => '3', 'reader' => 'Denis' },
{'book' => '3', 'reader' => 'Mike' },
{'book' => '5', 'reader' => '2' }
]
orders.each_with_object(Hash.new(0)) { |order, hash| hash[order['reader']] += 1 }.max_by { |k, v| v }
# => ["Denis", 2]
The problem with this is, if there are multiple "max" then the result will be returned based on the order the data is found. For instance, if the order is different:
orders.push(orders.shift)
# => [{"book"=>"2", "reader"=>"Mike"},
# {"book"=>"3", "reader"=>"Denis"},
# {"book"=>"3", "reader"=>"Mike"},
# {"book"=>"5", "reader"=>"2"},
# {"book"=>"1", "reader"=>"Denis"}]
the result changes:
orders.each_with_object(Hash.new(0)) { |order, hash| hash[order['reader']] += 1 }.max_by { |k, v| v }
# => ["Mike", 2]
key = 'reader'
x = orders.inject({}) do |a,i|
a[i[key]] = 0 unless a.has_key? i[key]
a[i[key]] +=1
a
end.max_by{|k,v| v}
Which returns:
=> ["Denis", 2]
I saw this a few days ago and thought this would be easy if Array or Enumerable had a mode_by! Well I finally got around to whipping one up.
Implementing mode_by
A true mode_by would probably return a subarray of the items matching a block:
orders.mode_by{|order| order['reader']}
#=> [{'book'=>'1', 'reader'=>'Denis'}, {'book'=>'3', 'reader'=>'Denis'}]
and to get your desired result:
orders.mode_by{|order| order['reader']}.first['reader']
#=> "Denis"
So let's implement a mode_by:
class Array
def mode_by(&block)
self.group_by(&block).values.max_by(&:size)
end
end
et voila!
A custom alternative
In your case, you don't need to return array elements. Let's simplify further by implementing something that returns exactly what you want, the first result of the block that appears the most. We'll call it mode_of:
class Array
def mode_of(&block)
self.group_by(&block).max_by{|k,v| v.size}.first
end
end
Now you can simply do this:
orders.mode_of{|order| order['reader']}
#=> "Denis"
using group_by and max_by :
orders.group_by { |h| h['reader']}.to_a.max_by {|x| x[1].length}.first
Output :
=> "Denis"

I can't find a way to create a simple multidimensional array or hash in Ruby

You Ruby pros will laugh but I'm having such a hard time with this. I've searched and searched and tried a lot of different things but nothing seems right. I guess I'm just used to dealing with arrays in js and php. Here is what I want to do; consider this pseudo code:
i = 0
foreach (items as item) {
myarray[i]['title'] = item['title']
myarray[i]['desc'] = item['desc']
i++
}
Right, so then I can loop through myarray or access 'title' and 'desc' by the index (i). Simplest thing in the world. I've found a few ways to make it work in Ruby but they've all been really messy or confusing. I want to know the right way to do it, and the cleanest.
Unless you are actually updating my_array (which implies that there is probably a better way to do this), you probably want map instead:
items = [
{'title' => 't1', 'desc' => 'd1', 'other' => 'o1'},
{'title' => 't2', 'desc' => 'd2', 'other' => 'o2'},
{'title' => 't3', 'desc' => 'd3', 'other' => 'o3'},
]
my_array = items.map do |item|
{'title' => item['title'], 'desc' => item['desc'] }
end
items # => [{"title"=>"t1", "desc"=>"d1", "other"=>"o1"}, {"title"=>"t2", "desc"=>"d2", "other"=>"o2"}, {"title"=>"t3", "desc"=>"d3", "other"=>"o3"}]
my_array # => [{"title"=>"t1", "desc"=>"d1"}, {"title"=>"t2", "desc"=>"d2"}, {"title"=>"t3", "desc"=>"d3"}]
I'm not quite sure why you are trying to do this, as it seems like items is already an array with hashes inside it, and in my code below, myarray is exactly the same as items.
Try using each_with_index instead of a foreach loop:
items.each_with_index do |item, index|
myarray[index] = item
end
If you have extra attributes in each item, such as a id or something, then you would want to remove those extra attributes before you add the item to myarray.
titles = ["t1", "t2", "t3"]
descs = ["d1", "d2", "d3"]
h= Hash.new
titles.each.with_index{ |v,i| h[i] = {title: "#{v}" } }
puts h[0][:title] #=> t1
puts h #=> {0=>{:title=>"t1"}, 1=>{:title=>"t2"}...}
descs.each.with_index{ |v,i| h[i] = h[i].merge( {desc: "#{v}" } ) }
puts h[0][:desc] #=> d1
puts h #=> {0=>{:title=>"t1", :desc=>"d1"}, 1=>...

merge some complex hashes in ruby

I'd like to merge the following hashes together.
h1 = {"201201" => {:received => 2}, "201202" => {:received => 4 }}
h2 = {"201201" => {:closed => 1}, "201202" => {:closed => 1 }}
particularly, my expected result is:
h1 = {"201201" => {:received => 2, :closed => 1}, "201202" => {:received => 4, :closed => 1 }}
I have tried every way as:
h = h1.merge(h2){|key, first, second| {first , second} }
unfortunately, neither seemed to work out fine for me.
any advice would be really appreciated.
This should work for you:
h = h1.merge(h2){|key, first, second| first.merge(second)}

How to add child nodes in NodeSet using Nokogiri

I am trying to add child nodes under a root node. I tried it with the following XML but it doesn't work.
builder = Nokogiri::XML::Builder.with(#doc) do |xml|
nodes = Nokogiri::XML::NodeSet.new(#doc, [])
[].each {|nodes_one_by_one|
<< nodes_one_by_one.Book
<< nodes_one_by_one.Pen
}
end
I need to add nodes below a root node like this:
<Catalog>
<Book>abc</Book>
<Book_Author>Benjamin</Book_author>
</Catalog>
That works for me, but I to add these Nodes after a specific position in the document:
<Catalog>
<!--
<Book>abc</Book>
<Book_Author>Benjamin</Book_author>
-->
<Interface></Interface>
<Dialog></Dialog>
<Manifest></Manifest>
</Catalog>
I tried it with at_xpath('//Catlog') but it is adding it at the end of the element:
<Catalog>
<Interface></Interface>
<Dialog></Dialog>
<Manifest></Manifest>
<!--
<Book>abc</Book>
<Book_Author>Benjamin</Book_author>
-->
</Catalog>
and
book = Nokogiri::XML::Node.new('book', doc)
pen = Nokogiri::XML::Node.new('pen', doc)
.
.
Is there a way to loop using each instead of adding them one by one. I tried this way but that doesn't work:
builder = Nokogiri::XML::Builder.with(doc) do |xml|
nodes = Nokogiri::XML::Node.new(doc, [])
[].each {|child_list_element|
child_list_element.Book "value"
child_list_element.Pen "value"
child_list_element.Diary "value"
child_list_element.Pen_stand "value"
child_list_element.Pencil "value"
.
.
.
}
end
doc << nodes
The code might be wrong, but I want to do this way.
Also, can I add all the elements as a NodeSet instead of a Node.
Nested OpenStruct doesn't seem to be working. I tried:
Catalog collection of Store:
require 'ostruct'
require 'nokogiri'
collection = [
OpenStruct.new(:catalogStoreNumber => '657758',
:catalogStoreId => 'CTH6536',
:catalogStoreLocation => 'UnitedStates',
:catalogOwnerId => 'TYCT11190',
:catalogOwner => 'McGrawHill Pub.',
:catalogList => OpenStruct.new(
:catalogProductInfo => OpenStruct.new(
:productType => 'Book',
:productName => 'The Client',
:productId => 'CRSUS113246A',
:productCategory => 'Crime And Suspense',
:productSubcategory => 'Vintage Books',
:productPrice => '45.50 USD',
:productAuthor => OpenStruct.new(
:authorFirstName =>'John Grisham',
:authorMiddleName=> 'Willburt',
:authorContact => '19876648981')),
:catalogProductInfo => OpenStruct.new(
:productType => 'Pen',
:productName => 'Reynolds',
:productId => 'PRREY546647',
:productCategory => 'Misc. Stationary',
:productSubcategory => 'Stationery Items',
:productPrice => '3.00 USD',
:productManufacturer => 'Reynolds Inc.',
:productAuthor => OpenStruct.new(
:authorFirstName => 'Ryan Reynolds',
:authorMiddleName => 'William',
:authorContact => '16577589898')),
:catalogListType => 'ProductCollection',
:catalogListSource => 'Web'
),
:catalogVerificaitionLog => OpenStruct.new(
:catalogVerificationStatus => 'Verified',
:catalogVerificationDateTime => '2012-03-12T13:00:15+5:30',
:catalogVerificationId => '64774A',
:catalogVerificationRequestedBy => 'user_121')
)]
I want to access the productType of the first catalogProductInfo and I used
collection.catalogList.catalogProductInfo.productType.content
and I got this error:
undefined method `productType' for #<Array:0x3057438> (NoMethodError)
Does OpenStruct have the support for the nested OpenStruct I want to construct XML in the following format using OpenStruct and Nokogiri?
<CatalogOrder>
<CatalogStoreNumber>657758</CatalogStoreNumber>
<CatalogStoreId>CTH6536</CatalogStoreId>
<CatalogStoreLocation>UnitedStates</CatalogStoreLocation>
<CatalogOwnerId>TYCT11190</CatalogOwnerId>
<CatalogOwner>McGrawHill Pub.</CatalogOwner>
<CatalogList>
<CatalogProductInfo>
<ProductType>Book</ProductType>
<ProductName>The Client</ProductName>
<ProductId>CRSUS113246A</ProductId>
<ProductCategory>Crime And Suspense</ProductCategory>
<ProductSubCategory>Vintage Books</ProductSubCategory>
<ProductPrice>45.50 USD</ProductPrice>
<ProductAuthor>
<AuthorFirstName>John Grisham</AuthorFirstName>
<AuthorMiddleName>Willbur</AuthorMiddleName>
<AuthorContact>19876648981</AuthorContact>
</ProductAuthor>
</CatalogProductInfo>
<CatalogProductInfo>
<ProductType>Pen</ProductType>
<ProductName>Reynolds</ProductName>
<ProductId>PRREY546647</ProductId>
<ProductCategory>Misc. Stationary</ProductCategory>
<ProductSubCategory>Stationary Items</ProductSubCategory>
<ProductPrice>3.00 USD</ProductPrice>
<ProductAuthor>
<AuthorFirstName>Ryan Reynolds</AuthorFirstName>
<AuthorMiddleName>William</AuthorMiddleName>
<AuthorContact>16577589898</AuthorContact>
</ProductAuthor>
</CatalogProductInfo>
<CatalogListType>ProductCollection</CatalogListType>
<CatalogListSource>Web</CatalogListSource>
</CatalogList>
<CatalogVerificationLog>
<CatalogVerificationStatus>Verified</CatalogVerificationStatus>
<CatalogVerificationDateTime>2012-03-12T13:00:15+5:30</CatalogVerificationDateTime>
<CatalogVerificationId>64774A</CatalogVerificationId>
<CatalogVerificationRequestedBy>User_121</CatalogVerificationRequestedBy>
</CatalogVerificationLog>
</CatalogOrder>
I want to do this using Nokogiri and OpenStruct, but I am not sure whether it is possible with OpenStruct, as it lacks nesting capabilities. Is there any other way to use JSON to accomplish this without any limitations?
If I am understanding you correctly, the following should be roughly what you are looking for:
doc = Nokogiri::XML(original_xml_string)
catalog = doc.at_css('Catalog') # #at_css will just grab the first node.
# use #css if you want to loop through several.
# alternatively just use doc.root
book = Nokogiri::XML::Node.new('Book', doc)
book_author = Nokogiri::XML::Node.new('Book_Author', doc)
book.content = 'abc'
book_author.content = 'benjamin'
catalog << book
catalog << book_author
The << should append the nodes just before the end of the element.
After the updated question and simplified with #Phrogz's suggestions, this should meet your requirements:
require 'nokogiri'
xml = <<'XML'
<Catalog>
<Interface></Interface>
<Dialog></Dialog>
<Manifest></Manifest>
</Catalog>
XML
doc = Nokogiri::XML(xml)
catalog = doc.root
catalog.first_element_child.before("<Book_Author>abc</Book_Author>")
catalog.first_element_child.before("<Book>benjamin</Book>")
puts doc.to_xml
To iterate over a collection, add the nodes dynamically, and using a NodeSet, try the following:
require 'nokogiri'
require 'ostruct'
xml = <<-'XML'
<Catalog>
<Interface></Interface>
<Dialog></Dialog>
<Manifest></Manifest>
</Catalog>
XML
collection = [
OpenStruct.new(book: '1984', pen: 'George Orwell'),
OpenStruct.new(book: 'Thinking, Fash and Slow', pen: 'Daniel Kahneman')
]
doc = Nokogiri::XML(xml)
catalog = doc.root
node_set = Nokogiri::XML::NodeSet.new(doc)
collection.each do |object|
book = Nokogiri::XML::Node.new('Book', doc)
book_author = Nokogiri::XML::Node.new('Book_Author', doc)
book.content = object.book
book_author.content = object.pen
node_set << book
node_set << book_author
end
catalog.first_element_child.before(node_set)
puts doc.to_xml

Testing hash contents using RSpec

I have a test like so:
it "should not indicate backwards jumps if the checker position is not a king" do
board = Board.new
game_board = board.create_test_board
board.add_checker(game_board, :red, 3, 3)
x_coord = 3
y_coord = 3
jump_locations = {}
jump_locations["upper_left"] = true
jump_locations["upper_right"] = false
jump_locations["lower_left"] = false
jump_locations["lower_right"] = true
adjusted_jump_locations = #bs.adjust_jump_locations_if_not_king(game_board, x_coord, y_coord, jump_locations)
adjusted_jump_locations["upper_left"].should == true
adjusted_jump_locations["upper_right"].should == false
adjusted_jump_locations["lower_left"].should == false
adjusted_jump_locations["lower_right"].should == false
end
which, I know, is verbose. Is there a more concise way to state my expectations? I've looked at the docs but I can't see where to compress my expectations. Thanks.
It works for hashes too:
expect(jump_locations).to include(
"upper_left" => true,
"upper_right" => false,
"lower_left" => false,
"lower_right" => true
)
Source:
include matcher # relishapp.com
Just wanna add to #David's answer. You could nest and use matchers in your include hash. For example:
# Pass
expect({
"num" => 5,
"a" => {
"b" => [3, 4, 5]
}
}).to include({
"num" => a_value_between(3, 10),
"a" => {
"b" => be_an(Array)
}
})
A caveat: a nested include hash must test all keys or the test will fail, e.g.:
# Fail
expect({
"a" => {
"b" => 1,
"c" => 2
}
}).to include({
"a" => {
"b" => 1
}
})
Syntax has changed for RSpec 3, but include matcher is still the one:
expect(jump_locations).to include(
"upper_left" => true,
"upper_right" => false,
"lower_left" => false,
"lower_right" => true
)
See built-in-matchers#include-matcher.
Another easy way to test if the whole content is a Hash is to checkout if the content is the Hash Object itself:
it 'is to be a Hash Object' do
workbook = {name: 'A', address: 'La'}
expect(workbook.is_a?(Hash)).to be_truthy
end
For the question above we can check as follow:
expect(adjusted_jump_locations).to match(hash_including('upper_left' => true))

Resources