Unable to upload image with Rails PaperClip Gem with angular 9+ - ruby-on-rails

I am using rails as a backend and angular as a frontend. I want to save images to rails backend. I decided to use the paperclip gem. Here is the error below.
↳ app/controllers/products_controller.rb:49:in `set_product'
Unpermitted parameters: :id, :created_at, :updated_at,
:image_file_name, :image_content_type, :image_file_size,
:image_updated_at
Here is some model for angular.
export class Product {
public image:File;
...//the rest of the variables
}
Here is where I set the value of the products image.
public fileSelected(event) {
this.product.image=event.target.files[0];
}
Then I use the httpclient angular functions to send it. That all is working so I wont show it unless requested. Only with an image does this fail.
On the rails side. Here is the paperclip migration
class AddAttachmentImageToProducts < ActiveRecord::Migration[6.0]
def self.up
change_table :products do |t|
t.attachment :image
end
end
def self.down
remove_attachment :products, :image
end
end
My model
class Product < ActiveRecord::Base
has_attached_file :image, styles: { medium: "300x300>", thumb: "100x100>" }, default_url: "/images/:style/missing.png"
validates_attachment :image, presence: true
do_not_validate_attachment_file_type :image
end
My controller
class ProductsController < ApplicationController
before_action :set_product, only: [:show, :update, :destroy]
# GET /products
def index
#products = Product.all
render json: #products.to_json
end
# GET /products/1
def show
render json: #products.image
end
# POST /products
def create
#products = Product.new(product_params)
if #products.save
render json: #products, status: :created, location: #products
else
render json: #products.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /products/1
def update
if #products.update(product_params)
render json: #products
else
render json: #products.errors, status: :unprocessable_entity
end
end
# DELETE /products/1
def destroy
#products.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_product
#products = Product.find(params[:id])
end
# Only allow a trusted parameter "white list" through.
def product_params
params.require(:product).permit(:name, :description, :image)
end
end

If you are using Rails API, then you should use paperclip adapters to save the image. You have to send base64 file from front end.
def update
#products.name = params[:product][:name]
#products.description = params[:product][:description]
image_base = params[:product][:image] ###base64 text of your image came from front-end
image = Paperclip.io_adapters.for(image_base)
image.original_filename = "image" ###Name of the file, you can take it from parameters / you can assign it statically
#products.image = image
if #products.save
##Do your stuff
else
render json: #products.errors, status: :unprocessable_entity
end
end
In case if you get any exceptions after adding above code in your controller, create a config/initializers/paperclip.rb file and place the following code in that initializer
Paperclip::DataUriAdapter.register
require 'paperclip/media_type_spoof_detector'
module Paperclip
class MediaTypeSpoofDetector
def spoofed?
false
end
end
end

Related

Rails API Controller Update Action -- How to add many-to-many

I have a Rails Api that feeds a Vue front end. I have two main models, Contacts and Outlets, with a many to many relationship via a join table ContactOutlets. I am trying to figure out how in the Contacts controller to add an association to an outlet.
I recognize, I could call the ContactOutlet create action separately, but it seems wasteful if Rails can handle this on the back end. I want vue to call contact#update once.
Contact Model:
class Contact < ApplicationRecord
has_many :contact_outlets
has_many :outlets, through: :contact_outlets
has_many :calls
validates_uniqueness_of :email
validates_uniqueness_of :name
end
Outlet Model:
class Outlet < ApplicationRecord
has_many :contact_outlets
has_many :contacts, through: :contact_outlets
has_many :calls
validates_uniqueness_of :website
end
ContactOutlet:
class ContactOutlet < ApplicationRecord
belongs_to :contact
belongs_to :outlet
validates_uniqueness_of :contact_id, :scope => :outlet_id
end
Contacts Controller:
class ContactsController < ApplicationController
before_action :set_contact, only: %i[ show update destroy ]
# GET /contacts
def index
#contacts = Contact.all
render json: #contacts, include: :outlets
end
# GET /contacts/1
def show
render json: #contact, include: :outlets
end
# POST /contacts
def create
#contact = Contact.new(contact_params)
if #contact.save
render json: #contact, status: :created, location: #contact
else
render json: #contact.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /contacts/1
def update
if #contact.update(contact_params)
render json: #contact, include: :outlets
else
render json: #contact.errors, status: :unprocessable_entity
end
end
# DELETE /contacts/1
def destroy
#contact.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_contact
#contact = Contact.find(params[:id])
end
# Only allow a list of trusted parameters through.
def contact_params
params.require(:contact).permit(:name, :email, :bio, :image_url)
end
end
Solved this. In case anyone else is looking the models above are fine. made some adjustments to the contact_params to allow access to the outlets array. Then fixed the update action. Full controller code below:
class ContactsController < ApplicationController
before_action :set_contact, only: %i[ show update destroy ]
# GET /contacts
def index
#contacts = Contact.all
render json: #contacts, include: :outlets
end
# GET /contacts/1
def show
render json: #contact, include: :outlets
end
# POST /contacts
def create
#contact = Contact.new(contact_params)
if #contact.save
render json: #contact, status: :created, location: #contact
else
render json: #contact.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /contacts/1
def update
if #contact.outlets
#contact.outlets.delete_all
end
if params[:outlets]
contactOutlets = params[:outlets]
contactOutlets.each do |outlet|
#contact.outlets << Outlet.find(outlet[:key])
end
end
if #contact.update(contact_params)
render json: #contact, include: :outlets
else
render json: #contact.errors, status: :unprocessable_entity
end
end
# DELETE /contacts/1
def destroy
#contact.destroy
end
private
# Use callbacks to share common setup or constraints between actions.
def set_contact
#contact = Contact.find(params[:id])
end
# Only allow a list of trusted parameters through.
def contact_params
params.require(:contact).permit(:name, :email, :bio, :image_url, outlet_ids:[])
end
end

My photo field don't save selection when I edit my post

The problem occurs when I edit a post in my back-office. All the belong_to table don't save the informations were saved when I create the post.
I don't find solution on the web. I'm not an experiment dev so I don't now where I must look for.
My upload_file :
require "image_processing/mini_magick"
class ImageUploader < Shrine
plugin :processing # allows hooking into promoting
plugin :versions # enable Shrine to handle a hash of files
plugin :delete_raw # delete processed files after uploading
process(:store) do |io, context|
original = io.download
pipeline = ImageProcessing::MiniMagick.source(original)
size_200 = pipeline.resize_to_limit!(200, 200)
size_300 = pipeline.resize_to_limit!(300, 300)
original.close!
{ original: io, moyen: size_300, small: size_200 }
end
end
My model :
has_one :photo, as: :attachable
accepts_nested_attributes_for :photo
My controller :
# frozen_string_literal: true
class Admin::ActualitiesController < Admin::AdminController
before_action :set_actuality, only: %i[show edit update destroy]
def index
#actualities = Actuality.order('id DESC').page(params[:page]).per(10)
end
def new
#actuality = Actuality.new
#actuality.build_photo
end
def edit
end
def show; end
def create
#actuality = Actuality.new(actuality_params)
if #actuality.save
redirect_to :show, notice: ''
else
render :new, notice: ''
end
end
def update
if #actuality.update(actuality_params)
redirect_to :show, notice: ''
else
render :edit
end
end
def destroy
#actuality.destroy
redirect_to admin_actualities_path, notice: ''
end
private
def set_actuality
#actuality = Actuality.find_by(id: params[:id])
end
def actuality_params
params.require(:actuality).permit(:title, :body, :apercu, :meeting, photo_attributes: [:photo])
end
end
And in my form :
<%= f.simple_fields_for :photo do |ff| %>
<%= ff.input :photo, as: :hidden %>
<%= ff.input :photo, as: :file %>
<% end %>
If you have any solutions, I'm here !
Thanks !

Paperclip gem, Image has an extension that does not match its content

I am new to rails and have been following a tutorial on YouTube https://www.youtube.com/watch?v=70Pu_28yvdI and have gotten to around minute 40 and when I try to create a new post and attach an image I get an error Image has an extension that does not match its content, I coded the exact same code he did and keep getting the error. Thank you so much for the help.
post.rb file
class Post < ActiveRecord::Base
belongs_to :user
has_attached_file :image, styles: { medium: "700x500#", small: "350x250" }
validates_attachment_content_type :image, content_type: /\Aimage\/.*\Z/
end
post_controller.rb file
class PostsController < ApplicationController
before_action :find_post, only: [:show, :edit, :update, :destroy]
before_action :authenticate_user!, except: [:index, :show]
def index
#post = Post.all.order("created_at DESC")
end
def show
end
def new
#post = current_user.posts.build
end
def create
#post = current_user.posts.build(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def edit
end
def update
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post.destroy
redirect_to root_path
end
private
def find_post
#post = Post.find(params[:id])
end
def post_params
params.require(:post).permit(:title, :link, :description, :image)
end
end
20150728130528_add_attachment_image_to_posts.rb file
class AddAttachmentImageToPosts < ActiveRecord::Migration
def self.up
change_table :posts do |t|
t.attachment :image
end
end
def self.down
remove_attachment :posts, :image
end
end
If you're on windows(from https://github.com/thoughtbot/paperclip):
If you're using Windows 7+ as a development environment, you may need
to install the file.exe application manually. The file spoofing system
in Paperclip 4+ relies on this; if you don't have it working, you'll
receive Validation failed: Upload file has an extension that does not
match its contents. errors.
Make sure you're using the same image type as you are validating:
You should ensure that you validate files to be only those MIME types
you explicitly want to support. If you don't, you could be open to XSS
attacks if a user uploads a file with a malicious HTML payload.
If you're only interested in images, restrict your allowed
content_types to image-y ones:
validates_attachment :avatar,
:content_type => { :content_type => ["image/jpeg", "image/gif", "image/png"] }
Paperclip::ContentTypeDetector will attempt to match a file's
extension to an inferred content_type, regardless of the actual
contents of the file.

Uploaded images appear to save, then disappear later

I'm using Rails 4 to build a blog. Each blog post has an image, title and text. I can upload an image, see that the image is there when I look at the posts/:id page, but later when I go back to that same page the image is gone. I'm using the Paperclip gem for rails 4.
Is my image tied to a session somehow? Is it not really saving to the database? Here is a link to the deployed project with the image that doesn't show up: https://vinna.herokuapp.com/posts/1
I'm still learning, so any information is greatly appreciated!
Here is my controller:
class PostsController < ApplicationController
def index
#posts = Post.all
end
def new
#post = Post.new
end
def create
#post = Post.new(post_params)
if #post.save
redirect_to #post
else
render 'new'
end
end
def show
#post = Post.find(params[:id])
end
def edit
#post = Post.find(params[:id])
end
def update
#post = Post.find(params[:id])
if #post.update(post_params)
redirect_to #post
else
render 'edit'
end
end
def destroy
#post = Post.find(params[:id])
#post.destroy
redirect_to posts_path
end
private
def post_params
params.require(:post).permit(:image, :title, :text)
end
end
My model:
class Post < ActiveRecord::Base
has_many :comments
has_attached_file :image, styles: { small: "100x100", med: "200x200", large: "600x600"}
validates :title, presence: true,
length: { minimum: 2 }
validates :text, presence: true,
length: { minimum: 2 }
validates_attachment_presence :image
validates_attachment_size :image, :less_than => 5.megabytes
validates_attachment_content_type :image, :content_type => ['image/jpeg', 'image/png']
end
My migrations:
class CreatePosts < ActiveRecord::Migration
def change
create_table :posts do |t|
t.string :title
t.text :text
t.timestamps null: false
end
end
end
And adding paperclip:
class AddPaperclipToPost < ActiveRecord::Migration
def change
add_attachment :posts, :image
end
end
And part of my view from posts/:id
<p class="blog-photo_large"><%= link_to image_tag(#post.image.url(:large)), #post.image.url %></p>
This should work fine on single machine. However using heroku your app should be a 12-factor-app. In this case you shouldn't be using the Filesystem but an additional service for storing files. This is because the app code on heroku is distributed across multiple physical hardware instances and your never know which actual node will be responding to https://vinna.herokuapp.com/posts/1. So your first see the image on some particular node and then your are loadbalanced to some other which does not have it stored.
See point IV of The Twelve-Factor-App.

Conditional sideloaded data in ActiveRecord::Serializer(based on controller actions)

I don't have so much experience in rails and building an application.i was trying to building an API using ActiveModel::Serializers.
What is the best way to side load data conditionally for particular event action?
Do I have to do it by sending query params with each call or i can set includes: true only for specific action or any another suggestion?
class EventsController < ApplicationController
before_filter :authenticate_user!, :except => [:index, :show]
before_filter :locate_collection, :only => :index
skip_before_filter :verify_authenticity_token, :only => [:create, :index, :show]
before_action :set_event, only: [:show, :edit, :update, :destroy]
# GET /events
def index
if params.has_key?("mode") and params[:mode] == "owned"
own_event
else
# #events = Event.all
render json: #events
end
end
# GET /events/1
def show
render json: #event
end
# GET /events/new
def new
#event = Event.new
end
# GET /events/1/edit
def edit
end
# POST /events
# POST /events.json
def create
#event = Event.new(event_params)
#event.creator_user_id = current_user.id
if #event.save
render json: #event
else
render json: #event.errors, status: :unprocessable_entity
end
end
# PATCH/PUT /events/1
# PATCH/PUT /events/1.json
def update
if #event.update(event_params)
render json: #event
else
render json: #event.errors, status: :unprocessable_entity
end
end
# DELETE /events/1
# DELETE /events/1.json
def destroy
aa = #event.destroy
render json: aa
end
def own_event
#events = Event.where(creator_user_id: current_user.id)
if #events.count > 0
render json: #events
else
render json: []
end
# else
# render json: {error: 1, message: "Events not found."}, status: 404
# end
end
def locate_collection
if (params.has_key?("filter"))
#events = EventPolicy::Scope.new(current_user, Event).resolve(filtering_params)
# #event_orders = EventOrder.filter(filtering_params)
else
#events = policy_scope(Event)
end
end
private
# Use callbacks to share common setup or constraints between actions.
def set_event
#event = Event.find(params[:id])
end
# Never trust parameters from the scary internet, only allow the white list through.
def filtering_params
params.slice(:event_type_id)
end
end
**My Event serializer**
It includes data for multiple association listed below. i don't want to show all the data with event call.
class EventSerializer < ActiveModel::Serializer
attributes :id, :event_name, :event_start_date, :event_end_date, :creator_user_id, :event_proposal_id, :daily_start_time, :daily_start_time, :payment_category, :total_capacity, :contact_email, :description, :graced_by, :contact_details, :video_url, :demand_draft_instructions, :status, :cannonical_event_id, :website, :event_type_id, :additional_details, :event_start_time, :event_end_time, :committee_id
embed :ids
has_one :address, include: true
has_many :tickets, include: true
has_many :event_cost_estimations, include: true
has_many :event_seating_category_associations, include: true
has_many :event_awarenesses, include: true
has_one :pandal_detail, include: true
has_one :bhandara_detail, include: true
has_many :event_tax_type_associations, include: true
has_many :event_team_details, include: true
has_one :event_type, include: true
# has_many :event_registration_center_associations, include: true
has_many :registration_centers, include: true
# has_many :event_registrations, include: true
has_many :event_orders, include: true
has_one :venue_type
end
In event serializer i have includes :true for sideloaded data and I want to show sideloaded data(includes: true) of registration_centers and tickets only for index action.
What can I do for this?
Considering that you are using AMS version 0.8.x,
You can create a base_serializer.rb like this and extend the same.
class BaseSerializer < ActiveModel::Serializer
def include_associations!
if #options[:embed]
embed = #options[:embed].split(',').map!(&:to_sym)
embed.each do |assoc|
include! assoc if _associations.keys.include?(assoc)
end
end
end
end
class EventSerializer < BaseSerializer
and the index method in EventsController can be written as
# GET /events?embed=registration_centers,tickets
def index
if params.has_key?("mode") and params[:mode] == "owned"
own_event
else
# #events = Event.all
render json: #events, embed: params[:embed]
end
end
I just showed you that, the associations that are needed, can be added in url params. You can think of some smart way to send the params such that you don't need to add them in the url request.

Resources