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
Related
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)
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(., '{{{')]
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/, '') }
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
I have coded some Ruby script to scrap some web data; then I merged that script with a small Sinatra application to "publish" some rest methods to fetch reports...
My Sinatra app is making a request to Nagios3 and navigate through some forms (3 steps in fact), this is just working fine.
Then under step3, I "press" the submit button and after a few seconds I have a huge html report with servicegroups availabilities. At this point I am using Nokogiri to extract just one field, this is working fine too for any servicegroup (it can manage any servicegroup dimension).
To have the /index with a list of servicegroups, I need to goto Step1 and Step2 only.
The Step3 is needed just for /check (build the report and fetch / print availability)
Well, all is just working fine so the question I'm making is like weird because the thing is I don't know how to dry it correctly.
I'm writting the #Initialize Mechanize stuff, Step1 and Step2 under before do filters. They are needed in both /index and /check, but in fact I don't know if this is the right place to do it.
Any code architectural tip :)
thanks in advance.
Francisco
require 'rubygems'
require 'sinatra'
require 'mechanize'
require 'nokogiri'
require 'logger'
configure :production do
enable :logging, :dump_errors
enable :inline_templates
disable :run
end
error do
e = request.env['sinatra.error']
puts e.to_s
puts e.backtrace.join("\n")
"Application Error!"
end
not_found do
"Page not found!"
end
before do
content_type 'text/html', :charset => 'utf-8'
# Initialize Mechanize
#agent = Mechanize.new
#agent.keep_alive = false
#agent.open_timeout = 3600
#agent.read_timeout = 3600
#agent.max_history = 0
#agent.log = Logger.new('mechanize.log')
#agent.auth('myusername', 'mysecretpass')
#page = #agent.get('http://nagios3/nagios/cgi-bin/avail.cgi')
# Step1 and Step2 are use in both /index and /check
# Step1 - Form1 (Select Report Type[hostgroups,hosts,hostgroups,servicegroups*,services])
#form1 = #page.forms.first
#form1.field_with(:name => 'report_type').options[2].select
#page = #agent.submit(#form1)
# Step2 - Form2 (Select Servicegroup)
#form2 = #page.forms.first
#total_services_list = #form2.field_with(:name => 'servicegroup').options
end
get '/' do
# When under /index we don't go further to Step3 - Form3 (generate report)
erb :index
end
get '/check/:servicegroup' do
#servicegroup = params[:servicegroup].to_i
# Step3 - Form3 (Select Report Options)
#form2.field_with(:name => 'servicegroup').options[#servicegroup].select
#page = #agent.submit(#form2)
#form3 = #page.forms.first
#form3.field_with(:name => 'timeperiod').options[7].select
#page = #agent.submit(#form3)
# Step4 - Extract Average from computed data
page_html = Nokogiri::HTML.parse(#page.body)
#total_hostservices_size = page_html.xpath("html/body/div[3]/table/tr[*]/td[2]").to_a.size
puts #average = page_html.xpath("html/body/div[3]/table/tr[#{#total_hostservices_size}]/td[2]").inner_text
erb :check, :layout => false
end
__END__
##layout
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head><title>Nagios Availability Checker</title></head>
<body>
<strong>Nagios Availability Checker</strong>
<hr />
<div>
<%= yield %>
</div>
<hr />
</body>
</html>
##index
<h3>List Service Groups</h3>
<% #total_services_list.each_with_index do |name,id| %>
<%= id %> - <%= name %><br />
<% end %>
##check
<%= #average %>
You can use helpers:
helpers do
def select_report_type
# ...
end
def something_else
# ...
end
end
before { select_report_type }
get "/" do
select_report_type
# ...
haml :index
end