Upload File parameter not coming through to controller - ruby-on-rails

I am fairly new to Rails and have been making steady progress on a Mobile Web App I am working on for our local high school but have run into an issue which I am stumped on. I am hoping the collective knowledge here will point me in the right direction.
I have a model for the school athletes (first name, last name, height, weight, graduation years, - standard stuff) which is working (CRUD via standard scaffold generation) and now I want to add the ability to import records via CSV upload.
In an effort to not reinvent the wheel, I am following this example from Rich on Rails. To get familiar with it, I created a separate Rail project and followed the example and it all works as expected. Great. Now to integrate into my existing project.
Everything seems to integrate fine with one exception - the CSV file is never passed to my model in the params I cannot figure out why. I am sure it is something obvious but I have stared at this problem for several hours and am unable to see what I am doing wrong.
Here is a portion of my Athletes controller:
class AthletesController < ApplicationController
before_action :set_athlete, only: [:show, :edit, :update, :destroy]
# GET /athletes
# GET /athletes.json
def index
#athletes = Athlete.all.order(:lastname, :firstname)
end
# POST /athletes/import
# POST /athletes/import.json
def import
logger.info(params.to_yaml)
begin
Athlete.import(params[:file])
redirect_to page_path('admin'), notice: "Athletes imported."
rescue
redirect_to page_path('admin'), notice: "Invalid CSV file format."
end
end
# GET /athletes/1
# GET /athletes/1.json
def show
end
# GET /athletes/new
def new
#athlete = Athlete.new
end
# GET /athletes/1/edit
def edit
end
My model looks like this:
class Athlete < ActiveRecord::Base
# an athlete can be on more than one team
has_and_belongs_to_many :teams, through: :athletes
require 'csv'
## CSV import
def self.import(file)
CSV.foreach(file.path, headers: true) do |row|
athlete_hash = row.to_hash # exclude the ? field
athlete = Athlete.where(id: athlete_hash["id"])
if athlete.count == 1
athlete.first.update_attributes
else
Athlete.create!(athlete_hash)
end # end if !athlete.nil?
end # end CSV.foreach
end # end self.import(file)
I've added this onto my index view for testing, later on it will be in an admin area:
<div>
<h3>Import a CSV File</h3>
<%= form_tag import_athletes_path, multipart: true do %>
<%= file_field_tag :file %>
<%= submit_tag "Import CSV" %>
<% end %>
</div>
No matter what I do, I never get the value of the file_field_tag to come through to the controller. If I add other fields using text_field_tag they come through as expected but the file_field_tag value never does.
--- !ruby/hash:ActionController::Parameters
utf8: "✓"
authenticity_token: it3yBxBnzA4UQ/NILP5GNoYJeO5dyg+Z+VfhE/C6p7k=
commit: Import CSV
action: import
controller: athletes
Redirected to http://localhost:3000/
Completed 302 Found in 8ms (ActiveRecord: 0.0ms)
I am stumped - if anyone has any ideas as to what I might be doing wrong, I would be grateful. I have about 300 athletes which I want to import and have no desire to type them in.

It turns out because I am using jQuery Mobile for my framework, I need to add "data-ajax=false" to my form tag. This change to my form allowed the file parameter to be visible in the controller:
<h3>Import a CSV File</h3>
<%= form_tag(import_athletes_path, { :multipart => true, :'data-ajax' => false }) do %>
<%= file_field_tag :file %>
<%= submit_tag "Import CSV" %>
<% end %>
</div>
A short while ago I recalled reading something about file uploads and jQuery Mobile not working by default. It is due to the standard AJAX navigation employed by jQM.

Related

Rails form_tag not POSTing, GETing on the source page

It's been a while since I've needed anything but a form_for.
Now I'm trying to set up an admin area search where the search box is in the menu bar. But instead of POSTing to the results path it's reloading the existing page with GET params, including the authenticity_token.
So my set up:
Gemfile
source 'https://rubygems.org'
ruby '2.2.3'
gem 'rails', '4.2.3'
# And so on....
routes.rb
namespace :admin do
resources :results do #search results
collection do
get :display
end
end
# many more routes here
end
admin/results_controller.rb
class Admin::ResultsController < ApplicationController
def display
#results = Elasticsearch::Model.search(params[:query], [Article]).paginate(page: params[:page], per_page: 30)
respond_to do |format|
format.html # index.html.erb
end
end
private
# Don't think this is really relevant, adding out of desparation
def result_params
params.require(:result).permit(:query)
end
end
And in my universal admin bar:
<%= form_tag(display_admin_results_path, method: :post) do %>
<%= text_field_tag :query, params[:query], {class: "form-control", placeholder: "Search"} %>
<span class="input-group-btn"><%= submit_tag "→".html_safe, class: "btn btn-success" %></span>
<% end %>
Logs show no sign of a redirection, Chome console shows no javascript "bork."
Instead of submitting that form and getting sent to /admin/results/display or even /admin/results/display?query=blah I'm gettting
?utf8=✓&authenticity_token=j3w4dtBbFLzJzqWGZ9x4Q4GsUi%2FxmjYFrPjdzm8ccLKdxpOR0KwrX2hIAzXkR96cuTVgwG1sbYBKDdSO%2F3O6Wg%3D%3D&query=hello&commit=→
It's got to be something obvious... prepared for the 'doh' moment.
The problem is in your routes.
In your console write rails routes (or rake routes), and you will see that it expects get method for your display_admin_results_path. So you access your controller with GET method.
your routes:
display_admin_results GET /admin/results/display(.:format) admin/results#display
admin_results GET /admin/results(.:format) admin/results#index
POST /admin/results(.:format) admin/results#create
admin_result GET /admin/results/:id(.:format) admin/results#show
PATCH /admin/results/:id(.:format) admin/results#update
PUT /admin/results/:id(.:format) admin/results#update
DELETE /admin/results/:id(.:format) admin/results#destroy
You should implement your search query, so that it will use POST, and return corresponding view(which is defenetly not index).
Also, you render method searches the view with coresponding name, so you may want to create display.html.erb file, or specify the view you want to use in controller.

Empty Array in Controller ...but works in Rails Console

I'm baffled...
I have a model called Distributor and when I do Distributor.all in the console I get the array of all the records as I should (there are currently 657 records in there according to this query).
However, I created a controller action to show this distributors in an index view. And an empty array is returned from the same query Distributor.all
I've restarted the server
I've checked for spelling mistakes (literally copied and pasted the Distributor.all from controller to
console to make sure and it worked in console)
I've used binding.pry to debug and no problems are showing there
I've checked the logs and no errors are showing up
I've checked the logs vs the console and the same SQL query is being run Distributor Load (10.9ms) SELECT "distributors".* FROM "distributors" for both, but returning different results
generating the CSV or HTML isn't a problem because both load without errors
Any suggestions?
Distributor Model:
class Distributor < ActiveRecord::Base
belongs_to :brand
def self.to_csv
attributes = %w{distributor_id mfg_brand_id company_name address_one address_two city state postcode country address_type primary_address users_count created_at}
CSV.generate(headers: true) do |csv|
csv << attributes
all.each do |d|
csv << d.attributes.values_at(*attributes)
end
end
end
end
Distributors Controller
class DistributorsController < ApplicationController
before_action :authenticate_user!
def index
#user = current_user
if #user.admin?
#distributors = Distributor.all
respond_to do |format|
format.html
format.csv { send_data #distributors.to_csv }
end
else
flash["danger"] = "You must be a logged in Admin to access this route"
redirect_to root_path
end
end
end
Distributors index.html.erb
<h1>Distributors#index</h1>
<p>Find me in app/views/distributors/index.html.erb</p>
<%= #distributors.each do |d| %>
<%= d %>
<% end %>
In your controller, if you replace #distributors = RheemruudDistributor.all with #distributors = Distributor.all it should work, right? Or am I missing something?
Finally figured it out...and I figured it was something simple.
This is a multi-tenant app using the apartment gem and I had to put this newly created model in the excluded models part of the apartment.rb initializer.
Figured I'd leave this hear in case anyone else is in this situation.
use
<% #distributors.each do |d| %>
<%= d %>
<% end %>
instead of
<%= #distributors.each do |d| %>
<%= d %>
<% end %>
on your view

How to pass params to new view in Ruby on Rails app?

I'm trying to make simple app. I input my first name and last name to simple <%= form_for #data do |f| %> rails form and after submitting it, app should render simple text like this. My first name is <%= data.first_name %> and my last name is <%= data.last_name %>. I don't know why but my app is saying this error:
undefined local variable or method `data' for
It's probably saying it because no params are passed to view.
Here is my code.
routes.rb
resources :data, only: [:new, :create, :index]
data_controller.rb
class DataController < ApplicationController
def new
#data = Data.new
end
def index
end
def create
#data = Data.new(data_params)
if #data.valid?
redirect_to #data
else
render :new
end
end
private
def data_params
params.require(:data).permit(:first_name, :second_name)
end
end
/views/data/new.html.erb
<%= form_for #data do |f| %>
<%= f.label :first_name %>
<%= f.text_field :first_name %>
<%= f.label :second_name %>
<%= f.text_field :second_name %>
<%= f.submit 'Continue', class: 'button' %>
<% end %>
/views/data/index.html.erb
<h2>Coolest app ever :D</h2>
<p>My first name is: <%= data.first_name %>.</p>
<p>And my second name is: <%= data.second_name %>.</p>
/models/data.rb
class Data
include ActiveModel::Model
attr_accessor :first_name, :second_name
validates :first_name, :second_name, presence: true
end
Please help to find out why params are not passing to next page. Thanks anyways :D
Your view should look like this:
<h2>Coolest app ever :D</h2>
<p>My first name is: <%= #data.first_name %>.</p>
<p>And my second name is: <%= #data.second_name %>.</p>
Also, I would suggest that calling a model something generic like Data is not a very Rails-y approach. Generally, domain models correspond to real-world things like User and Article, which are easy to understand and relate to. It'll get confusing quite fast if you use need to make another model and want to call it Data2 or something :)
Edit:
Since you specified that you do not wish to use the database, I would recommend passing in the object params through the redirect:
redirect_to(data_path(data: #data))
and in your controller's index method:
def index
#data = Data.new(params[:data])
end
Now your view should render properly, since you're passing the in-memory #data object attributes as params within the redirect. You then recreate this object in the index page or wherever you wish to redirect to.
To expand on Matt's answer, the reason you're getting NilClass errors is because:
You're redirecting to a data#show action when no show action has been enabled within your routes file. Since you've set your views up for the index, I'm assuming you want to redirect there when the #data object has been verified as valid:
redirect_to data_path
However I would recommend you follow Rails conventions and specify the data#show route within your routes.rb:
resources :data, only: [:index, :new, :create, :show]
and in your data_controller.rb:
def show
#data = Data.find(params[:id])
end
Another problem is that you're not actually saving the #data object upon creating it. The new method populates the attributes, and valid? runs all the validations within the specified context of your defined model and returns true if no errors are found, false otherwise. You want to do something like:
def create
#data = Data.new(data_params)
if #data.save
redirect_to data_path
else
render :new
end
end
Using save attempts to save the record to the database, and runs a validation check anyways - if validation fails the save command will return false, the record will not be saved, and the new template will be re-rendered. If it is saved properly, the controller will redirect to the index page, where you can call upon the particular data object you want and display it within your view.

form submit in rails not working, possible routing/path error unsure why?

I am trying to submit a form in rails that is just a pdf uplaod (using paperclip). There is something wrong with either my form, controller or model and i am not sure which.
this is my form:
<%= form_for #yearguide, :html => { :multipart => true } do |form| %>
<%= form.file_field :pdf %>
<%= form.submit "Add Event", class: "btn btn-primary" %>
<% end %>
my controller:
class YearController < ApplicationController
def new
#yearguide = Year.create(year_params)
end
def create
if #yearguide = Year.save
redirect_to '/'
else
render 'new'
end
end
my model:
class YearlyGuide < ActiveRecord::Base
has_attached_file :pdf
validates_attachment :document, content_type: { content_type: "application/pdf" }
end
my routes:
resources :year
I add the file and press upload, but I am being redirected to 'update.html.erb'.
The file doesn;t exist in the db, just an empty record.
When i debug the params on pressing uplaod I get this output
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"G7ZrXEiip/gsqhDObcfYhTT9kerYZGk+Zl29kWA5jos=", "year"=>{"pdf"=>#<ActionDispatch::Http::UploadedFile:0x000001029b0340 #tempfile=#<Tempfile:/var/folders/ns/ry6z7jfd6qg6j8xr2q6dw0yc0000gn/T/RackMultipart20140609-21455-1eg1sk3>, #original_filename="Artsmill Hebden Bridge Exhibition Programme 2014.pdf", #content_type="application/pdf", #headers="Content-Disposition: form-data; name=\"year[pdf]\"; filename=\"Artsmill Hebden Bridge Exhibition Programme 2014.pdf\"\r\nContent-Type: application/pdf\r\n">}, "commit"=>"Add Event", "action"=>"update", "controller"=>"year", "id"=>"9"}
=========================
EDIT
OK, so discrepancies with my naming led to the previosu errors, i started again, generating:
rails g model YearlyGuide pdf:attachment start:datetime end:datetime
rails g controller YearlyGuide new index show
now in my routes i have added
resources :yearly_guides
when i visit
/yearly_guides/new
I get this error
uninitialized constant YearlyGuidesController
I am really at a loss as to what I am doing wrong, I have done this before and never had these issues.
#iceman, thanks for your help and patience thus far.
The controller is not doing what it's supposed to do. This is the bare bones basic scheme of creating a new object in Rails.
class YearsController < ApplicationController
def new
#yearguide = Year.new
end
def create
#yearguide = Year.create(year_params)
if #yearguide.save
redirect_to '/' # I would consider redirect_to #yearguide to show the newly created object
else
render 'new'
end
end
end
EDIT:
You have to update your routes.rb to
resources :years
Since you are creating the yearguide object, rails infer that you have to do put/patch request, so request is going to update since rails getting id.
You have two options.
1) Change new method of controller like below
class YearController < ApplicationController
def new
#yearguide = Year.new
end
end
2) Override the method parameter by passing method params as 'post' inside your form tag

Trying to link to specfic new comic review page

I'm trying to get a link on an articles show page so that when a user clicks write new review it takes them to the link
/comic_reviews/'the article they want to comment on'/reviews/new
where they will be directed to the new reviews page
how can i accomplish this with
In your routes file you would specify a route like this
match '/comic_reviews/:comic_name/reviews/new' => 'reviews#new', via: :get
Then in your reviews controller you would need something like this
reviews_controller.rb
class ReviewsController < ApplicationController
def new
#comic = Commic.find_by_name(params[:comic_name])
if #comic
#review = #comic.reviews.build
render 'new'
else
#Render some error page since comic was not found
end
end
end
You will then have access to #comic and #review in your reviews/new view so you could build a form that just makes a post to create a review and allows you to store it. This should get you going.
Edit
In your new view you'd need to have a form that looks something like this
<%= form_for #review do |f| %>
<%= f.label :some_attribute %>:
<%= f.text_field :some_attribute %><br />
<%= f.submit %>
<% end %>
This will be expecting you have a route to create a review in your routes file and an action in your ReviewsController.
If you are struggling with such topics I suggest you read over this excellent tutorial
http://ruby.railstutorial.org/ruby-on-rails-tutorial-book
Or just read through the documentation for Rails API which will give you pretty accurate examples.
You can do this via routes
resources :comic_reviews do
resources :reviews
#probably_some_other_route_here
end
And with restful pattern it will be easy to achieve whatever you want
controller
Someclass < Someotherclass
#some your code
def new
#instance_var = Your_model.new
end
def create
#instance_var = Your_model.new(params[:some_name_here])
if #instance_var.save
redirect_to somewhere
else
render 'new'
end
end
end
Also you'll need form, but i dont think that will cause any troubles

Resources