I am working on web app development using ruby on rails. I want to enable users to upload images for their favorite food. I have food_item model and a food_image model. In the food_item model:
has_many :food_images
has_many :food_portions #this is the work done by another teammate
I also define in the food_item controller:
def food_item_params
params.require(:food_items).permit(:name, :category, :description, food_images_attributes: [:id, :food_item_id, :avatar]).tap do |whitelisted|
whitelisted[:portion] = params[:food_items][:portion]
whitelisted[:price] = params[:food_items][:price]
end
There are two issues:
You need to save your updated object
You should be doing this within the bounds of resources (although not essential)
Resourceful
The first step is to ensure you're using this with the correct routes etc.
You shouldn't have an add_images method in your controller - you could achieve what you need with edit/update:
#config/routes.rb
resources :food_items do
resources :images #-> if necessary, this should be its own controller rather than adding needless methods to your other controller
end
You should use the following controller setup:
#app/models/food_item.rb
class FoodItem < ActiveRecord::Base
accepts_nested_attributes_for :food_images
end
#app/controllers/food_items_controller.rb
class FoodItemsController < ApplicationController
def edit
#food_item = FoodItem.find params[:id]
#food_item.food_images.build
end
def update
#food_item = FootItem.find params[:id]
respond_to do |format|
if #food_item.update food_item_params
...
end
end
end
private
def food_item_params
params.require(:food_items).permit(:name, :category, :description, food_images_attributes: [:id, :food_item_id, :avatar]) #-> this is enough (no need to "whitelist")
end
end
This will give you the ability to load the following:
#url.com/food_items/:id/edit
#app/views/food_items/edit.html.erb
<%= form_for #food_item do |f| %>
= form_for #food_item, html: { :multipart => true } do |f|
= f.label :title
= f.text_field :title
= f.fields_for :food_images do |p|
= p.label :avatar
= p.file_field :avatar
.actions
= f.submit
<% end %>
This will submit to the "update" method, which should save the required object for you.
You should only upload one file at a time
If you need to upload multiple files, you'll need to use a gem such as cocoon to add them. Rails is great but not magical -- it has to build a single object with each fields_for.
I can explain more about this if required.
--
To give you context on why you should be using the edit / update methods for this, you need to look up the resourceful principle for object orientated programming.
This is built on the "resources" principle put forward at the inception of HTTP -- a standardized set of technologies which allow browsers to send and retrieve data from servers.
In short, it means there are certain conventions you should abide by to keep your app extensible.
Because Ruby/Rails is object orientated, everything you do in a well-tailored application should revolve around objects. Like resources, these allow you to create a system which is both flexible and extensible if done properly.
Thus, with your code, you have to remember that you're trying to add an image to the food items object. You should therefore be editing the food items object, updating it with the extra image; which the above code will help you achieve.
Like Badheka said: "why are you querying and just saving the #food_item?
Anyways, Notice that when you try to create food_images, you did:
food_item_params[:food_image]['avatar'].each do |a|
#food_image = #food_item.food_images.create!(:avatar=>a)
end
You were looping through the avatar in food_image attributes of food_item_params
However, if you check your food_item_params, you have:
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
With this, in all possibility, the structure of the data that this params is expecting will be something like:
{
food_items:
{
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: ""
}
}
}
while what is being returned by your food_item_params will be as follow:
{
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: ""
}
}
There are a number of issues with this:
1) The error you are getting: param is missing or the value is empty: food_items this suggests that the param that is coming in is not in the format specified above. the parent attribute food_items is missing, so that is the first part of your code that you need to rectify from inside the view where you are sending the avatar, or conform your food_item_params method to meet the structure of what is coming in through the params.
2) There is no food_item_params[:food_image]['avatar'] to loop on, since thee is no food_image in your params. What you have is food_image_attributes, so your loop will be on food_item_params[:food_image_attributes][:avatar]
3) Looping through the food_item_params[:food_image_attributes][:avatar] suggests that it is an array. If so, then the way you are permitting it is wrong too. You will have to specify that it is an array from inside the food_item_params method as follow:
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, avatar: []])
end
If however, as I suspect, what is coming through your param is as follow:
food_images_attributes:
{
id: "",
food_item_id: "",
avatar: []
}
Then your food_item_params method should be as follows:
def food_item_params
params.require(:food_images_attributes).permit(:id, :food_item_id, avatar: [])
end
I'll advise you check your params and paste what it looks like here.
And as a last word, check what you are sending from the view, and set your controller accordingly, or check what you want the controller to expect, and send same from the view.
You need to change food_item_params method :
From :
def food_item_params
params.require(:food_items).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
To :
def food_item_params
params.require(:food_item).permit(food_images_attributes: [:id, :food_item_id, :avatar])
end
Also, you can improve your code :
Add accepts_nested_attributes_for :food_images in food_item model, then make changes in controller's method
def add_image
#food_item = FoodItem.find(params[:id])
respond_to do |format|
if #food_item.update_attributes(food_item_params)
format.html { redirect_to #food_item, notice: 'Images uploaded successfully' }
else
format.html { redirect_to add_image_food_item_path}
end
end
end
Related
I have a model which exposes a field called 'body' in the api. The user submit json including that field, and it should get saved to a field called 'custom_body' in the database.
If custom_body is empty, then I use i18n to return a default string for the 'body' field.
This is the model:
class Reminder < ApplicationRecord
# Relationships
belongs_to :user
def body=(value)
self.custom_body = value
end
def body
custom_body.presence || I18n.t('reminder.default_body', name: self.user.name)
end
end
The controller is scaffolded so standard and works fine. Here is the update action:
# PATCH/PUT /reminders/1
def update
if #reminder.update(reminder_params)
render json: #reminder
else
render json: #reminder.errors, status: :unprocessable_entity
end
end
and here are the whitelisted params:
# Only allow a trusted parameter "white list" through.
def reminder_params
params.require(:reminder).permit( :id, :user_id, :subject, :greeting, :body, :is_follow_up)
end
The problem is the default returns fine, but when the user submits 'body' it does not get persisted into 'custom_body', which I thought is solved by this method:
def body=(value)
self.custom_body = value
end
It works in this gorails cast (see 10 min mark), so what am I missing?
Just in case someone else stumbles on this question the Rails code is correct.
The problem here was strong parameters. In the controller notice the require:
def reminder_params
params.require(:reminder).permit( :id, :user_id, :subject, :greeting, :body, :is_follow_up)
end
The json body of the patch request therefore must be structured like this:
{ "reminder":
{ ... all the fields ... }
}
The client was just sending a list of fields and not putting them inside the 'reminder' object.
For example, it needs to be like this:
Parameters:
{"reminder"=>
{"subject"=>"your checklist reminder!",
"greeting"=>"Hi",
"body"=>"\nThis is a quick reminder about the outstanding items still on your checklist.\n\nPlease click the button below to review the items we're still waiting on.\n\nThank you,\n\nRichard Owner",
"id"=>"33b29a76-298f-4ef2-a76a-ca4438c6d1ce"
}
}
I'm working in Rails and I have two models, a prelaunch and an initiative. Basically I want a user to be able to create an initiative using the attributes of the prelaunch. Basically what I want to have happen is when a user visit's their prelaunch and is ready to turn it into an initiative, it brings them to a form that has their prelaunch information already populated and they can just add the additional info. I've managed to do this for every attribute so far except for the attached image, called :cover_image.
I think the problem is that I'm setting the initiative's cover_image to the prelaunch's cover_image on the new action of my controller, but because this is the new action and not create, I'm not saving the initiative yet. I think this means the cover_image isn't getting reuploaded yet, so #iniative.cover_image.url doesn't point to anything. It also doesn't appear to be prepopulating the file field of my form with anything.
I'm not entirely sure how feasible all of this is, but it's what the client asked for so I'm trying to make it work for them.
Here's my controller:
def new
#initiative = Initiative.new
populate_defaults(#initiative)
#initiative.build_location
3.times{ #initiative.rewards.build }
#initiative.user = current_user
if !params[:prelaunch_id].nil? && !params[:prelaunch_id].empty?
# if user is transferring a prelaunch, assign its attributes to the intiative
#prelaunch = Prelaunch.find(params[:prelaunch_id])
#initiative.assign_attributes(title: #prelaunch.title,
teaser: #prelaunch.teaser,
category: #prelaunch.category,
funding_goal: #prelaunch.funding_goal,
term: #prelaunch.campaign.term,
story: #prelaunch.story,
location: #prelaunch.campaign.location,
video_url: #prelaunch.video_url,
EIN: #prelaunch.campaign.EIN,
nonprofit: #prelaunch.nonprofit,
organization_name: #prelaunch.campaign.organization.name)
end
end
Edit:
Thanks to peterept's answer below I've managed to get the prelaunch cover_image into the form and into the create action of the initiatives controller. The problem now is that everything seems to work perfectly in the create action: the initiative gets the prelaunch's cover image, it saves without error, and it redirects to the show action.
UNFORTUNATELY, By the time it reaches the show action of the controller, #initiative.cover_image is set to the default again. I can't figure out what could possibly be happening between the successful create action and the show action.
Here are the create and show actions of the initiatives controller:
def create
if !params[:initiative][:prelaunch_id].nil? && !params[:initiative][:prelaunch_id].empty?
#prelaunch = Prelaunch.find(params[:initiative][:prelaunch_id]) # find the prelaunch if it exists
end
#initiative = Initiative.new(initiatives_params)
#initiative.user = current_user
begin
#payment_processor.create_account(#initiative)
if #initiative.save
# #prelaunch.destroy # destroy the prelaunch now that the user has created an initiative
flash[:alert] = "Your initiative will not be submitted until you review the initiative and then press 'Go Live' on the initiative page"
redirect_to initiative_path(#initiative)
else
flash[:alert] = "Initiative could not be saved: " + #initiative.errors.messages.to_s
render :new
end
rescue Exception => e
logger.error e.message
flash[:error] = "Unable to process request - #{e.message}"
render :new
end
end
def show
#initiative = Initiative.find(params[:id])
#other_initiatives = Initiative.approved.limit(3)
end
And here is the initiatives_params method from the same controller:
def initiatives_params
initiative_params = params.require(:initiative).permit(
:terms_accepted,
:title,
:teaser,
:term,
:category,
:funding_goal,
:funding_type,
:video_url,
:story,
:cover_image,
:nonprofit,
:EIN,
:role,
:send_receipt,
:organization_name,
:crop_x, :crop_y, :crop_h, :crop_w,
location_attributes: [:address],
rewards_attributes: [:id, :name, :description, :donation, :arrival_time, :availability, :_destroy, :estimated_value])
if #prelaunch.media.cover_image
initiative_params[:cover_image] = #prelaunch.media.cover_image
end
initiative_params
end
You can pass the Image URL and display it on the page.
The user can then override this by uploading a new image (as per normal).
In you're create action, if they have not supplied a new image, then set it to the one in the assocoiated prelaunch - you'd want to copy the original so it doesn't get replaced if they upload a new one. (If you don't know which was the prelaunch, you could pass the ID down to the page).
I was able to make it work by saving the Paperclip object only. This is my model:
class Grade < ActiveRecord::Base
has_attached_file :certificate
end
If I run the following:
#grade.certificate = new_file
#grade.certificate.save
It saves/overwrite the file, but don't update the Grade object.
Versions: ruby-2.3.8, Rails 4.2.11.3 and paperclip (4.3.6)
I would like to store the youtube embed link version of youtube URL's in my db. I have a helper that helps convert the users pasted link into the embed version.
Anyway before saving I'd like to convert it then pass the converted value over to the model for it to be saved. I've created a method in my controller that I use to pass the link param into the helper method that does the conversion.
Anyway this has no effect. The orignal link pasted into the text box gets saved. I've tried doing this in the model with before_save and self.link but it doesn't work either. Below is my current code.
Form:
= form_for #micropost, :remote => true do |f|
= f.text_field :link, :class => "addLinkField"
= f.submit
Helper for converting pasted link:
module OgpObjectsHelper
def video_embed(video_url)
if video_url[/(https?):\/\/(www.)?(youtube\.com\/watch\?v=|youtu\.be\|youtube\.com\/watch\?feature=player_embedded&v=)([A-Za-z0-9_-]*)(\&\S+)?(\S)*/]youtube_id = $4
"http://www.youtube.com/embed/#{ youtube_id }"
end
end
Controller:
class MicropostsController < ApplicationController
include OgpObjectsHelper
before_filter :convert_video_link
def create
#micropost = current_user.microposts.build(params[:micropost])
respond_to do |format|
if #micropost.save
format.html { render :partial => '/users/partials/micropost'}
end
end
end
def convert_video_link
video_embed(params[:micropost][:link])
end
end
I would appreciate a best approach solution thanks.
Kind regards
put it in your Micropost model and do not forget to validate cos if no match to youtube format, link will now be nil
# micropost.rb
include OgpObjectsHelper
before_validate :convert_video_link
protected
def convert_video_link
self.link = video_embed(link)
end
In the code you have above, you need to modify the convert_video_link method to read:
params[:micropost][:link] = video_embed(params[:micropost][:link])
Since you are still creating the record from the param values. The method as it stands does not alter those parameters.
The problem lies in your helper. You're trying to set the link attribute to the 4th backreference in your regex but not actually assigning the value. The filter just returns a link but doesn't set any attribute on the model to that link. My suggestion would be to move this into a before_save on your model. The following untested code should do it.
class Micropost
before_save :convert_video_url
def convert_video_url
url = self.link
youtube_id = url.scan(/(https?):\/\/(www.)?(youtube\.com\/watch\?v=|youtu\.be\|youtube\.com\/watch\?feature=player_embedded&v=)([A-Za-z0-9_-]*)(\&\S+)?(\S)*/)[0][3]
self.link = "http://www.youtube.com/embed/#{ youtube_id }"
end
Note, the regex will break if YouTube change the format of their URLs.
Robin
I have a user class, which has_many resumes, each of which has many items. On my users/show page, I render multiple resumes, which is working. In my users_controller I have the following:
def show
...
#resumes = #user.resumes.paginate(page: params[:page])
#resume = #user.resumes.build if user_signed_in?
#resume_items = #user.res.paginate(page: params[:page])
#edu_items = #resume.edu.paginate(page: params[:page])
...
end
I defined the function res in my User model:
def res
Resume.where("student_id = ?", id)
end
And that worked quite well. However, I'm trying to do the same thing with the function edu in my Resume model:
def edu
Education.where("resume_id = ?", id)
end
but it's not working, #edu_items isn't being set to anything. Now I know it has to do with this method specifically, because if I change id to the id of a specific resume, that resume's items are rendered correctly, except across every resume. I know it's a simple fix, I've just been staring at it for way too long at this point and can't figure it out. Any advice would be amazing.
EDIT: #makaroni4: Instead of having #educations = #user.educations, I'd rather keep the items from each resume separate. Is it possible to define a method like the educations one that will make #educations = #resume.educations?
EDIT 2: I managed to get what I was trying to do to work, thanks for the advice. I solved it by doing away with the edu method altogether, and passing local variables to the partial:
<%= render :partial => 'shared/edu', :as => :educations, :locals => {:resume_educations => resume_item.educations} %>
shared/edu
<% if resume_educations.any? %>
<ol class="educations">
<%= render partial: 'shared/edu_item', collection: resume_educations %>
</ol>
<%= will_paginate #educations %>
<% end %>
Probably not the cleanest solution, but it seems to work.
I think that your model structure should look like:
class User < ActiveRecord::Base
has_many :resumes
def educations
Education.joins(:resume => :user).where(:users => { :id => id })
end
end
class Resume < ActiveRecord::Base
belongs_to :user
has_many :educations
end
class Education < ActiveRecord::Base
belongs_to :resume
end
So in your controller you can access them like:
#resumes = #user.resumes
#educations = #user.educations # all users educations, from all resumes
or
#educations = #resume.educations # educations for particular resume
And also I recommend you to read this article http://petdance.com/2012/04/the-worlds-two-worst-variable-names/ about variables naming, variables like resume_items and methods res and edu should say that you're doing smtg not in the right way.
It does not work, because the result of your edu method will always be empty.
In your code you are building a resume object:
#resume = #user.resumes.build if user_signed_in?
If you use build an object is created, but not saved to the database yet. This means that your #resume.id is nil. And thus the result of your edu method will be empty.
You could use the following to create the record in the database:
#resume = #user.resumes.create if user_signed_in?
But your edu method will still return an empty collection, because it's a new record and it won't be associated with any items yet.
Please expand on what you are trying to do exactly, because with this code #resume.edu will always be empty for the reason explained above.
Also: consider using the built-in Rails functionality instead of making your own methods.
I have a Rails app that lets a user construct a database query by filling out an extensive form. I wondered the best practice for checking form parameters in Rails. Previously, I have had my results method (the one to which the form submits) do the following:
if params[:name] && !params[:name].blank?
#name = params[:name]
else
flash[:error] = 'You must give a name'
redirect_to :action => 'index'
return
end
But for several form fields, seeing this repeated for each one got tiresome. I couldn't just stick them all in some loop to check for each field, because the fields are set up differently:
a single key: params[:name]
a key and a sub-key: params[:image][:font_size]
only expect some form fields to be filled out if another field was set
Etc. This was also repetitive, because I was setting flash[:error] for each missing/invalid parameter, and redirecting to the same URL for each one. I switched to using a before_filter that checks for all necessary form parameters and only returns true if everything's okay. Then the my results method continues, and variables are just assigned flat-out, with no checking involved:
#name = params[:name]
In my validate_form method, I have sections of code like the following:
if (
params[:analysis_type][:to_s] == 'development' ||
params[:results_to_generate].include?('graph')
)
{:graph_type => :to_s, :graph_width => :to_s,
:theme => :to_s}.each do |key, sub_key|
unless params[key] && params[key][sub_key]
flash[:error] = "Cannot leave '#{Inflector.humanize(key)}' blank"
redirect_to(url)
return false
end
end
end
I was just wondering if I'm going about this in the best way, or if I'm missing something obvious when it comes to parameter validation. I worry this is still not the most efficient technique, because I have several blocks where I assign a value to flash[:error], then redirect to the same URL, then return false.
Edit to clarify: The reason I don't have this validation in model(s) currently is for two reasons:
I'm not trying to gather data from the user in order to create or update a row in the database. None of the data the user submits is saved after they log out. It's all used right when they submit it to search the database and generate some stuff.
The query form takes in data pertaining to several models, and it takes in other data that doesn't pertain to a model at all. E.g. graph type and theme as shown above do not connect to any model, they just convey information about how the user wants to display his results.
Edit to show improved technique: I make use of application-specific exceptions now, thanks to Jamis Buck's Raising the Right Exception article. For example:
def results
if params[:name] && !params[:name].blank?
#name = params[:name]
else
raise MyApp::MissingFieldError
end
if params[:age] && !params[:age].blank? && params[:age].numeric?
#age = params[:age].to_i
else
raise MyApp::MissingFieldError
end
rescue MyApp::MissingFieldError => err
flash[:error] = "Invalid form submission: #{err.clean_message}"
redirect_to :action => 'index'
end
You could try active_form (http://github.com/cs/active_form/tree/master/lib/active_form.rb) - just ActiveRecord minus the database stuff. This way you can use all of AR's validation stuff and treat your form like you would any other model.
class MyForm < ActiveForm
validates_presence_of :name
validates_presence_of :graph_size, :if => # ...blah blah
end
form = MyForm.new(params[:form])
form.validate
form.errors
Looks like you are doing the validation in the controller, try putting it in the model, it's better suited to that sort of thing.
If you were to tackle the problem again today, you could create a model for the query parameter set and use Rails' built in validations, Rails 3 makes this a lot easier with ActiveModel::Validations see this post.
Model
class Person
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name
attr_accessor :email
validates_presence_of :name,:message => "Please Provide User Name"
validates_presence_of :email,:message => "Please Provide Email"
end
Note that you don't necessarily need to save/persist the model to validate.
Controller
#person.name= params["name"]
#person.email= params["email"]
#person.valid?
One you called .valid? method on the model, the errors will be populated inside #person object. Hence,
View
<%if #person.errors.any? %>
<%#person.errors.messages.each do|msg| %>
<div class="alert alert-danger">
<%=msg[0][1]%>
</div>
<%end%>
<%end%>