My update method of a Product controller is defined as follows:
def update
#product = Product.find(params[:id])
if params[:product][:image_path]
# Check if this product already has an image
File.delete(#product.full_image_path) if File.exist?(#product.full_image_path)
# Upload the new image
uploaded_img = params[:product][:image]
#product.image_path = Time.now.to_i.to_s + File.extname(uploaded_img.original_filename)
File.open(#product.full_image_path, 'w') do |file|
file.write(uploaded_img.read)
end
end
#product.name = params[:product][:name]
#product.description = params[:product][:description]
respond_to do |format|
if #product.errors.count == 0
format.html { redirect_to products_path, :notice => t(:product_updated) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #product.errors, :status => :unprocessable_entity }
end
end
end
This simply deletes the old image if already present, and uploads the new one. It also updates the Product attributes
How can I use #product.update_attributes(params[:product]) to avoid updating name and description attributes as I've done here?
If I do #product.update_attributes(params[:product]) I get an error because the params hash contains a value named "image" which is not an attribute of the object.
Thanks in advance
You could create an attribute setter for the image in the Product model called image=:
def image=(uploaded_img)
# Check if this product already has an image
File.delete(full_image_path) if File.exist?(full_image_path)
# Upload the new image
image_path = Time.now.to_i.to_s + File.extname(uploaded_img.original_filename)
File.open(full_image_path, 'w') do |file|
file.write(uploaded_img.read)
end
end
After that, remove the rest of the code in the controller and use #product.update_attributes(params[:product]).
I didn't tried it but I think it should work.
Do you know that you have some gems that allow to manage easily file upload like https://github.com/jnicklas/carrierwave or https://github.com/thoughtbot/paperclip
You should try to refactor your controller a bit, a controller should not be running any other tasks other than directing the traffic of your models and views. Try to move all your file operations to a separate helper.
Related
I have a few hours with something that is probably very easy.
I have a nested model
resources :grades do
resources :students
end
So I defined
before_action :set_grade, except: [:mass_input]
to my students_controller
def set_grade
#grade = Grade.find(params[:grade_id])
end
I'm very good with this, the problem is that now I'm using another action that takes :grade_id from another source, so I cant use set_grade, instead I'm passing the id with javascript. Works.
My problem appears here, when I try to call to create method, I'm probably doing it wrong ..
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
#is this create way ok or I'm overriding???
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
end
This is my create action
def create
#student = Student.new(student_params)
#grade.students << #student
respond_to do |format|
if #student.save
format.html { redirect_to school_grade_path(#grade.school,#grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: #student }
else
format.html { render :new }
format.json { render json: #student.errors, status: :unprocessable_entity }
end
end
end
By this way code works but this line is not working
#grade.students << #student
#grade is not passing from mass_input to create. I think I'm not calling create properly but I cant find how to do it , because is not redirecting neither
My mass_input action is working by this way
def mass_input
#grade = Grade.find(#data['grade'])
#data = JSON.parse(params[:form_data])
Student.create(:rut => #data['mass_students'][1][0], :nombre => #data['mass_students'][1][1], :apellido => #data['mass_students'][1][2])
grade.students << student
respond_to do |format|
if student.save
format.html { redirect_to school_grade_path(grade.school,grade), notice: 'Alumno creado con éxito.' }
format.json { render :show, status: :created, location: student }
else
format.html { render :new }
format.json { render json: student.errors, status: :unprocessable_entity }
end
end
end
but I think is AWFUL, I must use my own create action
Thanks!!
Oh... From my point of view you are doing smth strange... The fast solution for your issue would be smth like this:
1) Rewrite before action in a new way:
before_action :set_grade
And method set_grade:
def set_grade
#grade = Grade.find(params[:grade_id].presence || #data['grade'])
end
2) Set method for student params
def student_params
data = JSON.parse(params[:form_data])['mass_students']
#Transform data to be student params. For ex:
data.map{|_key, info| {:rut => info[0], :nombre => info[1], :apellido => info[2]}}
end
3) Rewrite mass_input method
def mass_input
respond_to do |format|
if (#students = #grade.students.create(student_params).all?(&:persisted?)
#some actions when everything is great.
else
#some actions if not of them valid (maybe redirect & show info about not created students)
end
end
end
But you should definetly read more rails guides... http://guides.rubyonrails.org/
Sorry, I couldn't comment it. So I can just post a reply, it is not an complete answer though. In the student controller
Try to use
#student = #grade.students.new
or
#student = Student.new
#student.grade = #grade or #student.grade_id = params[:grade_id]
So when you do #student.save, you won't need to do the line below, and it will still work
#grade.students << #student
Ruby on rails has conventions you should follow to simplify lots of things. The first thing I see here is that in your def mass_input, you are using
Student.create(...)
The method create, as it says, creates an object but also saves it into database. So you should have new instead of create because new does not save it to database, just instantiates it:
#student = Student.new
...inside def mass_input, and by default the submit action in your view will take your object to the create method (if the object is new it goes to create, other way it goes to update, thanks to Rails). For this you could take a look at http://guides.rubyonrails.org/action_controller_overview.html
About the line #grade.students << #student, I assume you are intending to add the newly created student to his grade. See this example of usage of nested resources when trying to create, edit or destroy http://railscasts.com/episodes/139-nested-resources. In any case, nested resources implies this:
class Grade < ActiveRecord::Base
has_many :student
end
class Student < ActiveRecord::Base
belongs_to :grade
end
So, in your model Student you should have a column to store the Grade of that student. And then in your params you should receive the actual grade and store it in the grade_id inside your #student.
If something is not clear, I suggest you to take a look at the nested resources guide http://guides.rubyonrails.org/routing.html#nested-resources
As a commentary, << is used to add "things" to the end of an array, i.e. if you want to quickly store in an array some info you use:
array = []
Student.all.each do |s|
array << s.name
end
It will store in the array all the names of your students. Obviously there is a simpler way to do this by doing this:
Student.pluck(:name)
Sorry if the title is a little confusing. I have a form for an Item with the field name. There's a text field where the user can input a name and submit it. But if the user doesn't type in anything and hits submit, Rails gives me a param not found: item error, and I'm not sure who to get around this.
items_controller.rb
def new
#item = Item.new()
respond_to do |format|
format.html
format.json { render json: #item }
end
end
def create
#item = Item.new(item_params)
respond_to do |format|
if #item.save
format.html { redirect_to items_path }
format.json { render json: #item, status: :created, location: #item }
else
format.html { render action: 'new', :notice => "Input a name." }
format.json { render json: #item.errors, status: :unprocessable_entity }
end
end
end
private
def item_params
params.require(:item).permit(:name)
end
app/views/items/new.html.haml
= form_for #item do |f|
= f.label :name
= f.text_field :name
= f.submit "Submit"
The params.require(:item) part is what is causing the error. What the convention for handling the error when params[:item] isn't present?
It's late for an answer but i'll still write it for someone else. As stated in rails guides you need to use fetch instead of require in strong parameters, by using fetch you can provide a default value if nothing is passed as input. Something like:
params.fetch(:resource, {})
Update:
Scaffolded rails4 app:
https://github.com/szines/item_17751377
It works if a user keep name field empty when create a new item...
Looks, it works without problem...
Development.log shows that parameters would be the following if user keep a field empty:
"item"=>{"name"=>""}
There is always something in the hash...
As Mike Li already mentioned in a comment, something wrong... because shouldn't be empty this params[:item]...
You can check if something nil, with .nil? , in this case params[:item].nil? will be true if it is nil. Or you can use .present? as sytycs already wrote.
Previous answer:
If you have situation when :item is empty, you should just use params[:item] without require.
def item_params
params[:item].permit(:name)
end
More information about require in strong_parameters.rb source code:
# Ensures that a parameter is present. If it's present, returns
# the parameter at the given +key+, otherwise raises an
# <tt>ActionController::ParameterMissing</tt> error.
#
# ActionController::Parameters.new(person: { name: 'Francesco' }).require(:person)
# # => {"name"=>"Francesco"}
#
# ActionController::Parameters.new(person: nil).require(:person)
# # => ActionController::ParameterMissing: param not found: person
#
# ActionController::Parameters.new(person: {}).require(:person)
# # => ActionController::ParameterMissing: param not found: person
def require(key)
self[key].presence || raise(ParameterMissing.new(key))
end
I personally have not switched to strong parameters so I'm not sure how one should handle something like:
params.require(:item).permit(:name)
but you can always check for item presence with something like:
if params[:item].present?
…
end
I've got an problem with my update action for a nested resource.
In my app, my orders have many invoices.
Creating a new invoice, I correctly end up with the following url:
/orders/11/invoices/new
And when I edit the invoice, again, it's all correct:
/orders/11/invoices/3/edit
This works fine when the save is a success, however if the validation fails, it routes back to:
/invoices/3
I have the following in my invoices controller:
def update
# #order = Order.find(params[:order_id])
# #invoice = #order.invoices.find(params[:id])
#invoice = Invoice.find(params[:id])
respond_to do |format|
if #invoice.update_attributes(params[:invoice])
format.html { redirect_to(order_invoice_path(#invoice.order, #invoice), :notice => 'Invoice was successfully updated.') }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #invoice.errors, :status => :unprocessable_entity }
end
end
end
def edit
#invoice = Invoice.find(params[:id])
3.times { #invoice.invoice_items.build }
end
I'm assuming I need to edit the #invoice.errors part but I don't know what to change it to?
Any help appreciated. Jx
When updating failed, you use "render" (comparing with the "redirect_to" in the succeeding path), this brings you to invoice editing path by default. You can use "redirect_to" here to keep the URI path you want, but need remembering to preserve the models' states so your users don't need to fill the entire form all over again.
A detail instruction can be found here: How to make a render :edit call show the /edit in the address bar
Yan
in your form you should add your order, like this:
<%= form_for [#order, #invoice] ... do |f| %>
...
<% end %>
And then uncomment this two lines
# #order = Order.find(params[:order_id])
# #invoice = #order.invoices.find(params[:id])
so your form will send its request to POST /orders/XX/invoices/XX
I'm using Ruby on Rails 2.3.8 and I've got a registration form in which I receive a parameter as follows: /registration/4, which 4 is the id of a user who recommended the user that is about to register in the website.
The problem is that if the validation fails when the user submits the registation (the form renders to the controller users, action create_particular) the site will redirect to /users/create_particular, and therefore I lose the parameter with value 4 that I had before. Besides, I want the user to stay at the same url, which is /registration/4
How can I do that?
Then you should rewrite your create method. You should use redirect_to :back instead of render :action
UPD
def new
#word = Word.new(params[:word])
#word.valid? if params[:word]
end
def create
#word = Word.new(params[:word])
if #word.save
redirect_to #word
else
redirect_to new_word_path(:word => params[:word] )
end
end
Looks quite dirty, but this is just a scratch
UPD 2
This is really not the best solution, but it works
# routes.rb
match 'words/new' => 'words#create', :via => :post, :as => :create_word
# words_controller
def new
#word = Word.new
end
def create
#word = Word.new(params[:word])
respond_to do |format|
if #word.save
format.html { redirect_to(#word, :notice => 'Word was successfully created.') }
else
format.html { render :action => "new" }
end
end
end
# views/words/new.html.erb
<%= form_for(#word, :url => create_word_path) do |f| %>
...
<% end %>
Submit to the current URI (e.g. action=""). When the submission is valid, redirect. POST->Redirect->GET is a good habit.
From the top of my head:
Edit your controller (registrations_controller.rb file). Create method by default contains following piece of code:
if #registration.save
format.html { }
format.xml { }
else
format.html { }
format.xml { }
end
Add redirect_to (:back) between brackets to else format.html{}
Ok I solved the problem by doing the following:
1) I created two routes with the same path, but with different conditions method (one it's post and the other one is set to get)
2) I changed the form in order to post to the POST action defined above
3) I added render => :my_action when the validation fails
So that's pretty much it.
Thanks anyway for all your help.
Hidden field. That user ID param has a name by which you extract it in your controller, right? So just put that value in a hidden field of the same name, then it will survive a round-trip.
For example:
<%= hidden_field_tag :referring_user_id, params[:referring_user_id] %>
I'm trying to set up a model (client) that contains some general attributes about a company but also has a company logo attached. I'm reluctant to use a plugin because I want to grasp this aspect of rails if possible.
I've created a clients model and an image model and I can create a new client (scaffold code) and upload an accompanying image ok (using has_one :image, and belongs_to :client).
I used the following code (taken straight from Agile Rails 3rd ed)
class Client < ActiveRecord::Base
has_one :image
def uploaded_image=(image_file)
self.image = Image.new
self.image.name = base_part_of(image_file.original_filename)
self.image.content_type = image_file.content_type
self.image.data = image_file.read
end
def base_part_of(filename)
File.basename(filename).gsub(/^\w_-/,'')
end
end
when editing the client object however, the new file is uploaded but the changes aren't reflected in the db. Do I need to explicitly call update-attributes on #client.image? At the moment my controller update method is as follows:
def update
#client = Client.find(params[:id])
respond_to do |format|
if #client.update_attributes(params[:client])
flash[:notice] = 'Client was successfully updated.'
format.html { redirect_to(#client) }
format.xml { head :ok }
else
format.html { render :action => "edit" }
format.xml { render :xml => #client.errors, :status => :unprocessable_entity }
end
end
end
Thanks in advance for any tips, apologies for the noob question
I would strongly recommend to use Paperclip instead. It just works.