Odd rendering of an html table in Ruby on Rails when using content_tag - html

I'm trying to build a table in Ruby on Rails with the help of the content_tag method.
When I run this:
def itemSemanticDifferential(method, options = {})
if options[:surveyQuestion].any? then
#template.content_tag(:tr) do
#template.content_tag(:td, options[:surveyQuestion], :colspan => 3)
end
end
#template.content_tag(:tr) do
#template.content_tag(:th, options[:argument0])
end
end
Only the second part gets rendered:
#template.content_tag(:tr) do
#template.content_tag(:th, options[:argument0])
end
Can anyone tell me why this is?

Ruby Rails returns the last variable it used if no return is explicitly called.
( example: )
def some_method(*args)
a = 12
b = "Testing a String"
# ...
3
end # This method will return the Integer 3 because it was the last "thing" used in the method
Use an Array to return all the content_tag (WARNING: this method will return an Array, not a content_tag as you expect, you'll need to loop on it):
def itemSemanticDifferential(method, options = {})
results = []
if options[:surveyQuestion].any? then
results << #template.content_tag(:tr) do
#template.content_tag(:td, options[:surveyQuestion], :colspan => 3)
end
end
results << #template.content_tag(:tr) do
#template.content_tag(:th, options[:argument0])
end
return results # you don't actually need the return word here, but it makes it more readable
end
As asked by author of the Question, You need to loop on the result because it is an array of content_tags. Also, you need to use .html_safe to output the content_tags as HTML (not strings).
<% f.itemSemanticDifferential(:method, options = {}).each do |row| %>
<%= row.html_safe %>
<% end %>

Related

Include parent attributes when searching via query

I have a User Model that has_many Job Applications.
Everything works great, but when I try to search Job Applications by the User's first_name I get the error below.
Error
SQLite3::SQLException: no such column: first_name: SELECT "job_applications"
From my understanding, I need to include the User attributes in the Job Application query.
How can I accomplish this?
View (job application)
<%= form_for :search, :html => {:method => :get, :id => 'search'} do |f| %>
<%= text_field_tag :terms, params[:terms], :type => 'search' %>
<% end %>
Controller (job application)
def index
#job = Job.find(params[:job_id])
#job_applications = #job.job_applications.search(params[:terms])
end
Model (job application)
def self.search(terms)
terms ||= ""
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
### I am having an issue here...
### how do i include the user attributes in this query
"first_name like '%#{term}%'"
end
where(conditions.join " OR ")
end
You have to join job_applications table with users table.
# job_applications.rb
def self.search(terms)
terms ||= ""
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
"users.first_name like :term"
end
joins(:user).where(conditions.join " OR ")
end
Avoid passing raw user's inputs into your queries directly to avoid sql injection. Use rails' built-in filters or sanitize it yourself.
def self.search(terms)
terms ||= ""
term_args = []
conditions = terms.split(" ").map do |term|
term = term.strip.gsub("'","''")
term_args << "%#{term}%"
"users.first_name like ?"
end
joins(:user).where(conditions.join(' OR '), term_args)
end

How to put variables into HTML from ruby on rails

I tried to use a variable in HTML which is calculated from ruby code and MSSQL
This is my Ruby code to get #result
class StartingController < ApplicationController
before_action :require_user, only: [:index, :start]
attr_reader :table
attr_writer :table
def initialize
#table = Hash.new()
#connection = ActiveRecord::Base.connection
#st='exec search '
end
def start
.... some code set #st values
#result = #connection.exec_query(#st)
#table = #result[0]
redirect_to '/results'
end
end
def index
end
def results
end
end
This is the HTML which need to use #result
<% #result.each do |x| %>
<tr>
s: <td><%= x %></td>
</tr>
<% end %>
But I always get
undefined method `each' for nil:NilClass
if I
puts #result
I can get correct value
Can anyone help?
Problem is the instance variables(and variables) only accessible within an action, and your redirect_to actually change the action from start to results, so #results is not initialized.
I assume that you have a view at views/start/result.html.erb.
The best solution for this is you can remove the action results, and call render :results instead of redirect_to.

NoMethodError in StaticPages#home when attempting to list database query

Ruby noob here, I created a search form and I am trying to query a db and display the results. I am getting NoMethodError in StaticPages#home
along with....
/home/action/Projects/CodonCoderTest5/app/views/static_pages/home.html.erb where line #4 raised:
undefined method `each' for nil:NilClass
Where am I going wrong?
layouts/StaticPages/home
<h1>StaticPages#home</h1>
<% #data_bases.each do |list| %>
<div class="list">
<h1 class="list-mrnaCodon"><%= link_to list.mrnaCodon %></h1>
</div>
<% end %>
controller
class DataBaseController < ApplicationController
def new
end
def index
if params[:search]
#data_bases = Match.search(params[:search]).order("created_at DESC")
else
#data_bases = Match.order("created_at DESC")
end
end
end
The error means that #data_bases in your view is evaluating to nil. That makes sense, since the only way the view for StaticPages#home would have access to that variable is if it were set in the corresponding controller action (i.e. the home method on the StaticPagesController). Looks like you're only setting that variable on the DataBaseController.
class StaticPagesController < ApplicationController
...
def home
if params[:search]
#data_bases = Match.search(params[:search]).order("created_at DESC")
else
#data_bases = Match.order("created_at DESC")
end
end
...
end

Rails4: Formtastic3.0 , save multiple instances (for Answer models) at the same time

I have Question model and an answer model.
Each question can have one answer per user. I am trying to preset to a user a form to answer all questions, but couldnt really figure out how to do it with formtastic
Here is what I have so far
- #questions.each do |q|
= q.content
- ans = #user.answers.where(question_id:q.id).first.try(:content) || q.description
= semantic_form_for #answers do |f|
= f.label q.content
= f.inputs :content, placeholder: ans
= f.actions
I am trying to get some hint from How to loop through two alternating resources on a form? but I keep getting "undefined method `model_name' for Class:Class" for #questions if I try:
= semantic_form_for #questions do |q|
= q.input :content
= q.semantic_fields_for #answer do |a|
= a.inputs :content
= q.actions
Based on Railscast 198, but using formtastic here is my attempt that doesn't work either:
- semantic_form_for :Answer, :url => api_v1_answers_path, :method => :put do |f|
- #questions.each do |q|
- f.fields_for 'questions[]', q do |ff|
= q.content
= ff.input
= submit_tag "Submit"
Note:
1] I will like to have user press submit only once after he has added/edited all the answers
2] If there is an answer already present, it should be pre-poulated in the text box
3] I dont mind using simple_form gem if that makes life easier
Rather then making a form for #questions you need to pass a single object to your form helper (#questions is an array of questions). A good way to achieve this is though a form object.
# app/forms/questions_form.rb
class QuestionsForm
include ActiveModel::Model
def initialize(user)
#user = user
end
def questions
#questions ||= Question.all
end
def submit(params)
params.questions.each_pair do |question_id, answer|
answer = Answer.find_or_initialize_by(question_id: question_id, user: current_user)
answer.content = answer
answer.save
end
end
def answer_for(question)
answer = answers.select { |answer| answer.question_id == question.id }
answer.try(:content)
end
def answers
#answers ||= #user.answers.where(question: questions)
end
end
Then in your controller you'd have:
# app/controllers/submissions_controller.rb
Class SubmissionsController < ApplicationController
...
def new
#questions_form = QuestionsForm.new(current_user)
end
def create
#questions_form = QuestionsForm.new(current_user)
#questions_form.submit(params[:questions_form])
redirect_to # where-ever
end
...
end
In your view you'll want something along the lines of:
# app/views/submissions/new.html.haml
= form_for #questions_form, url: submissions_path do |f|
- #questions_form.questions.each do |question|
%p= question.content
= f.text_field "questions[#{question.id}]", value: #questions_form.answer_for(question)
= f.submit "Submit"
This doesn't use formtastic at all, it's just using the plain rails form helpers.
This code isn't tested so not sure if it even works but hopefully it helps get you on the right track.

Import CSV Data in a Rails App with ActiveAdmin

i want to upload CSV files through the activeadmin panel.
on the index page from the resource "product" i want a button next to the "new product" button with "import csv file".
i dont know where to start.
in the documentation is something about collection_action, but with the code below i have no link at the top.
ActiveAdmin.register Post do
collection_action :import_csv, :method => :post do
# Do some CSV importing work here...
redirect_to :action => :index, :notice => "CSV imported successfully!"
end
end
anyone here who use activeadmin and can import csv data?
Continuing from Thomas Watsons great start to the answer which helped me get my bearings before figuring the rest of it out.
The code blow allows not just CSV upload for the example Posts model but for any subsequent models thereafter. all you need to do is copy the action_item ands both collection_actions from the example into any other ActiveAdmin.register block and the functionality will be the same. hope this helps.
app/admin/posts.rb
ActiveAdmin.register Post do
action_item :only => :index do
link_to 'Upload CSV', :action => 'upload_csv'
end
collection_action :upload_csv do
render "admin/csv/upload_csv"
end
collection_action :import_csv, :method => :post do
CsvDb.convert_save("post", params[:dump][:file])
redirect_to :action => :index, :notice => "CSV imported successfully!"
end
end
app/models/csv_db.rb
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
csv_file = csv_data.read
CSV.parse(csv_file) do |row|
target_model = model_name.classify.constantize
new_object = target_model.new
column_iterator = -1
target_model.column_names.each do |key|
column_iterator += 1
unless key == "ID"
value = row[column_iterator]
new_object.send "#{key}=", value
end
end
new_object.save
end
end
end
end
note: this example does a check to see whether or not the first column is an ID column, it then skips that column as rails will assign an ID to the new object (see example CSV below for reference)
app/views/admin/csv/upload_csv.html.haml
= form_for :dump, :url=>{:action=>"import_csv"}, :html => { :multipart => true } do |f|
%table
%tr
%td
%label{:for => "dump_file"}
Select a CSV File :
%td
= f.file_field :file
%tr
%td
= submit_tag 'Submit'
app/public/example.csv
"1","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"2","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"3","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"4","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
"5","TITLE EXAMPLE","MESSAGE EXAMPLE","POSTED AT DATETIME"
note: quotations not always needed
Adding a collection_action does not automatically add a button linking to that action. To add a button at the top of the index screen you need to add the following code to your ActiveAdmin.register block:
action_item :only => :index do
link_to 'Upload CSV', :action => 'upload_csv'
end
But before calling the collection action you posted in your question, you first need the user to specify which file to upload. I would personally do this on another screen (i.e. creating two collection actions - one being a :get action, the other being your :post action). So the complete AA controller would look something like this:
ActiveAdmin.register Post do
action_item :only => :index do
link_to 'Upload posts', :action => 'upload_csv'
end
collection_action :upload_csv do
# The method defaults to :get
# By default Active Admin will look for a view file with the same
# name as the action, so you need to create your view at
# app/views/admin/posts/upload_csv.html.haml (or .erb if that's your weapon)
end
collection_action :import_csv, :method => :post do
# Do some CSV importing work here...
redirect_to :action => :index, :notice => "CSV imported successfully!"
end
end
#krhorst, I was trying to use your code, but unfortunately it sucks on big imports. It eat so much memory =( So I decided to use own solution based on activerecord-import gem
Here it is https://github.com/Fivell/active_admin_import
Features
Encoding handling
Support importing with ZIP file
Two step importing (see example2)
CSV options
Ability to prepend CSV headers automatically
Bulk import (activerecord-import)
Ability to customize template
Callbacks support
Support import from zip file
....
Based on ben.m's excellent answer above I replaced the csv_db.rb section suggested with this:
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
begin
target_model = model_name.classify.constantize
CSV.foreach(csv_data.path, :headers => true) do |row|
target_model.create(row.to_hash)
end
rescue Exception => e
Rails.logger.error e.message
Rails.logger.error e.backtrace.join("\n")
end
end
end
end
While not a complete answer I did not want my changes to pollute ben.m's answer in case I did something egregiously wrong.
expanding on ben.m's response which I found very useful.
I had issues with the CSV import logic (attributes not lining up and column iterator not functioning as required) and implemented a change which instead utilizes a per line loop and the model.create method. This allows you to import a .csv with the header line matching the attributes.
app/models/csv_db.rb
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
csv_file = csv_data.read
lines = CSV.parse(csv_file)
header = lines.shift
lines.each do |line|
attributes = Hash[header.zip line]
target_model = model_name.classify.constantize
target_model.create(attributes)
end
end
end
end
So your imported CSV file can look like this (use to match up with model attributes):
importExample.csv
first_name,last_name,attribute1,attribute2
john,citizen,value1,value2
For large excel which takes time on normal process, I created a gem that process Excel sheets using an active job and display results using action cable(websockets)
https://github.com/shivgarg5676/active_admin_excel_upload
Some of the solutions above worked pretty well. I ran into challenges in practice that I solved here below. The solved problems are:
Importing CSV data with columns in different orders
Preventing errors caused by hidden characters in Excel CSVs
Resetting the database primary_key so that the application can continue to add records after the import
Note: I took out the ID filter so I could change IDs for what I'm working on, but most use cases probably want to keep it in.
require 'csv'
class CsvDb
class << self
def convert_save(model_name, csv_data)
csv_file = csv_data.read
csv_file.to_s.force_encoding("UTF-8")
csv_file.sub!("\xEF\xBB\xBF", '')
target_model = model_name.classify.constantize
headers = csv_file.split("\n")[0].split(",")
CSV.parse(csv_file, headers: true) do |row|
new_object = target_model.new
column_iterator = -1
headers.each do |key|
column_iterator += 1
value = row[column_iterator]
new_object.send "#{key.chomp}=", value
end
new_object.save
end
ActiveRecord::Base.connection.reset_pk_sequence!(model_name.pluralize)
end
end
end