Upload image through Paperclip without saving object - ruby-on-rails

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)

Related

Custom Validator on: :create not running on rails app

I have a rails application for creating volumes and have written two custom validators using ActiveModel::Validator.
volume.rb:
class Volume < ActiveRecord::Base
include UrlSafeCode
include PgSearch::Model
include ActiveModel::Validations
validates :user_id, presence: true
validates_with Validators::VolumeValidator
validates_with Validators::CreateVolumeValidator, on: :create
def self.digest text
Digest::SHA256.hexdigest(text)
end
def text=(new_text)
new_text.rstrip!
new_text.downcase!
self.text_digest = Volume.digest(new_text)
super(new_text)
end
My Problem: The CreateVolumeValidator checks if a record with the same text_digest is already in the database. I only want to run this when creating a new volume so that I can still update existing volumes. However, adding on: :create to the CustomVolumeValidator causes the validator to stop working.
I've read through a lot of the other entries about similar issues and haven't found a solution. I am pretty sure I am missing something about when different attributes are getting created, validated, and saved, but I haven't worked with custom validations much, and I'm lost.
Here is the other relevant code.
volumes_controller.rb
def new
#volume = Volume.new
end
def create
our_params = params
.permit(:text, :description)
if params[:text].nil?
render :retry
return
end
text = params[:text].read.to_s
text_digest = Volume.digest(text)
#description = our_params[:description]
begin
#volume = Volume.where(text_digest: text_digest)
.first_or_create(text: text, user: current_user, description: our_params[:description])
rescue ActiveRecord::RecordNotUnique
retry
end
if #volume.invalid?
render :retry
return
end
render :create
end
def edit
get_volume
end
def update
get_volume
unless #volume
render nothing: true, status: :not_found
return
end
#volume.update(params.require(:volume).permit(:text, :description))
if #volume.save
redirect_to volume_path(#volume.code)
else
flash[:notice] = #volume.errors.full_messages.join('\n')
render :edit
end
end
def get_volume
#volume = Volume.where(code: params.require(:code)).first
end
create_volume_validator.rb
class Validators::CreateVolumeValidator < ActiveModel::Validator
def validate(volume)
existing_volume = Volume.where(text_digest: volume.text_digest).first
if existing_volume
existing_volume_link = "<a href='#{Rails.application.routes.url_helpers.volume_path(existing_volume.code)}'>here</a>."
volume.errors.add :base, ("This volume is already part of the referral archive and is available " + existing_volume_link).html_safe
end
end
end
If your goal is for all Volume records to have unique text_digest, you are better off with a simple :uniqueness validator (and associated DB unique index).
However, the reason your existing code isn't working is:
Volume.where(text_digest: text_digest).first_or_create(...)
This returns either the first Volume with the matching text_digest or creates a new one. But that means if there is a conflict, no object is created, and therefore your (on: :create) validation doesn't run. Instead, it simply sets #volume to the existing object, which is, by definition, valid. If there is no matching record, it does call your validator, but there's nothing to validate because you've already proved there is no text_digest conflict.
You could resolve by replacing the first_or_create with create, but again, you are vastly better off with a unique index & validator (with custom message if you like).

Rails 5 API - saving data using virtual attributes does not save data

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"
}
}

Why my user_id is updated to null?

I have 2 model one for user(amitians) other is about.rb to store their details
they have a has_one relationship between them but problem is whenever I create a new about my about table has an amitan_id = null
When I tried to do the same in rails console. It works fine.
here is my code for models
About_amitians.rb
class AboutAmitian < ApplicationRecord
belongs_to :amitian
end
Amitian.rb
has_one :about_amitian , foreign_key: "amitian_id"
My AboutAmitian controller
def new
#about_amitian = current_amitian.build_about_amitian
end
def create
#about_amitian = current_amitian.create_about_amitian!(about_amitian_params)
if #about_amitian.save
redirect_to :back
flash[:notice] = 'success'
else
render root_url
end
end
private
def about_amitian_params
params.require(:about_amitian).permit(:dob,:interest,:bio,:catch_phrase,:relationship_status)
end
In my server logs I have this query
Update 'about_amitians' set 'amitian_id' = NULL where 'about_amitian'.'id' = 1
andhere are the params send via form
Parameters: {"utf8"=>"✓", "authenticity_token"=>"6MtQlOfl4kU1BPMDT81m8rwwxSYdaQKpUEZbvnFw9ux1nVELSTSmaRNOgUCKNyTe2VrrkY01Ewn70hGWn/9wQg==", "about_amitian"=>{"dob"=>"m", "interest"=>"vm", "bio"=>"blyhjb", "catch_phrase"=>"hjkl", "relationship_status"=>"bljhbl"}, "commit"=>"Create About amitian"}
I finally solved it but this is insane...
Everything is fine in the code.. problem is completely diff than what I assumed.
Whenever I submit the form everything is fyn and i have my amitian_id set correctly. But whenever I redirect to my new page... my amitian_id updates to NULL.
As you can see from code.. I redirect_to :back (i.e back to new) so my amitian_id updates to null.

How to implement update controller method

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

Why do my changes to model instances not get saved sometimes in Rails 3?

I have a model named Post and I created two methods within the model that make changes to fields. The first method's changes get persisted when a save is called. The second method's changes do not get saved. I have noticed this behavior before in other models and I think I'm missing some basic knowledge on how models work. Any help on this would be greatly appreciated!
class Post < ActiveRecord::Base
def publish(user) # These changes get saved
reviewed_by = user
touch(:reviewed_at)
active = true
end
def unpublish() # These changes get ignored.
reviewed_by = nil
reviewed_at = nil
active = false
end
end
EDIT:
Here is a snippet from the controller"
class PostsController < ApplicationController
def publish
if request.post?
post = Post.find(params[:id].to_i)
post.publish(current_user)
redirect_to(post, :notice => 'Post was successfully published.')
end
end
def unpublish
if request.post?
post = Post.find(params[:id].to_i)
post.unpublish()
redirect_to(post, :notice => 'Post was successfully unpublished.')
end
end
...
UPDATE
Problem was solved by adding self to all the attributes being changed in the model. Thanks Simone Carletti
In publish you call the method touch that saves the changes to the database. In unpublish, you don't save anything to the database.
If you want to update a model, be sure to use a method that saves the changes to the database.
def publish(user)
self.reviewed_by = user
self.active = true
self.reviewed_at = Time.now
save!
end
def unpublish
self.reviewed_by = nil
self.reviewed_at = nil
self.active = false
save!
end
Also, make sure to use self.attribute when you set a value, otherwise the attribute will be consideres as a local variable.
In my experience you don't persist your changes until you save them so you can
explicitly call Model.save in your controller
explicitly call Model.update_attributes(params[:model_attr]) in your controller
if you want to save an attribute in your model I saw something like write_attribute :attr_name, value but TBH I never used it.
Cheers

Resources