How do I avoid calling ".clone()" on same String in match multiple times? - regex

Background:
I have some code (Rust) that finds (Regex) matches and assigns the found values to fields in a struct named Article (where all fields are of type String):
pub struct Article {
// user facing data
title: String,
category: String,
subcategory: String,
genre: String,
published: String,
estimated_read_time: String,
description: String,
tags: String,
keywords: String,
image: String,
artwork_credit: String,
// meta data
metas: String,
// location
path: String,
slug: String,
// file data
content: String
}
A regular expression ("//\- define (.*?): (.*?)\n") is used to extract comments from the article's template that define data for that article:
// iterate through HTML property pattern matches
for capture in re_define.captures_iter(&file_content as &str) {
// remove the declaration from the the HTML output
article_content = article_content.replace(&capture[0].to_string(), "");
// get the property value
let property_value: &String = &capture[2].to_string();
// determine what field to assign the property to and assign it
match capture[1].to_lowercase().as_str() {
"title" => article.title = property_value.clone(),
"category" => article.category = property_value.clone(),
"subcategory" => article.subcategory = property_value.clone(),
"genre" => article.genre = property_value.clone(),
"published" => article.published = property_value.clone(),
"estimated_read_time" => article.estimated_read_time = property_value.clone(),
"description" => article.description = property_value.clone(),
"tags" => article.tags = property_value.clone(),
"keywords" => article.keywords = property_value.clone(),
"image" => article.image = property_value.clone(),
unknown_property # _ => {
println!("Ignoring unknown property: {}", &unknown_property);
}
}
}
Note: article is an instance of Article.
Issue:
The code works but what I'm concerned about the following part:
"title" => article.title = property_value.clone(),
"category" => article.category = property_value.clone(),
"subcategory" => article.subcategory = property_value.clone(),
"genre" => article.genre = property_value.clone(),
"published" => article.published = property_value.clone(),
"estimated_read_time" => article.estimated_read_time = property_value.clone(),
"description" => article.description = property_value.clone(),
"tags" => article.tags = property_value.clone(),
"keywords" => article.keywords = property_value.clone(),
"image" => article.image = property_value.clone(),
It calls .clone() on the same String (property_value) for every match (10 matches per article template), for every article template (a couple dozen templates in total), and I don't think it's the most efficient way to do it.
Note: I'm not sure if match is cloning for non-matches.
What I tried:
I tried referencing the property_value String, but I got an error for each reference.
Error from IDE (VS Code):
mismatched types
expected struct `std::string::String`, found `&&std::string::String`
expected due to the type of this binding
try using a conversion method: `(`, `).to_string()`
Error from cargo check:
error[E0308]: mismatched types
--> src/article.rs:84:38
|
84 | "image" => article.image = &property_value,
| ------------- ^^^^^^^^^^^^^^^ expected struct `std::string::String`, found `&&std::string::String`
| |
| expected due to the type of this binding
|
help: try using a conversion method
|
84 | "image" => article.image = (&property_value).to_string(),
| + +++++++++++++
I did try using .to_string(), but I'm not sure if converting a String to the same type is the most efficient to do it either.
Question:
How do I avoid calling .clone() on property_value so many times?

Going by the types, you should just be able to drop the borrow in property_value and then you don't need the .clone()s.
let property_value: &String = &capture[2].to_string();
// change to
let property_value: String = capture[2].to_string();
// or just simply
let property_value = capture[2].to_string();
I'm assuming this was added as capture[2] returns a str (non-sized type) which would require the & but with to_string() it converts to the owned type String which is fine on it's own. This wont have any performance effect as to_string() copies anyway.

Related

Array of objects in Crystal

Beginner question:
How to make an Array of objects from a Struct in Crystal? Or how to make an array of objects in Crystal? I am trying to emulate the go code.
struct Book
def initialize(
#Id : Int32,
#Title : String,
#Author : String,
#Desc : String
)
end
end
books = [] of Int32 | String <--- This is wrong
book1 = Book.new(1, "Hello", "Me", "This is a test.")
GO CODE:
type Book struct {
Id int `json:"id"`
Title string `json:"title"`
Author string `json:"author"`
Desc string `json:"desc"`
}
var Books = []models.Book{
{
Id: 1,
Title: "Golang",
Author: "Gopher",
Desc: "A book for Go",
},
}
Changing to a class allows me to return a new object. Which I am sure is the wrong way to make a new object in Crystal? I can then add objects to the array.
class Book
def initialize(id : Int32, title : String, author : String, desc : String)
#id = id
#title = title
#author = author
#desc = desc
end
def object
{
#id,
#title,
#author,
#desc
}
end
end
books = [] of Book <--- HOW TO SET ARRAY OF OBJECTS ON THE TYPE?
book1 = Book.new(1, "Hello", "Me", "This is a test.")
puts book1.object
books << book1.object
puts books
You can write
books = [] of Book
books << book1
or you can simply do this, and let Crystal recognise the type:
books = [book1]
The closest to the original is
books = [
Book.new(1, "Hello", "Me", "This is a test.")
]
EDIT: If you need JSON, you need to implement it, as described in the documentation. The easiest is to include JSON::Serializable:
require "json"
module TestBookJSON
struct Book
include JSON::Serializable # <-- this
def initialize(
#Id : Int32,
#Title : String,
#Author : String,
#Desc : String
)
end
end
books = [] of Book
book1 = Book.new(1, "Hello", "Me", "This is a test.")
books << book1
puts books.to_json
# => [{"Id":1,"Title":"Hello","Author":"Me","Desc":"This is a test."}]
end
It seems really what I wanted, and what was not the same as the struct version in go was an Array of Hashes, which in turn is an Array of Objects, as everything is an Object in Crystal?*.
hash = [] of Hash(Int32, String) # Array of Hashes
hash0 = {0 => "Jackson0"}
hash1 = {1 => "Jackson1"}
hash2 = {2 => "Jackson2"}
hash3 = {3 => "Jackson3"}
#Append the hashes to the array
hash << hash0
hash << hash1
hash << hash2
hash << hash3
OR
(0..3).each do |i|
hash << {i => "Jackson#{i}"}
end
RESULT : [{0 => "Jackson0"}, {1 => "Jackson1"}, {2 => "Jackson2"}, {3 => "Jackson3"}]
# Display them as JSON
get "/" do
hash.to_json
end
RESULT : [{"0":"Jackson0"},{"1":"Jackson1"},{"2":"Jackson2"},{"3":"Jackson3"}]
Which in turn can be queried using hash[0][1]
RESULT : {"1": "Jackson1"}

Type error when processing graphql result

I just started playing with reasonML and graphql and have built a simple react component that retrieves data from a world cup API. My code is below:
[#bs.module] external gql: ReasonApolloTypes.gql = "graphql-tag";
module GetMatches = [%graphql
{|
query getMatches($id: ID!){
matches(id: $id) {
date
homeTeam {name}
awayTeam {name}
homeScore
awayScore
goals {
scorer {
name
}
time
}
}
}
|}
];
module GetMatchesQuery = ReasonApollo.CreateQuery(GetMatches);
let component = ReasonReact.statelessComponent("Matches");
let make = _children => {
...component,
render: _self => {
let matchParam = GetMatches.make(~id="300331511", ());
<GetMatchesQuery variables=matchParam##variables>
...{
({result}) =>
switch (result) {
| Loading => <div> {ReasonReact.string("Loading")} </div>
| Error(error) =>
<div> {ReasonReact.string(error##message)} </div>
| Data(response) => <Matches matches=response##matches />
}
}
</GetMatchesQuery>;
},
};
Matches Component
let component = ReasonReact.statelessComponent("Matches");
let make = (~matches, _children) => {
...component,
render: _self =>
<div>
{
matches
|> Js.Array.map(match => <Match match key=match##id />)
|> ReasonReact.array
}
</div>,
};
But I'm getting this error:
This has type:
option(Js.Array.t(option({. "awayScore": option(int),
"awayTeam": option({. "name": option(string)}),
"date": option(string),
"goals": option(Js.Array.t(option({. "scorer":
option(
{. "name":
option(
string)}),
"time":
option(
string)}))),
"homeScore": option(int),
"homeTeam": option({. "name": option(string)})})))
But somewhere wanted:
Js.Array.t(Js.t(({.. id: string} as 'a))) (defined as array(Js.t('a)))
I added Js.log(response##matches); and got this in the console
I'm pretty sure there's some context missing here, and that you have something like open Belt at the top of your file.
The type error says response##matches is an array in an option, but also that the array elements themselves are in options, which is super weird. So my theory is that the code generated by graphql-ppx uses something like array indexing, which translates to a call to Array.get that in the standard library throws an exception if out of bounds, but in Belt, and many other standard libraries replacements, returns an option.
This is one of many problems with using ppxs. It generates code not in isolation and can interact weirdly with the rest of your code, and it's not easy to debug.

HTTP client post with optional splat argument not compiled

I would like to send HTTP post request by using
HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, *, form : Hash(String, String) | NamedTuple)
I try to do this
url = "https://api.authy.com/protected/json/phones/verification/start"
headers = HTTP::Headers{"X-Authy-API-Key" => api_key}
form = {
via: "sms",
country_code: country_code,
phone_number: phone_number,
code_length: 6,
locale: "ru",
}.to_h
response = HTTP::Client.post(url, headers: headers, form: form)
Unfortunately, I got compilation error
no argument named 'form'
Matches are:
- HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, body : BodyType = nil) (trying this one)
- HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, body : BodyType = nil, &block)
- HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, *, form : String | IO)
- HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, *, form : String | IO, &block)
- HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, *, form : Hash(String, String) | NamedTuple)
- HTTP::Client#post(path, headers : HTTP::Headers | ::Nil = nil, *, form : Hash(String, String) | NamedTuple, &block)
What is correct way to do it?
This compilation error happens because .to_h returns a Hash(Symbol, Int32 | String) on your named tuple, and that is incompatible with any of the definitions for HTTP::Client.post.
To solve this, I suggest to explicitly define form as a Hash(String, String) and replace the keys and values by their string representation:
form : Hash(String, String) = {
"via" => "sms",
"country_code" => country_code.to_s, # assuming this is not a string
"phone_number" => phone_number,
"code_length" => "6",
"locale" => "ru",
}

How to check existance and nil in single line for variable in ruby on rails

def import_update
require 'csv'
file = params[:file]
CSV.foreach(file.path, headers: true) do |row|
#prod = Spree::Product.find(row["id"])
#var = Spree::Variant.find_by(product_id: #prod.id)
Spree::Product.where(:id => row["id"]).update_all(:name => row["name"] if !row[name].nil?.present?, :meta_description => row["meta_description"], :shipping_category_id => row["shipping_category_id"], :description => row["description"], :meta_keywords => row["meta_keywords"], :tax_category_id => row["tax_category_id"], :available_on => row["available_on"], :deleted_at => row["deleted_at"], :promotionable => row["promotionable"], :meta_title => row["meta_title"], :featured => row["featured"], :supplier_id => row["supplier_id"])
end
end
I want check that row is present or not. if it is present then it updated when it is not null and condition is in single line because I want to apply this for all variable in updation statement.I wrote code above but showing error.
Try this:
params = ["name","meta_description","shipping_category_id","description","meta_keywords","tax_category_id","available_on","deleted_at","promotionable","meta_title","featured","supplier_id"
hash = {}
params.each do |param|
if row[param]
hash[param] = row[param]
end
end
Spree::Product.where(:id => row["id"]).update_attributes(hash)
This will let you keep your code dry.
EDIT:
What are these lines supposed to do?:
#prod = Spree::Product.find(row["id"])
#var = Spree::Variant.find_by(product_id: #prod.id)
I presume you don't have several entries with one ID. And your not using the objects that you retrieved in those two lines, so simply write the method like this:
def import_update
require 'csv'
file = params[:file]
params = ["name","meta_description","shipping_category_id","description","meta_keywords","tax_category_id","available_on","deleted_at","promotionable","meta_title","featured","supplier_id"]
CSV.foreach(file.path, headers: true) do |row|
hash = {}
params.each do |param|
if row[param]
hash[param] = row[param]
end
end
Spree::Product.find(row["id"]).update_all(hash)
end
end

ZF2 - set selected value on Select Element

I've a problem with dropdown list with Zend Framework 2 & Doctrine.
I would put the "selected" attribute on my dropdown list but all options pass to selected
My code :
Controller :
public function editAction()
{
// get error message during addAction
$this->layout()->setVariable("messageError", $this->flashMessenger()->getErrorMessages());
$auth = $this->getAuthService();
if ($auth->hasIdentity()){
$builder = new AnnotationBuilder();
// Get id of StaticContent
$id = (int)$this->getEvent()->getRouteMatch()->getParam('id');
if (!$id) {
$this->flashMessenger()->addErrorMessage("Aucun plan choisi !");
return $this->redirect()->toRoute('admin/plans');
}
$plan = $this->getEntityManager()->getRepository("Admin\Entity\Plan")->find((int)$id);
$form = $builder->createForm($plan);
// Find options for Localite list (<select>)
$localites = $this->getEntityManager()->getRepository("Admin\Entity\Localite")->getArrayOfAll();
$form->get('localiteid')->setValueOptions($localites);
$form->get('localiteid')->setValue("{$plan->getLocaliteid()->getId()}");
// Find options for TypePlan list (<select>)
$typesPlan = $this->getEntityManager()->getRepository("Admin\Entity\TypePlan")->getArrayOfAll();
$form->get('typeid')->setValueOptions($typesPlan);
$form->get('typeid')->setValue("{$plan->getTypeid()->getId()}");
// Options for Statut list (<select>)
$form->get('statut')->setValueOptions(array('projet'=>'Projet', 'valide'=>'Validé'));
$form->get('statut')->setValue($plan->getStatut());
$form->setBindOnValidate(false);
$form->bind($plan);
$form->add(array(
'name' => 'submit',
'attributes' => array(
'type' => 'submit',
'value' => 'Modifier',
'id' => 'submitbutton',
'class' => "btn btn-primary"
),
));
$request = $this->getRequest();
if ($request->isPost()) {
[...]
}
}
With
$localites = $this->getEntityManager()->getRepository("Admin\Entity\Localite")->getArrayOfAll();
$form->get('localiteid')->setValueOptions($localites);
i populate my dropdown correctly, normally with
$form->get('localiteid')->setValue("{$plan->getLocaliteid()->getId()}");
just set "selected" on option defined by :
$plan->getLocaliteid()->getId()
So why all options are selected in my dropdown ?!
Information : It's the same for typeId but no Statut
It's probably not working because of the curly braces. According to the PHP documentation
Using single curly braces ({}) will not work for accessing the return values of functions or methods or the values of class constants or static class variables.
This is also unnecessary when using setValue. ZF2 will convert it to a string when formatting it in the view.
When you create the arrays to pass to setValueOptions() you should make it an associative array of arrays with the following values:
$form->get('select')->setValueOptions(array(
'field' => array(
'value' => 'value_of_the_option',
'label' => 'what is displayed',
'selected' => true,
),
));
Which ever of the fields has the selected option set to true will be the default selection in the form element.
Personally i don't know if getArrayOfAll() such function exists, i assume that you are correctly passing array to FORM,
I think you should be doing something like this to set value.
$form->get('localiteid')->setValue($plan->getLocaliteid()->getId());
But Since you are populating DROP down i guess this approach will not work best with Drop Down. You need to do something like this
$form->get('localiteid')->setAttributes(array('value'=>$plan->getLocaliteid()->getId(),'selected'=>true));
I've found a bug ?!
$plan = $this->getEntityManager()->getRepository("Admin\Entity\Plan")->find((int)$id);
$idLocalite = 18;//(int)$plan->getLocaliteid()->getId();
$idTypePlan = 2;//(int)$plan->getTypeid()->getId();
When i'm using $plan->getLocaliteid()->getId(); or $plan->getTypeid()->getId() to pass parameter into Repository method getArrayOfAll($idLocalite)
LocaliteRepository.php :
class LocaliteRepository extends EntityRepository {
public function getArrayOfAll($currentLocaliteId) {
$result = $this->_em->createQuery("SELECT l.nom, l.localiteid FROM Admin\Entity\Localite l ORDER BY l.nom")->getArrayResult();
$localite = array();
foreach($result as $loc) {
if ($currentLocaliteId == $loc['localiteid']) {
$localite[$loc['localiteid']] = array(
'value' => $loc['localiteid'],
'label' => $loc['nom'],
'selected' => true,
);
} else {
$localite[$loc['localiteid']] = array(
'value' => $loc['localiteid'],
'label' => $loc['nom'],
'selected' => false
);
//$localite[$loc['localiteid']] = $loc['nom'];
}
}
return $localite;
}
}
So, if i'm using $idLocalite = 18 instead of $idLocalite = (int)$plan->getLocaliteid()->getId() only wanted option are selected. Why ?!