How to display an array item in dancer? - perl

I try to display an array item in dancer, here is the code:
get '/' => sub {
my #rows = ('aaa','bbb','ccc');
template 'crud.tt', {'rows' => \#rows};
};
and the template is:
<h2><% $rows[1] %></h2>
<h2><% rows[1] %></h2>
<% FOREACH r IN rows %>
<p><% r %></p>
<% END %>
In the h2 element show nothing, what is the right way?

You can't pass anything but a simple scalar value if you are using the default Dancer template engine. But if you enable Template::Toolkit as the engine then all kinds of things are possible.
You can do this globally by setting template: template_toolkit in the YAML config file, or you can set it just for this route by writing
get '/' => sub {
my #rows = ('aaa','bbb','ccc');
set template => 'template_toolkit';
template 'crud.tt', { rows => \#rows };
};
Your template will look like
<h2><% rows.1 %></h2>
<% FOREACH r IN rows %>
<p><% r %></p>
<% END %>
and you will need
use Template;
to load the Template::Toolkit module before you use either method

Related

How do you populate a hidden field in a Rails 6 form that loops through one table and stores answers in another table?

I am trying to create a personality test.
I have a Questions table that looks like this
ID | Question |
----------------------------
1 | How likely would you etc...
and a Results table that looks like this
ID | Answer | Question_ID | User_Id
------------------------------------------------
1 | 1 | 1 | 1
I have 68 questions that I want to loop through and I want to store the answers (which are integers on a scale from 1-10) in my Results table.
How do I create a form that will save data to my Results table?
I am having trouble populating the answer column of my Results table.
Here's what I have. It doesn't work.
<%= form_with(model: #result, local: true) do |form| %>
<% #questions.each do |question| %>
<div>
<h4><%= question.id %>. <%=question.question %></h4><br />
<div class="hidden-field">
<%= form.hidden_field :question_id, value: question.id %>
</div>
<div class="field">
<%= form.number_field :answer, placeholder:'Please answer on a scale from 1 to 10' %>
</div>
</div>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
In my controller, I have a Home Controller (to display a home page)
class HomeController < ApplicationController
def index
#questions = Question.all.order(:created_at)
#user = User.new
#result = Result.new
end
end
I also have my Results Controller
class ResultsController < ApplicationController
before_action :set_result, only: [:show, :edit, :update, :destroy]
def new
#result = Result.new
end
def create
#result = Result.new(result_params)
end
private
def set_result
#result = Result.find(params[:id])
end
def result_params
params.require(:result).permit(:answer, :user_id, :question_id)
end
end
The questions are displaying perfectly fine. I can even store the data, but only one record gets saved and it only populates the question_id column with 68 and leaves the answer column NULL
What am I doing wrong?
A few tips to get you headed in the right direction:
User.new is a problem.
New users don't have an ID, so their non-existent ID can't be saved to the database.
I noticed your form doesn't have a user_id field. This is good. Since it will be the same for all records, once you have a viable #user record saved to the database first, you can just set the #user.id to the record in the controller:
`#record.create(user_id: #user.id, question_id: results_params[:question_id])
Also, you don't need to have :user_id in your permitted parameters if it's not a field in your form. This opens you up to form value injection:
def result_params
params.require(:result).permit(:answer, :question_id) # delete :user_id
end
You want to create multiple records at once
Totally doable. You just can't use #result = Result.new(result_params) because this only creates one #result.
You'll need to loop over the params[:results] in the create action and call #result.create() on each one.
Pro tip for someday: you could actually delegate this to a method in your model: Result.batch_create(params)
Look at your form submission parameters for tips
Place a debugger (I use byebug or pry) call at the top of your create action. This will halt your terminal output and give you a console inside your server.
You should be able to see the parameters that have just been submitted in your server log in your terminal (this depends on what server you are using).
Or, once the debugger has paused the server and given you a console, just type params to see what is being sent. You should see a whole collection of results, not just one.
You've opened yourself up to duplicates
Just automatically creating a new #result each time means your database could have lots of duplicate values.
I think what you should do is first check to see if a record exists where the user_id and question_id match what you have in the form, then either update or create the record accordingly.
Longhand, this method would look something like this (again, remember we need to do this inside a loop of every form result parameter):
params[:results].each do |result_param|
if Result.where(user_id: result_param[:user_id], question_id: result_param[:question_id]).exists?
result = Result.where(user_id: result_param[:user_id], question_id: result_param[:question_id])
else
result = Result.new(user_id: result_param[:user_id], question_id: result_param[:question_id])
end
But, this is really bad code and of course Rails gives you a method for this:
result = Result.where(user_id: result_param[:user_id], question_id: result_param[:question_id]).first_or_create
Be sure to read this good article about .first_or_create
TL;DR you've got a few core issues to explore to make this work, but you're on the right track.
So after reading up on this, I have code that works. This post helped tremendously along with #chiperific reply
I put this form in my results/new.html.erb file
<%= form_with(model: result, local: true) do |form| %>
<% #questions.each do |question| %>
<%= fields_for "result[]", result do |result_field| %>
<h4><%= question.id %>. <%=question.question %></h4><br />
<%= result_field.hidden_field :question_id, value: question.id %>
<%= result_field.number_field :answer, placeholder:'Please answer on
a scale from 1 to 10' %>
<% end %>
<% end %>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
I put this in my results controller
class ResultsController < ApplicationController
before_action :set_result, only: [:show, :edit, :update, :destroy]
def new
#result = Result.new
#questions = Question.all.order(:created_at)
end
def create
params[:result].each do |result_param|
result = Result.new(result_param.permit(:answer, :user_id, :question_id))
end
end
private
def set_result
#result = Result.find(params[:id])
end
end
The key insight was that I need to loop through my results and create a new result instance for every single one of my results.
I removed my Home controller and home view page and changed my routes.rb file to root 'results#new'
I have not addressed the issues of duplicate records (which is very valid) so I know my code will need some refactoring, but this code answers my initial question (I'm very open to feedback)

DOMXPath multiple contain selectors not working

I have the following XPath query that a kind user on SO helped me with:
$xpath->query(".//*[not(self::textarea or self::select or self::input) and contains(., '{{{')]/text()") as $node)
Its purpose is to replace certain placeholders with a value, and correctly catches occurences such as the below that should not be replaced:
<textarea id="testtextarea" name="testtextarea">{{{variable:test}}}</textarea>
And replaces correctly occurrences like this:
<div>{{{variable:test}}}</div>
Now I want to exclude elements that are of type <div> that contain the class name note-editable in that query, e.g., <div class="note-editable mayhaveanotherclasstoo">, in addition to textareas, selects or inputs.
I have tried:
$xpath->query(".//*[not(self::textarea or self::select or self::input) and not(contains(#class, 'note-editable')) and contains(., '{{{')]/text()") as $node)
and:
$xpath->query(".//*[not(self::textarea or self::select or self::input or contains(#class, 'note-editable')) and contains(., '{{{')]/text()") as $node)
I have followed the advice on some questions similar to this: PHP xpath contains class and does not contain class, and I do not get PHP errors, but the note-editable <div> tags are still having their placeholders replaced.
Any idea what's wrong with my attempted queries?
EDIT
Minimum reproducible DOM sample:
<div class="note-editing-area">
<textarea class="note-codable"></textarea>
<div class="note-editable panel-body" contenteditable="true" style="height: 350px;">{{{variable:system_url}}</div>
</div>
Code that does the replacement:
$dom = new DOMDocument();
libxml_use_internal_errors(true);
$dom->loadHTML($html);
$xpath = new DOMXpath($dom);
foreach ($xpath->query(".//*[not(self::textarea or self::select or self::input or self::div[contains(#class,'note-editable')]) and contains(., '{{{')]/text()") as $node) {
$node->nodeValue = preg_replace_callback('~{{{([^:]+):([^}]+)}}}~', function($m) use ($placeholders) {
return $placeholders[$m[1]][$m[2]] ?? '';
},
$node->nodeValue);
}
$html = $dom->saveHTML();
echo html_entity_decode($html);
Use this below xpath.
.//*[not(self::textarea or self::select or self::input or self::div[contains(#class,'note-editable')]) and contains(., '{{{')]

How can I access the current template name in Mojolicious?

I'd like to access the template name in Mojolicious from inside the template itself for debugging purposes, in the same way the Template Toolkit does (see here)
The variable __FILE__ works neatly but it refers to the current file and not to the top level template, which means it's useless inside a layout template.
I've also tried
<%= app->renderer->template_name %>
but no result
Is it possible at all in Mojolicious?
This can be done in two slightly different ways:
First by adding a before_render hook and setting a variable. It's easy to pack it all inside a plugin like so:
package Mojolicious::Plugin::TemplateName;
use Mojo::Base 'Mojolicious::Plugin';
sub register {
my ($self, $app, $conf) = #_;
$app->helper('template' => sub { return shift->stash('mojo.template') });
$app->hook(before_render => sub {
my $c = shift;
$c->stash('mojo.template', $_[0]->{template} )
});
}
1;
and use it inside a template like this
<%= template %>
Second, it can be done inside the templates - by setting the variable inside the template itself:
% stash('template', __FILE__);
and then reusing the variable in the layout:
<%= $template %>
In this case you get the file name with suffix and all - not just the template.
Inspired by the answer here about templates being rendered inside-out.

How to use .sub on a variable in rails

I am trying to strip the parent directory path out of my string variable for display.
Here is my command that generates the list of directories for my select_tag to display:
<% #get_dir_list = Dir["/watchfolder/miniprod/*"].sort %>
Here is what it currently displays:
/watchfolder/hot
/watchfolder/inhouse
/watchfolder/contract/inhouse
Here is what I want to display:
/hot
/inhouse
/contract/inhouse
I want to strip the parent path off of the display list of sub-directories to make it easier for the user to read.
Here is the command that I have. I can't seem to get the formatting correct:
<% #get_dir_list_display = #get_dir_list.sub(/[watchfolder]/,'') %>
In addition, How do I use sub for this string: "watchfolder/archive" I'm not sure how to setup the sub with the '/' (slash) included.
<% #get_dir_list_display = #get_dir_list.sub(/[watchfolder/archive]/,'') %>
You will have to map:
#get_dir_list.map{ |dir| dir.sub(/\/watchfolder/, '') }

error happens when I try "all" method in datamapper

When I try to do this in Sinatra,
class Comment
include DataMapper::Resource
property :id, Serial
property :body, Text
property :created_at, DateTime
end
get '/show' do
comment = Comment.all
#comment.each do |comment|
"#{comment.body}"
end
end
It returns this error,
ERROR: undefined method `bytesize' for #<Comment:0x13a2248>
Could anyone point me to the right direction?
Thanks,
Your getting this error because Sinatra takes the return value of a route and converts it into a string before trying to display it to the client.
I suggest you use a view/template to achieve your goal:
# file: <your sinatra file>
get '/show' do
#comments = Comment.all
erb :comments
end
# file: views/comments.erb
<% if !#comments.empty? %>
<ul>
<% #comments.each do |comment| %>
<li><%= comment.body %></li>
<% end %>
</ul>
<% else %>
Sorry, no comments to display.
<% end %>
Or append your comments to a String variable and return it when your done:
get '/show' do
comments = Comment.all
output = ""
comments.each do |comment|
output << "#{comment.body} <br />"
end
return output
end