Hi I'm building a json api on rails. I have a model class Product that has a image associated with it.
Here's my Products Controller
def create
params[:product][:image] = parse_image_data(params[:product][:image]) if params[:product][:image]
product = Product.new(product_params)
product.image = params[:product][:image]
if product.save
render json: product, status: 201
else
render json: {error: product.errors}, status: 422
end
end
private
def product_params
params.require(:product).permit(:title,:description,:published,:product_type, :user_id, :image, address_attributes:[:address, :city_id])
end
def parse_image_data(image_data)
#tempfile = Tempfile.new('item_image')
#tempfile.binmode
#tempfile.write Base64.decode64(image_data[:content])
#tempfile.rewind
uploaded_file = ActionDispatch::Http::UploadedFile.new(
tempfile: #tempfile,
filename: image_data[:filename]
)
uploaded_file.content_type = image_data[:content_type]
uploaded_file
end
def clean_tempfile
if #tempfile
#tempfile.close
#tempfile.unlink
end
end
The Product Model :
class Product < ActiveRecord::Base
belongs_to :user
has_one :address
has_attached_file :image, styles: { thumb: ["64x64#", :jpg],
original: ['500x500>', :jpg] },
convert_options: { thumb: "-quality 75 -strip",
original: "-quality 85 -strip" }
validates :title, :description, :user_id, presence: true
validates :product_type, numericality:{:greater_than => 0, :less_than_or_equal_to => 2}, presence: true
accepts_nested_attributes_for :address
validates_attachment :image,
content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] },
size: { in: 0..500.kilobytes }
end
I'm sending the post request using postman with the following request payload :
{
"product": {
"title":"Lumial 940",
"description": "A black coloured phone found in Hudda Metro Station",
"published": "true",
"product_type":"2",
"user_id":"1",
"address_attributes":{
"address": "Flat 16, Sharan Apartments, South City 1",
"city_id": "2"
},
"image":{
"filename": "minka.jpg",
"content": "BASE64 STRING",
"content_type": "image/jpeg"
}
}
}
But while creating, the Product is being created with the image properties but the image_path attribute is saved as null in the database. I can see the image being written in the public directory under my rails application. How can I get to save the image_path as well?
Can someone help me how to solve this?
Related
I have a custom processor for my Paperclip styles: cropper.rb. Though it is not called and return NameError (uninitialized constant Paperclip::Cropper) error.
It has been discussed here : Rails3 and Paperclip but a while ago. It was concerning Rails 3 back then.
I am under Rails 5 (update from Rails 4.x)
Profilepic.rb
class Profilepic < ApplicationRecord
belongs_to :professionnels
has_attached_file :image, styles: { big: "1200x1200", medium: "400x400", small: "250x250"}
validates_attachment :image, content_type: { content_type: ["image/jpeg", "image/gif", "image/png"] }, size: {less_than: 10.megabytes}
has_attached_file :finalimage, styles: { medium: "500x500", small: "200x200"}, whiny: false, use_timestamp: false, processors: [:cropper]
attr_accessor :crop_x, :crop_y, :crop_w, :crop_h
end
lib/paperclip_processors/cropper.rb
module Paperclip
class CustomCropper < Thumbnail
def initialize(file, options = {}, attachment = nil)
super
if target.crop_w && target.crop_x
#current_geometry.width = target.crop_w
#current_geometry.height = target.crop_h
end
end
def target
#attachment.instance
end
def transformation_command
# call crop processing only if user inputs are there
if target.crop_w && target.crop_x
crop_command = [
"-crop",
"#{target.crop_w}x" \
"#{target.crop_h}+" \
"#{target.crop_x}+" \
"#{target.crop_y}",
"+repage"
]
crop_command + super
else
super
end
end
end
end
OK spent a day to realize the correct lib subfolder is actually paperclip and not paperclip_processors although Paperclip Git does mention both as valid and automatically loaded.
I am trying to implement the steps to check and resize images with paperclip based on this blog post: http://www.techdarkside.com/how-to-re-size-images-that-are-too-large-on-the-fly-with-paperclip-and-rails
Here is what I have in place...
class Question < ActiveRecord::Base
# subclasses
class Question::Image < Asset
has_attached_file :attachment,
:url => "/uploads/:class/:attachment/:id_partition/:basename_:style.:extension",
:styles => Proc.new { |attachment| attachment.instance.styles },
:styles => Proc.new { |attachment| attachment.instance.resize }
attr_accessible :attachment
# http://www.ryanalynporter.com/2012/06/07/resizing-thumbnails-on-demand-with-paperclip-and-rails/
def dynamic_style_format_symbol
URI.escape(#dynamic_style_format).to_sym
end
def styles
unless #dynamic_style_format.blank?
{ dynamic_style_format_symbol => #dynamic_style_format }
else
{ :medium => "300x300>", :thumb => "100x100>" }
end
end
def dynamic_attachment_url(format)
#dynamic_style_format = format
attachment.reprocess!(dynamic_style_format_symbol) unless attachment.exists?(dynamic_style_format_symbol)
attachment.url(dynamic_style_format_symbol)
end
def resize
if self.attachment_file_size > 2000000
"300x300>"
else
" "
end
end
end
I'm thinking the issue is with the reuse of the :styles symbol, however I'm not sure how to work both the styles method AND the resize method into a single Proc statement.
Here is what I ended up with thanks to #janfoeh suggestion. I did need to add :originalto the options in style to get this to work. I also bumped the max file size up to 5mb.
class Question < ActiveRecord::Base
# subclasses
class Question::Image < Asset
has_attached_file :attachment,
:url => "/uploads/:class/:attachment/:id_partition/:basename_:style.:extension",
:styles => Proc.new { |attachment| attachment.instance.styles }
attr_accessible :attachment
# http://www.ryanalynporter.com/2012/06/07/resizing-thumbnails-on-demand-with-paperclip-and-rails/
def dynamic_style_format_symbol
URI.escape(#dynamic_style_format).to_sym
end
def styles
unless #dynamic_style_format.blank?
{ dynamic_style_format_symbol => #dynamic_style_format }
else
{ :original => resize, :medium => "300x300>", :thumb => "100x100>" }
end
end
def dynamic_attachment_url(format)
#dynamic_style_format = format
attachment.reprocess!(dynamic_style_format_symbol) unless attachment.exists?(dynamic_style_format_symbol)
attachment.url(dynamic_style_format_symbol)
end
def resize
if self.attachment_file_size > 5000000
"1000x1000>"
else
" "
end
end
end
I've been stuck on this issue for a couple of days and seem to have run into a wall. I am using rails 4.1, angular 1.2.23, and paperclip 4.1. I sampled from many of the solution for similar problems but none seem to have resolved the issue.
When I set up the paperclip attachments inside of the doc model, I was able to upload a single attachment. However, when I separated the attachments from the doc model and added the images model for multiple attachments, I couldn't get it to work.
Here is the code I am using:
doc.rb
class Doc < ActiveRecord::Base
has_many :image, :dependent => :destroy
accepts_nested_attributes_for :image, :reject_if => lambda { |a| a[:content].blank? }, :allow_destroy => true
end
image.rb
class Image < ActiveRecord::Base
belongs_to :doc
has_attached_file :image, :styles => { :lrg => "700x700>", :med => "350x350>", :sml => "100x100>" }, :whiny => false,
:path => ":rails_root/public/system/:attachment/:id/:style/:filename",
:url => "/system/:attachment/:id/:style/:filename"
validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/
def image=(files = [])
files.each{|f| (#image ||= []) << image.create(image: f) }
end
end
Here is my controller:
class DocsController < ApplicationController
skip_before_filter :verify_authenticity_token
def index
#docs = if params[:keywords]
Doc.where("title ilike ?", "%#{params[:keywords]}%")
else
[]
end
#items = Doc.all
end
def show
#doc = Doc.find(params[:id])
end
def create
if params[:imageData]
decode_image
#doc = Doc.new(#up)
else
#doc = Doc.new(params.require(:doc).permit(:id, :title, :parent, :info))
end
if #doc.save
render 'show', status: 201
else
render json: #doc.errors, status: :unprocessable_entity
Rails.logger.info #doc.errors
end
end
def update
doc = Doc.find(params[:id])
if params[:imageData]
decode_image
doc.update_attributes(#up)
else
doc.update_attributes(params.require(:doc).permit(:id, :title, :parent, :info))
end
head :no_content
end
def destroy
doc = Doc.find(params[:id])
doc.destroy
head :no_content
end
private
def doc_params
#up = params.require(:doc).permit(:id, :title, :parent, :info, image_attributes: [:_destroy, :id, :image])
end
def decode_image
#up = params.require(:doc).permit(:id, :title, :parent, :info, image_attributes: [:_destroy, :id, :image])
# decode base64 string
Rails.logger.info 'decoding now'
decoded_data = Base64.decode64(params[:imageData]) # json parameter set in directive scope
# create 'file' understandable by Paperclip
#data = StringIO.new(decoded_data)
#data.class_eval do
attr_accessor :content_type, :original_filename
end
# set file properties
#data.content_type = params[:imageContent] # json parameter set in directive scope
#data.original_filename = params[:imagePath] # json parameter set in directive scope
# update hash, I had to set #up to persist the hash so I can pass it for saving
# since set_params returns a new hash everytime it is called (and must be used to explicitly list which params are allowed otherwise it throws an exception)
#up[image_attributes: [:image]] = #data # image is the model attribute that is defined as an attachment using paperclip generator
end
end
Here is my upload directive using angular:
angular.module('fileUpload', []) // using restangular is optional
.directive('uploadImage', function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
// listens on change event
elem.on('change', function() {
console.log('entered change function');
for (var i = 0; i < elem.length; i++) {
for (var x = 0; x < elem[i].files.length; x++) {
var file = elem[i].files[x];
// gathers file data (filename and type) to send in json
scope.doc.imageContent = file.type;
scope.doc.imagePath = file.name;
console.log(scope.doc.imagePath);
// converts file to binary string
var reader = new FileReader();
reader.readAsBinaryString(file);
reader.onload = function (e) {
// retrieves the image data from the reader.readAsBinaryString method and stores as data
// calls the uploadImage method, which does a post or put request to server
scope.doc.imageData = btoa(e.target.result);
// updates scope
}
scope.uploadImage(scope.doc.imagePath);
scope.$apply();
}
};
});
},
controller: ['$scope', '$location', '$resource', function($scope, $location, $resource){
$scope.uploadImage = function (path) {
var Doc;
Doc = $resource('/docs/:docId', {
docId: "#id",
format: 'json'
}, {
'save': {
method: 'PUT'
},
'create': {
method: 'POST'
}
});
// if updating doc
console.log($scope.doc);
var onError;
onError = function(_httpResponse) {
return flash.error = "Something went wrong";
};
if ($scope.doc.id) {
$scope.doc.$save((function() {
$scope.docImageLink = baseUrl + $scope.doc.image_url;
}), onError);
} else {
console.log("create");
}
};
}]
};
});
And finally, here is my form field:
<input type="file" id="doc.image" name="doc.image" ng-model='doc.image' upload-image multiple/>
<img ng-src="{{userImageLink}}" ng-click="openFileWindow()" ng-class="{ hidden: !userImageLink}" >
<div class="drop-box" ng-click="openFileWindow()" ng-class=" {hidden: userImageLink}">
Add Image
</div>
When I submit the form with multiple attachments, I receive this error in the rails log:
ActiveRecord::UnknownAttributeError (unknown attribute: {:image_attributes=>[:image]}):
app/controllers/docs_controller.rb:21:in `create'
This is my first major rails app so any help would be significantly appreciated. I can also provide any follow up information that is required.
Thanks!
I have this models in ruby on rails
Branch model: has_many :menus
class Branch < ActiveRecord::Base
belongs_to :place
belongs_to :city
has_many :menus , dependent: :destroy
belongs_to :type_place
end
Menu model: has_many :products
class Menu < ActiveRecord::Base
attr_accessible :description, :product_name, :price, :category_id, :menu_id
belongs_to :branch
has_many :products, dependent: :destroy
end
Product model:
class Product < ActiveRecord::Base
belongs_to :menu
belongs_to :category
end
with the following code in the view:
if #condition
json.code :success
json.branch do
json.array!(#branches) do |json, branch|
json.(branch, :id, :branch_name, :barcode)
json.menu branch.menus, :id, :menu_name
end
end
else
json.code :error
json.message 'Mensaje de error'
end
gets:
{
"code": "success",
"branch": [
{
"id": 1,
"branch_name": "Sucursal 1",
"barcode": "zPOuByzEFe",
"menu": [
{
"id": 2,
"menu_name": "carta sucursal 1"
}
]
},
{
"id": 2,
"branch_name": "Sucursal Viña Centro",
"barcode": "RlwXjAVtfx",
"menu": [
{
"id": 1,
"menu_name": "carta viña centro"
},
{
"id": 5,
"menu_name": "carta viña centro vinos"
}
]
},
{
"id": 3,
"branch_name": "dddd",
"barcode": "eSbJqLbsyP",
"menu": [
]
}
]
}
But as I get the products of each menu?, I suspect I need to iterate menu, but I have tried several ways without success.
I'm not sure which attributes your product can have but i would try something like:
if #condition
json.code :success
json.array!(#branches) do |json, branch|
json.(branch, :id, :branch_name, :barcode)
json.menus branch.menus do |json,menue|
json.id menue.id
json.menu_name menue.menu_name
json.products menue.products do |json, product|
json.product_attribute_1 product.product_attribute_1
end
end
end
else
json.code :error
json.message 'Mensaje de error'
end
i'm also not quite sure why you try to nest #branches under a branch as stated by:
json.branch do
...
end
i just removed that.
This is from the docs ("this" is the "json.array! method) http://www.rubydoc.info/github/rails/jbuilder/Jbuilder:array!
It's generally only needed to use this method for top-level arrays. If
you have named arrays, you can do:
json.people(#people) do |person|
json.name person.name
json.age calculate_age(person.birthday)
end
{ "people": [ { "name": David", "age": 32 }, { "name": Jamie", "age": 31 } ] }
I had unexpected behaviors using array! and regular iteration as suggested worked perfectly and made my code very readable:
json.user do
json.email #user.email
json.devices #user.devices do |device|
json.partial! 'devices/device', device: device
end
end
Here is my validation:
describe User
it { should validate_attachment_size(:avatar).less_than(20.megabytes) }
end
Here is my User model:
class User < ActiveRecord::Base
validates_attachment :avatar, :content_type => { :content_type => /image/ }, size: { in: 0..20.megabytes }, allow_blank: true
end
The error I keep getting is:
Failure/Error: it { should validate_attachment_size(:avatar).less_than(20.megabytes) }
Attachment avatar must be between and 20971520 bytes
Not sure why this is failing. Any help is greatly appreciated!