Adding tags to images in RefineryCMS - ruby-on-rails

I'm trying to add tags to the Image model in RefineryCMS (trying on 1.0.8 and 2.0.4), have added attr_accessible :tag_list, required acts-as-taggable and setup the views, but the problem is that the tags only save when editing/updating a previously uploaded image - not when uploading for first time, even though it uses the same form...
Any ideas?
It happens on every version of rails and Refinery I have tried...
The tags are going through in the post when looking at logs, just not saving...

I had a similar issue and eventually find the cause of the extra-attributes (in your case the :tag_list) not being saved on new image upload.
If you look at ::Refinery::ImageController you'll see that the create action actyally create the image with :
unless params[:image].present? and params[:image][:image].is_a?(Array)
#images << (#image = ::Refinery::Image.create(params[:image]))
else
params[:image][:image].each do |image|
#images << (#image = ::Refinery::Image.create(:image => image))
end
end
params[:image][:image] is an Array when multiple multiple file uploed is enabled (by default it is). But then the action only use take the array values when creating the images, ignoring the other params.
I quickly write the below work-around that allow to save the other params on multiple image upload :
unless params[:image].present? and params[:image][:image].is_a?(Array)
#images << (#image = ::Refinery::Image.create(params[:image]))
else
images_params = params[:image].dup
images_params.delete(:image)
params[:image][:image].each do |image|
#images << (#image = ::Refinery::Image.create({:image => image}.merge(images_params)))
end
end
It's probably not the most elegant solution bu it does the trick.
To use it in your app, you'll have to create a decorator for the ::Refinery::ImageController to copy and edit the create action in it. (see 'Extending a Controller' in Refinery's Guides)

Related

Fill up and update an uploaded PDF form online and save it back to the server - Ruby on Rails

Here is the requirement:
In my web-app developed in Ruby on Rails, we require to have an option to upload a PDF form template to the system, take it back in the browser itself and the user should be able to fill up the PDF form online and finally save it back to the server.
Then, user will come and download the updated PDF form from the application. I've searched a lot but couldn't find a proper solution to it. Please suggest.
As I stated for prebuilt PDF's with form fields already embedded I use pdtk Available Here and the active_pdftk gem Available Here. This is the standard process I use but yours may differ:
class Form
def populate(obj)
#Stream the PDF form into a TempFile in the tmp directory
template = stream
#turn the streamed file into a pdftk Form
#pdftk_path should be the path to the executable for pdftk
populated_form = ActivePdftk::Form.new(template,path: pdftk_path)
#This will generate the form_data Hash based on the fields in the form
#each form field is specified as a method with or without arguments
#fields with arguments are specified as method_name*args for splitting purposes
form_data = populated_form.fields.each_with_object({}) do |field,obj|
meth,args = field.name.split("*")
#set the Hash key to the value of the method with or without args
obj[field.name] = args ? obj.send(meth,args) : obj.send(meth)
end
fill(template,form_data)
end
private
def fdf(waiver_data,path)
#fdf ||= ActivePdftk::Fdf.new(waiver_data)
#fdf.save_to path
end
def fill(template,waiver_data)
rand_path = generate_tmp_file('.fdf')
initialize_pdftk.fill_form(template,
fdf(waiver_data,rand_path),
output:"#{rand_path.gsub(/fdf/,'pdf')}",
options:{flatten:true})
end
def initialize_pdftk
#pdftk ||= ActivePdftk::Wrapper.new(:path =>pdftk_path)
end
end
Basically what this does is it streams the form to a tempfile. Then it converts it to a ActivePdftk::Form. Then it reads all the fields and builds a Hash of field_name => value structure. From this it generates an fdf file and uses that to populate the actual PDF file and then outputs it to another tempfile flattened to remove the fields from the final result.
Your use case might differ but hopefully this example will be useful in helping you achieve your goal. I have not included every method used as I am assuming you know how to do things like read a file. Also my forms require a bit more dynamics like methods with arguments. Obviously if you are just filling in raw fixed data this portion could be changed a bit too.
An example of usage given your class is called Form and you have some other object to fill the form with.
class SomeController < ApplicationController
def download_form
#form = Form.find(params[:form_id])
#object = MyObject.find(params[:my_object_id])
send_file(#form.populate(#object), type: :pdf, layout:false, disposition: 'attachment')
end
end
This example will take #form and populate it from #object then present it to the end user as a filled and flattened PDF. If you just needed to save it back into the database I am sure you can figure this out using an uploader of some kind.

How do I ask Paperclip if I'm uploading a new file?

I want to do some processing on an image the first time it's uploaded. The model can be updated without updating the image and I want to avoid extra processing in those situations. How can I write a check to only process the image if the image is new, like being replaced?
Something like:
def update
#model = Model.find(params[:id])
#model.process_image if #model.image_is_different_from_existing?
...
end
There's one way if the photos you are uploading have unique name, Let's say paperclip attribute name is image, paperclip creates a column named image_file_name, before saving you can check whether the image file name present in your database matches with the name of image file you are uploading or not. I've implemented something like this, below is the snippet
product_images = product.product_attachments.pluck(:photo_file_name)
unless product_images.include?("name_of_the_image_i_am_uploading")
product.product_attachments.create!(:photo => File.open("/home/rajdeepb/Photo/#{data_array[1]}")) if all_images.include?("/home/rajdeepb/Photo/#{name_of_the_image_i_am_uploading}")
end
THis might not be the perfect solution but it may be helpful to you
I've written a solution that looks for relevant params in the update or create method.
Something like this:
user.rb
def attachments
%w(banner footer logo)
end
users_controller.rb
def new_resources?
attaching = []
#user.attachments.each do |a|
attaching << a if params[:user][a].present?
end
return true unless attaching.empty?
end
def attaching
[]
end
def update
...
if #user.request_necessary? && new_resources?
action_response << "Server updated: new #{attaching} uploaded."
redirect_to users_path, notice: action_response.join(' ')

Why won't Carrierwave and MiniMagick images composite correctly?

I am using carrierwave (0.9.0) with fog (1.18.0), mini_magick (3.6.0), and Rackspace CloudFiles. I want to composite images.
I have two models: Products and Emails. In the products uploader I am trying to create a process that loops through all the emails and composites the product image on top of each email.background_image. I also want to control the name of this image so that is:
"#{email.name}_#{product.product_number}.jpg"
I have included my process and method below. Test result in a file being uploaded to cloudfiles named "email_123.png" and the original image. The name doesn't currently include the email's name and images are not compositing, I just receive the original back. Anything obvious I am missing here? Thanks!
version :email do
process :email_composite
def email_composite
Email.all.each do |email|
email_image = email.secondary.url
email_name = email.name
merge(email_name, email_image)
end
end
def merge(email_name, email_image)
manipulate! do |img|
#product = Product.find(model.id)
email_image = ::MiniMagick::Image.open(email_image)
img = email_image.composite(img, "jpg") do |c|
c.gravity "Center"
end
# img = yield(img) if block_given?
img = img.name "#{email_name}_#{#product.product_number}.png"
img
end
end
end
UPDATED
First I wanted to thank you for your thorough answer, the amount of effort involved is obvious and appreciated.
I had a few more questions when trying to implement this solution.
Inside the #compose method you have:
email_image = ::MiniMagick::Image.open(email_image)
Should this be:
email_image = ::MiniMagick::Image.open(email_image_url)
I assume this from seeing the define_method call included below ( which was awesome by the way - I didn't know you could do that):
define_method(:email_image_url) { email.secondary.url }
After getting Undefined method "versions_for" I switched the #versions_for method as I will always want all email to have a secondary version.
versions_for before
def self.versions_for emails
reset_versions!
email.find_each { |e| version_for_email(e) }
end
versions_for after changes
def self.versions_for
reset_versions!
Email.all.find_each { |e| version_for_email(e) }
end
This change allowed me get rid of the error.
The end result of all of this is that everything appears as if it works, I can upload imagery without error, but now I have no resulting image for the emails versions. I still get my other versions like thumb etc. Any idea what could cause this? Again thanks for all the help you have provided thus far.
Calling manipulate! multiple times within the same version applies the changes in its block to the same image; what you want is to create many versions, one corresponding to each Email. This is tricky because CarrierWave really wants you to have a small set of statically defined versions in your code; it actually builds a new, anonymous Uploader class to handle each version.
We can trick it to build versions dynamically, but it's pretty ugly. Also, we have to be careful to not keep references to stale uploader classes around, or we'll accumulate classes endlessly and eventually run out of memory!
# in ProductUploader:
# CarrierWave stores its version classes in two places:
#
# 1. In a #versions hash, stored within the class; and
# 2. As a constant in your uploader, with a name based on its #object_id.
#
# We have to clean out both of them to be sure old versions get garbage
# collected!
def self.reset_versions!
versions.keys.select { |k| k =~ /^email_/ }.each do |k|
u = versions.delete(k)[:uploader]
remove_const("Uploader#{u.object_id}".gsub('-', '_'))
end
end
def self.version_name_for email
"email_#{email.name}".to_sym
end
# Dynamically generate the +version+ that corresponds to the image composed
# with the image from +email+.
def self.version_for_email email
version(version_name_for(email)) do
process :compose
# Use +define_method+ here so that +email+ in the block refers to the
# argument "email" from the outer scope.
define_method(:email_image_url) { email.secondary.url }
# Use the same trick to override the filename for this version.
define_method(:filename) { "#{email_name}_#{model.product_number}.png" }
# Compose +email_image+ on top of the product image.
def compose
manipulate! do |img|
email_image = ::MiniMagick::Image.open(email_image_url)
# Do the actual composition.
img = email_image.composite(img, "jpg") do |c|
c.gravity "Center"
end
img
end
end
end
end
# Clear out any preexisting versions and generate new ones based on the
# contents of +emails+.
def self.versions_for emails
reset_versions!
email.find_each { |e| version_for_email(e) }
end
# After you call Product.versions_for(emails), you can use this to fetch
# the version for a specific email:
def version_for_email email
versions[self.class.version_name_for(email)]
end
To use this, be sure to call Product.versions_for(Email.all) (possibly in a before_filter), Then, you can access the version for a specific email with #p.product.version_for(email):
# in your controller:
def action
# Recreate a +version+ for each email.
# If you don't want to overlay *every* email's image, you can also pass a
# subset here.
ProductUploader.versions_for(Email.all)
# Upload a file and its versions to Cloud Files...
#p = Product.find(params[:id])
#p.product = File.open(Rails.root.join('clouds.jpg'), 'r')
#p.save!
end
In your views, you can use url or any other helpers as usual:
# in a view:
<% Email.all.find_each do |email| %>
<%= image_tag #p.product.version_for_email(email).url %>
<% end %>

Having issue renaming tags with ActsAsTaggableOn

Here's my method:
def rename
old_tag = params[:old_tag]
new_tag = params[:new_tag]
if old_tag != new_tag
# find any articles that use the old tag
Article.tagged_with(old_tag).each do |article|
# give articles with the old tag the new tag
article.tag_list.add(new_tag)
# remove the old tag
article.tag_list.remove(old_tag)
article.save
end
end
render :json => "#{old_tag} renamed to #{new_tag}"
end
The problem I have is that .save is adding a new tag to the articles but it isn't removing the old tag.
The problem I was facing is that updating tags will initiate validation for all the related models, and in my case the validation was failing due to images not being available on my development machine and attachment validation was failing.
To ignore validation I do:
article.save(:validate => false)

How can I link to next or previous record belonging to an object in Rails 3?

So I have have an image(tattoo) sharing site with 3 different ways of indexing images. The main page that just shows Image.all, member's images which shows #member.images.all, and member's album images. That is to say, the a Member has many images (and albums), and an Album has many images.
Now when you click on an image, regardless of where it was (index, member's profile, or member's album) you get to the show page. Im thinking I probably need a previous and next link to show the next image but I need to be able to restrict the images to their parent container.
So if Im looking at an image within an album, I would need the next and previous links to only link to images in that album. If Im looking at a member's images NOT in an album it the links would point to images the member owns, not in an album.
And if the image was in the front page, then the links would just point to ALL images.
If you don't want pagination, then you'd have to do additional queries to ensure the links to the next/previous records are accurate. Example shown below...
In your image model:
class Image < ActiveRecord::Base
belongs_to :album
def previous
Image.where("album_id = ? AND id < ?", self.album.id, self.id).first
end
def next
Image.where("album_id = ? AND id > ?", self.album.id, self.id).first
end
end
Then in your controller:
def show
#current_image = Image.find(params[:id])
#prev = #current_image.previous
#next = #current_image.next
respond_to do |format|
format.html
end
end
And finally in your view (this is HAML code):
= link_to_unless(#prev.blank?, 'Prev', image_path(#prev))
= link_to_unless(#next.blank?, 'Next', image_path(#next))
Just keep in mind that previous and next could return nil if no previous or next image is found so you should not show the prev / next links in that scenario.
If you end up using the will_paginate or Kaminari gem (highly recommended), you can generate "pretty" routes by just adding a matched route to your routes.rb file:
match '/albums/:id/page/:page' => "albums#show"
# put the matched route above the regular :albums resource
# so it matches that route first if it's given a page
resources :albums
The reason that works is because both pagination gems use Rails url_for method to generate the previous/next links and therefore Rails will match that route and generate something like http://rtattoos.com/albums/2/page/26 instead of http://rtattoos.com/albums/2?page=26
This is a typical pagination type problem. Each display of records always comes with some kind of conditions (even if it is find(:all)). Take a look at will_paginate for easy creation of the view and collection objects. The only additional work is always providing the conditions back on a page change to reissue model request with the next set.
If in the show page you have #photo = Photo.find(params[:id]) You could try passing the id of the current photo to either a nextPhoto or a previousPhoto method. The nextPhoto method would then have #photo = Photo.find(id + 1) and previousPhoto have #photo = Photo.find(id -1). I'm not sure if this syntax works but something along these lines seems to be what you are looking for.

Resources