Duplicate documentable code and rename for imageable

This commit is contained in:
Senén Rodero Rodríguez
2017-09-12 18:12:49 +02:00
parent 614ff79ba1
commit 6f71da07ee
59 changed files with 1362 additions and 520 deletions

View File

@@ -63,6 +63,7 @@
//= require followable //= require followable
//= require flaggable //= require flaggable
//= require documentable //= require documentable
//= require imageable
//= require tree_navigator //= require tree_navigator
//= require custom //= require custom
//= require tag_autocomplete //= require tag_autocomplete
@@ -100,6 +101,7 @@ var initialize_modules = function() {
App.WatchFormChanges.initialize(); App.WatchFormChanges.initialize();
App.TreeNavigator.initialize(); App.TreeNavigator.initialize();
App.Documentable.initialize(); App.Documentable.initialize();
App.Imageable.initialize();
App.TagAutocomplete.initialize(); App.TagAutocomplete.initialize();
}; };

View File

@@ -0,0 +1,101 @@
App.Imageable =
initialize: ->
@initializeDirectUploads()
@initializeInterface()
initializeDirectUploads: ->
$('input.image_ajax_attachment[type=file]').fileupload
paramName: "image[attachment]"
formData: null
add: (e, data) ->
wrapper = $(e.target).closest('.image')
index = $(e.target).data('index')
is_nested_image = $(e.target).data('nested-image')
$(wrapper).find('.progress-bar-placeholder').empty()
data.progressBar = $(wrapper).find('.progress-bar-placeholder').html('<div class="progress-bar"><div class="loading-bar uploading"></div></div>')
$(wrapper).find('.progress-bar-placeholder').css('display','block')
data.formData = {
"image[title]": $(wrapper).find('input.image-title').val() || data.files[0].name
"index": index,
"nested_image": is_nested_image
}
data.submit()
change: (e, data) ->
wrapper = $(e.target).parent()
$.each(data.files, (index, file)->
$(wrapper).find('.file-name').text(file.name)
)
progress: (e, data) ->
progress = parseInt(data.loaded / data.total * 100, 10)
$(data.progressBar).find('.loading-bar').css 'width', progress + '%'
return
initializeInterface: ->
input_files = $('input.image_ajax_attachment[type=file]')
$.each input_files, (index, file) ->
wrapper = $(file).parent()
App.Imageable.watchRemoveImagebutton(wrapper)
watchRemoveImagebutton: (wrapper) ->
remove_image_button = $(wrapper).find('.remove-image')
$(remove_image_button).on 'click', (e) ->
e.preventDefault()
$(wrapper).remove()
$('#new_image_link').show()
$('.max-images-notice').hide()
uploadNestedImage: (id, nested_image, result) ->
$('#' + id).replaceWith(nested_image)
@updateLoadingBar(id, result)
@initialize()
uploadPlainImage: (id, nested_image, result) ->
$('#' + id).replaceWith(nested_image)
@updateLoadingBar(id, result)
@initialize()
updateLoadingBar: (id, result) ->
if result
$('#' + id).find('.loading-bar').addClass 'complete'
else
$('#' + id).find('.loading-bar').addClass 'errors'
$('#' + id).find('.progress-bar-placeholder').css('display','block')
new: (nested_fields) ->
$(".images-list").append(nested_fields)
@initialize()
destroyNestedImage: (id, notice) ->
$('#' + id).remove()
@updateNotice(notice)
replacePlainImage: (id, notice, plain_image) ->
$('#' + id).replaceWith(plain_image)
@updateNotice(notice)
@initialize()
updateNotice: (notice) ->
if $('[data-alert]').length > 0
$('[data-alert]').replaceWith(notice)
else
$("body").append(notice)
updateNewImageButton: (link) ->
if $('.image').length >= $('.images').data('max-images')
$('#new_image_link').hide()
$('.max-images-notice').removeClass('hide')
$('.max-images-notice').show()
else if $('#new_image_link').length > 0
$('#new_image_link').replaceWith(link)
$('.max-images-notice').hide()
else
$('.max-images-notice').hide()
$(link).insertBefore('.images hr:last')

View File

@@ -19,3 +19,4 @@
@import 'jquery-ui/autocomplete'; @import 'jquery-ui/autocomplete';
@import 'autocomplete_overrides'; @import 'autocomplete_overrides';
@import 'documentable'; @import 'documentable';
@import 'imageable';

View File

@@ -197,7 +197,7 @@
content: '\53'; content: '\53';
} }
.icon-budget-investment-image::before { .icon-image::before {
content: '\68'; content: '\68';
} }

View File

@@ -0,0 +1,64 @@
.progress-bar-placeholder {
display: none;
}
.image-form {
.image .file-name {
margin-top: 0;
}
.progress-bar-placeholder {
margin-bottom: 15px;
}
.image .loading-bar.errors {
margin-top: $line-height * 2;
}
}
.cached-image{
max-width: 150px;
max-height: 150px;
}
.image {
.button {
font-weight: normal;
}
.progress-bar {
width: 100%;
background-color: $light-gray;
}
input.image_ajax_attachment[type=file]{
display: none;
}
.file-name {
margin-top: 0px;
}
.loading-bar {
height: 5px;
width: 0;
transition: width 500ms ease-out;
&.uploading {
background-color: $dark-gray;
}
&.complete {
background-color: $success-color;
width: 100%;
}
&.errors {
background-color: $alert-color;
width: 100%;
margin-top: $line-height / 2;
}
}
.loading-bar.no-transition {
transition: none;
}
}

View File

@@ -2282,3 +2282,59 @@ table {
} }
} }
// 19. Images
.image-form form {
.source-option-link {
input {
padding-bottom: 0;
}
.error {
margin-bottom: $line-height;
}
label {
&.error {
margin-bottom: 0;
}
}
}
.source-option-file {
.file-name {
label {
@include breakpoint(small medium) {
float: none;
}
@include breakpoint(large) {
float: left;
}
}
p {
@include breakpoint(small medium) {
float: none;
margin-top: 0;
margin-left: 0;
margin-bottom: 0;
}
@include breakpoint(large) {
float: left;
margin-bottom: 0;
margin-top: $line-height / 2;
margin-left: $line-height;
}
}
}
}
.attachment-errors {
margin-bottom: $line-height;
}
}

View File

@@ -252,13 +252,13 @@
.document-form, .document-form,
.topic-new, .topic-new,
.topic-form, .topic-form,
.budget-investment-image-form { .image-form {
.icon-debates, .icon-debates,
.icon-proposals, .icon-proposals,
.icon-budget, .icon-budget,
.icon-documents, .icon-documents,
.icon-budget-investment-image { .icon-image {
font-size: rem-calc(50); font-size: rem-calc(50);
line-height: $line-height; line-height: $line-height;
opacity: 0.5; opacity: 0.5;
@@ -277,7 +277,7 @@
color: $budget; color: $budget;
} }
.icon-budget-investment-image { .icon-image {
color: $budget-investment-image; color: $budget-investment-image;
} }
} }
@@ -315,7 +315,7 @@
} }
} }
.budget-investment-image-form { .image-form {
.recommendations li::before { .recommendations li::before {
color: $budget-investment-image; color: $budget-investment-image;
@@ -955,7 +955,7 @@
} }
} }
.budget-investment-image-form { .image-form {
@include image-upload; @include image-upload;

View File

@@ -45,11 +45,13 @@ module Budgets
load_investment_votes(@investment) load_investment_votes(@investment)
@investment_ids = [@investment.id] @investment_ids = [@investment.id]
@document = Document.new(documentable: @investment) @document = Document.new(documentable: @investment)
@image = Image.new(imageable: @investment)
end end
def create def create
@investment.author = current_user @investment.author = current_user
recover_documents_from_cache(@investment) recover_documents_from_cache(@investment)
recover_image_from_cache(@investment)
if @investment.save if @investment.save
Mailer.budget_investment_created(@investment).deliver_later Mailer.budget_investment_created(@investment).deliver_later
@@ -80,24 +82,6 @@ module Budgets
super super
end end
def edit_image
end
def update_image
if @investment.update(investment_params)
redirect_to budget_investment_path(@investment.budget, @investment),
notice: t("flash.actions.update_image.budget_investment")
else
render :edit_image
end
end
def remove_image
@investment.image.destroy!
redirect_to budget_investment_path(@investment.budget, @investment),
notice: t("flash.actions.remove_image.budget_investment")
end
private private
def resource_model def resource_model
@@ -124,9 +108,9 @@ module Budgets
def investment_params def investment_params
params.require(:budget_investment) params.require(:budget_investment)
.permit(:title, :description, :external_url, :heading_id, .permit(:title, :description, :external_url, :heading_id, :tag_list,
:tag_list, :organization_name, :location, :terms_of_service, :organization_name, :location, :terms_of_service,
image_attributes: [:title, :attachment], image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id],
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id]) documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id])
end end

View File

@@ -59,6 +59,7 @@ module CommentableActions
def update def update
resource.assign_attributes(strong_params) resource.assign_attributes(strong_params)
recover_documents_from_cache(resource) recover_documents_from_cache(resource)
recover_image_from_cache(resource)
if resource.save if resource.save
redirect_to resource, notice: t("flash.actions.update.#{resource_name.underscore}") redirect_to resource, notice: t("flash.actions.update.#{resource_name.underscore}")
@@ -119,4 +120,10 @@ module CommentableActions
end end
end end
def recover_image_from_cache(resource)
return false unless resource.try(:image)
resource.image.attachment = resource.image.set_attachment_from_cached_attachment if resource.image.cached_attachment.present?
end
end end

View File

@@ -0,0 +1,106 @@
class ImagesController < ApplicationController
before_action :authenticate_user!
before_filter :find_imageable, except: :destroy
before_filter :prepare_new_image, only: [:new, :new_nested]
before_filter :prepare_image_for_creation, only: :create
before_filter :find_image, only: :destroy
load_and_authorize_resource except: :upload
skip_authorization_check only: :upload
def new
end
def new_nested
end
def create
recover_attachments_from_cache
if @image.save
flash[:notice] = t "images.actions.create.notice"
redirect_to params[:from]
else
flash[:alert] = t "images.actions.create.alert"
render :new
end
end
def destroy
respond_to do |format|
format.html do
if @image.destroy
flash[:notice] = t "images.actions.destroy.notice"
else
flash[:alert] = t "images.actions.destroy.alert"
end
redirect_to params[:from]
end
format.js do
if @image.destroy
flash.now[:notice] = t "images.actions.destroy.notice"
else
flash.now[:alert] = t "images.actions.destroy.alert"
end
end
end
end
def destroy_upload
@image = Image.new(cached_attachment: params[:path])
@image.set_attachment_from_cached_attachment
@image.cached_attachment = nil
@image.imageable = @imageable
if @image.attachment.destroy
flash.now[:notice] = t "images.actions.destroy.notice"
else
flash.now[:alert] = t "images.actions.destroy.alert"
end
render :destroy
end
def upload
@image = Image.new(image_params.merge(user: current_user))
@image.imageable = @imageable
if @image.valid?
@image.attachment.save
@image.set_cached_attachment_from_attachment(URI(request.url))
else
@image.attachment.destroy
end
end
private
def image_params
params.require(:image).permit(:title, :imageable_type, :imageable_id,
:attachment, :cached_attachment, :user_id)
end
def find_imageable
@imageable = params[:imageable_type].constantize.find_or_initialize_by(id: params[:imageable_id])
end
def find_image
@image = Image.find(params[:id])
end
def prepare_new_image
@image = Image.new(imageable: @imageable)
end
def prepare_image_for_creation
@image = Image.new(image_params)
@image.imageable = @imageable
@image.user = current_user
end
def recover_attachments_from_cache
if @image.attachment.blank? && @image.cached_attachment.present?
@image.set_attachment_from_cached_attachment
end
end
end

View File

@@ -20,12 +20,14 @@ class ProposalsController < ApplicationController
super super
@notifications = @proposal.notifications @notifications = @proposal.notifications
@document = Document.new(documentable: @proposal) @document = Document.new(documentable: @proposal)
@image = Image.new(imageable: @proposal)
redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal) redirect_to proposal_path(@proposal), status: :moved_permanently if request.path != proposal_path(@proposal)
end end
def create def create
@proposal = Proposal.new(proposal_params.merge(author: current_user)) @proposal = Proposal.new(proposal_params.merge(author: current_user))
recover_documents_from_cache(@proposal) recover_documents_from_cache(@proposal)
recover_image_from_cache(@proposal)
if @proposal.save if @proposal.save
redirect_to share_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal') redirect_to share_proposal_path(@proposal), notice: I18n.t('flash.actions.create.proposal')
@@ -78,6 +80,7 @@ class ProposalsController < ApplicationController
def proposal_params def proposal_params
params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url, params.require(:proposal).permit(:title, :question, :summary, :description, :external_url, :video_url,
:responsible_name, :tag_list, :terms_of_service, :geozone_id, :responsible_name, :tag_list, :terms_of_service, :geozone_id,
image_attributes: [:id, :title, :attachment, :cached_attachment, :user_id],
documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id] ) documents_attributes: [:id, :title, :attachment, :cached_attachment, :user_id] )
end end

View File

@@ -22,7 +22,7 @@ module DocumentablesHelper
.join(",") .join(",")
end end
def humanized_accepted_content_types(documentable) def documentable_humanized_accepted_content_types(documentable)
documentable.class.accepted_content_types documentable.class.accepted_content_types
.collect{ |content_type| content_type.split("/").last } .collect{ |content_type| content_type.split("/").last }
.join(", ") .join(", ")
@@ -30,7 +30,7 @@ module DocumentablesHelper
def documentables_note(documentable) def documentables_note(documentable)
t "documents.form.note", max_documents_allowed: max_documents_allowed(documentable), t "documents.form.note", max_documents_allowed: max_documents_allowed(documentable),
accepted_content_types: humanized_accepted_content_types(documentable), accepted_content_types: documentable_humanized_accepted_content_types(documentable),
max_file_size: max_file_size(documentable) max_file_size: max_file_size(documentable)
end end

View File

@@ -4,7 +4,7 @@ module DocumentsHelper
document.attachment_file_name document.attachment_file_name
end end
def errors_on_attachment(document) def document_errors_on_attachment(document)
document.errors[:attachment].join(', ') if document.errors.key?(:attachment) document.errors[:attachment].join(', ') if document.errors.key?(:attachment)
end end
@@ -71,7 +71,7 @@ module DocumentsHelper
class: "button hollow #{klass}" class: "button hollow #{klass}"
if document.errors[:attachment].any? if document.errors[:attachment].any?
html += content_tag :small, class: "error" do html += content_tag :small, class: "error" do
errors_on_attachment(document) document_errors_on_attachment(document)
end end
end end
end end

View File

@@ -0,0 +1,36 @@
module ImageablesHelper
def imageable_class(imageable)
imageable.class.name.parameterize('_')
end
def imageable_max_file_size
bytesToMeg(Image::MAX_IMAGE_SIZE)
end
def bytesToMeg(bytes)
bytes / Numeric::MEGABYTE
end
def imageable_accepted_content_types
Image::ACCEPTED_CONTENT_TYPE
end
def imageable_accepted_content_types_extensions
Image::ACCEPTED_CONTENT_TYPE
.collect{ |content_type| ".#{content_type.split("/").last}" }
.join(",")
end
def imageable_humanized_accepted_content_types
Image::ACCEPTED_CONTENT_TYPE
.collect{ |content_type| content_type.split("/").last }
.join(", ")
end
def imageables_note(imageable)
t "images.form.note", accepted_content_types: imageable_humanized_accepted_content_types,
max_file_size: max_file_size(imageable)
end
end

View File

@@ -0,0 +1,98 @@
module ImagesHelper
def image_attachment_file_name(image)
image.attachment_file_name
end
def image_errors_on_attachment(image)
image.errors[:attachment].join(', ') if image.errors.key?(:attachment)
end
def image_bytesToMeg(bytes)
bytes / Numeric::MEGABYTE
end
def image_nested_field_name(image, field)
parent = image.imageable_type.parameterize.underscore
"#{parent.parameterize}[image_attributes]#{field}"
end
def image_nested_field_id(image, field)
parent = image.imageable_type.parameterize.underscore
"#{parent.parameterize}_image_attributes_#{field}"
end
def image_nested_field_wrapper_id
"nested_image"
end
def image_class(image)
image.persisted? ? "image" : "cached-image"
end
def render_destroy_image_link(image)
if image.persisted?
link_to t('images.form.delete_button'),
image_path(image, nested_image: true),
method: :delete,
remote: true,
data: { confirm: t('images.actions.destroy.confirm') },
class: "delete float-right"
elsif !image.persisted? && image.cached_attachment.present?
link_to t('images.form.delete_button'),
destroy_upload_images_path(path: image.cached_attachment,
nested_image: true,
imageable_type: image.imageable_type,
imageable_id: image.imageable_id),
method: :delete,
remote: true,
class: "delete float-right"
else
link_to t('images.form.delete_button'),
"#",
class: "delete float-right remove-image"
end
end
def render_image_attachment(image)
html = file_field_tag :attachment,
accept: imageable_accepted_content_types_extensions,
class: 'image_ajax_attachment',
data: {
url: image_direct_upload_url(image),
cached_attachment_input_field: image_nested_field_id(image, :cached_attachment),
multiple: false,
nested_image: true
},
name: image_nested_field_name(image, :attachment),
id: image_nested_field_id(image, :attachment)
if image.attachment.blank? && image.cached_attachment.blank?
klass = image.errors[:attachment].any? ? "error" : ""
html += label_tag image_nested_field_id(image, :attachment),
t("images.form.attachment_label"),
class: "button hollow #{klass}"
if image.errors[:attachment].any?
html += content_tag :small, class: "error" do
image_errors_on_attachment(image)
end
end
end
html
end
def render_image(image, version, show_caption = true)
version = image.persisted? ? version : :original
render partial: "images/image", locals: { image: image,
version: version,
show_caption: show_caption }
end
def image_direct_upload_url(image)
upload_images_url(
imageable_type: image.imageable_type,
imageable_id: image.imageable_id,
format: :js
)
end
end

View File

@@ -5,7 +5,7 @@ module InvestmentsHelper
end end
def investment_image_advice_note(investment) def investment_image_advice_note(investment)
if investment.image.exists? if investment.image.present?
t("budgets.investments.edit_image.edit_note", title: investment.title) t("budgets.investments.edit_image.edit_note", title: investment.title)
else else
t("budgets.investments.edit_image.add_note", title: investment.title) t("budgets.investments.edit_image.add_note", title: investment.title)
@@ -13,7 +13,7 @@ module InvestmentsHelper
end end
def investment_image_button_text(investment) def investment_image_button_text(investment)
investment.image.exists? ? t("budgets.investments.show.edit_image") : t("budgets.investments.show.add_image") investment.image.present? ? t("budgets.investments.show.edit_image") : t("budgets.investments.show.add_image")
end end
def errors_on_image(investment) def errors_on_image(investment)

View File

@@ -74,6 +74,7 @@ module Abilities
cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation] cannot :comment_as_moderator, [::Legislation::Question, Legislation::Annotation]
can [:create, :destroy], Document can [:create, :destroy], Document
can [:create, :destroy], Image
end end
end end
end end

View File

@@ -40,6 +40,9 @@ module Abilities
can [:create, :destroy, :new], Document, documentable: { author_id: user.id } can [:create, :destroy, :new], Document, documentable: { author_id: user.id }
can [:new_nested, :upload, :destroy_upload], Document can [:new_nested, :upload, :destroy_upload], Document
can [:create, :destroy, :new], Image, imageable: { author_id: user.id }
can [:new_nested, :upload, :destroy_upload], Image
unless user.organization? unless user.organization?
can :vote, Debate can :vote, Debate
can :vote, Comment can :vote, Comment

View File

@@ -7,8 +7,8 @@ class Budget
include Reclassification include Reclassification
include Followable include Followable
include Communitable include Communitable
include Documentable
include Imageable include Imageable
include Documentable
documentable max_documents_allowed: 3, documentable max_documents_allowed: 3,
max_file_size: 3.megabytes, max_file_size: 3.megabytes,
accepted_content_types: [ "application/pdf" ] accepted_content_types: [ "application/pdf" ]

View File

@@ -15,6 +15,7 @@ module Documentable
@max_file_size = options[:max_file_size] @max_file_size = options[:max_file_size]
@accepted_content_types = options[:accepted_content_types] @accepted_content_types = options[:accepted_content_types]
end end
end end
end end

View File

@@ -8,7 +8,7 @@ class Document < ActiveRecord::Base
belongs_to :documentable, polymorphic: true belongs_to :documentable, polymorphic: true
# Disable paperclip security validation due to polymorphic configuration # Disable paperclip security validation due to polymorphic configuration
# Paperclip do not allow to user Procs on valiations definition # Paperclip do not allow to use Procs on valiations definition
do_not_validate_attachment_file_type :attachment do_not_validate_attachment_file_type :attachment
validate :attachment_presence validate :attachment_presence
validate :validate_attachment_content_type, if: -> { attachment.present? } validate :validate_attachment_content_type, if: -> { attachment.present? }
@@ -64,7 +64,7 @@ class Document < ActiveRecord::Base
!accepted_content_types(documentable).include?(attachment_content_type) !accepted_content_types(documentable).include?(attachment_content_type)
errors[:attachment] = I18n.t("documents.errors.messages.wrong_content_type", errors[:attachment] = I18n.t("documents.errors.messages.wrong_content_type",
content_type: attachment_content_type, content_type: attachment_content_type,
accepted_content_types: humanized_accepted_content_types(documentable)) accepted_content_types: documentable_humanized_accepted_content_types(documentable))
end end
end end

View File

@@ -1,35 +1,62 @@
class Image < ActiveRecord::Base class Image < ActiveRecord::Base
include ImagesHelper
include ImageablesHelper
TITLE_LEGHT_RANGE = 4..80 TITLE_LEGHT_RANGE = 4..80
MIN_SIZE = 475 MIN_SIZE = 475
MAX_IMAGE_SIZE = 1.megabyte
ACCEPTED_CONTENT_TYPE = %w(image/jpeg image/jpg)
attr_accessor :content_type, :original_filename, :attachment_data, :attachment_urls has_attached_file :attachment, styles: { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" },
path: ":rails_root/public/system/:class/:prefix/:style/:filename",
url: "/system/:class/:prefix/:style/:filename"
attr_accessor :cached_attachment
belongs_to :user
belongs_to :imageable, polymorphic: true belongs_to :imageable, polymorphic: true
before_validation :set_styles
has_attached_file :attachment, styles: { large: "x475", medium: "300x300#", thumb: "140x245#" }, # Disable paperclip security validation due to polymorphic configuration
url: "/system/:class/:attachment/:imageable_name_path/:style/:hash.:extension", # Paperclip do not allow to use Procs on valiations definition
hash_secret: Rails.application.secrets.secret_key_base do_not_validate_attachment_file_type :attachment
validates_attachment :attachment, presence: true, content_type: { content_type: %w(image/jpeg image/jpg) }, validate :attachment_presence
size: { less_than: 1.megabytes } validate :validate_attachment_content_type, if: -> { attachment.present? }
validate :validate_attachment_size, if: -> { attachment.present? }
validates :title, presence: true, length: { in: TITLE_LEGHT_RANGE } validates :title, presence: true, length: { in: TITLE_LEGHT_RANGE }
validate :check_image_dimensions validates :user_id, presence: true
validates :imageable_id, presence: true, if: -> { persisted? }
validates :imageable_type, presence: true, if: -> { persisted? }
validate :validate_image_dimensions, if: -> { attachment.present? && attachment.dirty? }
after_create :redimension_using_origin_styles after_create :redimension_using_origin_styles
accepts_nested_attributes_for :imageable after_save :remove_cached_image, if: -> { valid? && persisted? && cached_attachment.present? }
# # overwrite default styles for Image class def set_cached_attachment_from_attachment(prefix)
# def set_image_styles self.cached_attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
# { large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" } attachment.path
# end
def set_styles
if imageable
imageable.set_styles if imageable.respond_to? :set_styles
else else
{ large: "x#{MIN_SIZE}", medium: "300x300#", thumb: "140x245#" } prefix + attachment.url
end end
end end
Paperclip.interpolates :imageable_name_path do |attachment, _style| def set_attachment_from_cached_attachment
attachment.instance.imageable.class.to_s.downcase.split('::').map(&:pluralize).join('/') self.attachment = if Paperclip::Attachment.default_options[:storage] == :filesystem
File.open(cached_attachment)
else
URI.parse(cached_attachment)
end
end
Paperclip.interpolates :prefix do |attachment, style|
attachment.instance.prefix(attachment, style)
end
def prefix(attachment, style)
if !attachment.instance.persisted?
"cached_attachments/user/#{attachment.instance.user_id}"
else
":attachment/:id_partition"
end
end end
private private
@@ -38,11 +65,37 @@ class Image < ActiveRecord::Base
attachment.reprocess! attachment.reprocess!
end end
def check_image_dimensions def validate_image_dimensions
return unless attachment?
dimensions = Paperclip::Geometry.from_file(attachment.queued_for_write[:original].path) dimensions = Paperclip::Geometry.from_file(attachment.queued_for_write[:original].path)
errors.add(:attachment, :min_image_width, required_min_width: MIN_SIZE) if dimensions.width < MIN_SIZE errors.add(:attachment, :min_image_width, required_min_width: MIN_SIZE) if dimensions.width < MIN_SIZE
errors.add(:attachment, :min_image_height, required_min_height: MIN_SIZE) if dimensions.height < MIN_SIZE errors.add(:attachment, :min_image_height, required_min_height: MIN_SIZE) if dimensions.height < MIN_SIZE
end end
def validate_attachment_size
if imageable.present? &&
attachment_file_size > 1.megabytes
errors[:attachment] = I18n.t("images.errors.messages.in_between",
min: "0 Bytes",
max: "#{imageable_max_file_size} MB")
end
end
def validate_attachment_content_type
if imageable.present? &&
!imageable_accepted_content_types.include?(attachment_content_type)
errors[:attachment] = I18n.t("images.errors.messages.wrong_content_type",
content_type: attachment_content_type,
accepted_content_types: imageable_humanized_accepted_content_types)
end
end
def attachment_presence
if attachment.blank? && cached_attachment.blank?
errors[:attachment] = I18n.t("errors.messages.blank")
end
end
def remove_cached_image
File.delete(cached_attachment) if File.exists?(cached_attachment)
end
end end

View File

@@ -10,6 +10,7 @@ class Proposal < ActiveRecord::Base
include Graphqlable include Graphqlable
include Followable include Followable
include Communitable include Communitable
include Imageable
include Documentable include Documentable
documentable max_documents_allowed: 3, documentable max_documents_allowed: 3,
max_file_size: 3.megabytes, max_file_size: 3.megabytes,

View File

@@ -21,6 +21,10 @@
<%= f.text_field :external_url %> <%= f.text_field :external_url %>
</div> </div>
<div class="images small-12 column" data-max-images="1">
<%= render 'images/nested_images', imageable: @investment %>
</div>
<div class="documents small-12 column" data-max-documents="<%= Budget::Investment.max_documents_allowed %>"> <div class="documents small-12 column" data-max-documents="<%= Budget::Investment.max_documents_allowed %>">
<%= render 'documents/nested_documents', documentable: @investment %> <%= render 'documents/nested_documents', documentable: @investment %>
</div> </div>
@@ -53,29 +57,6 @@
data: {js_url: suggest_tags_path} %> data: {js_url: suggest_tags_path} %>
</div> </div>
<%= f.fields_for :image do |builder| %>
<div class="small-12 column">
<div class="image-upload">
<%= f.file_field :attachment, accept: 'image/jpg,image/jpeg', label: false, class:'show-for-sr' %>
<br>
<%= f.label :attachment, t("budgets.investments.form.image_label"), class:'button' %>
</div>
</div>
<% if @investment.errors.has_key?(:attachment) %>
<div class="small-12 column">
<div class="image-errors">
<small class="error"><%= errors_on_image(@investment)%></small>
</div>
</div>
<% end %>
<div class="small-12 column">
<%= f.text_field :title %>
</div>
<% end %>
<% unless current_user.manager? %> <% unless current_user.manager? %>
<div class="small-12 column"> <div class="small-12 column">

View File

@@ -1,53 +0,0 @@
<%= form_for([@investment.budget, @investment], url: update_image_budget_investment_path(@investment.budget, @investment), multipart: true, method: :put) do |f| %>
<%= render 'shared/errors', resource: @investment %>
<div class="row">
<div class="small-12 column">
<span class="note-marked">
<p><%= investment_image_advice_note(@investment) %></p>
</span>
</div>
<% if @investment.image.exists? %>
<div class="small-12 column">
<%= image_tag @investment.image_url(:large) %>
</div>
<% end %>
<div class="small-12 column">
<div class="image-upload">
<%= f.file_field :attachment, accept: 'image/jpg,image/jpeg', label: false, class:'show-for-sr' %>
<br>
<%= f.label :attachment, t("budgets.investments.edit_image.form.image_label"), class:'button' %>
</div>
</div>
<% if @investment.errors.has_key?(:image) %>
<div class="small-12 column">
<div class="image-errors">
<small class="error"><%= errors_on_image(@investment)%></small>
</div>
</div>
<% end %>
<div class="small-12 column">
<%= f.label :title, t("budgets.investments.edit_image.form.image_title") %>
<%= f.text_field :title, placeholder: t("budgets.investments.edit_image.form.image_title"), label: false %>
</div>
<div class="actions small-12 column">
<%= f.submit(class: "button", value: t("budgets.investments.edit_image.form.submit_button")) %>
<% if @investment.image.exists? %>
<div class="float-right">
<%= link_to t("budgets.investments.edit_image.form.remove_button"),
remove_image_budget_investment_path(@investment.budget, @investment),
class: "button hollow alert",
method: :delete,
data: { confirm: t("budgets.investments.edit_image.form.remove_alert") } %>
</div>
<% end %>
</div>
</div>
<% end %>

View File

@@ -3,11 +3,8 @@
<div class="row"> <div class="row">
<div class="small-12 medium-3 large-2 column"> <div class="small-12 medium-3 large-2 column">
<% if investment.image.exists? %> <% if investment.image.present? %>
<figure>
<%= image_tag investment.image_url(:thumb), alt: investment.image.title %> <%= image_tag investment.image_url(:thumb), alt: investment.image.title %>
<figcaption><%= investment.image.title %></figcaption>
</figure>
<% else %> <% else %>
<div class="no-image"></div> <div class="no-image"></div>
<% end %> <% end %>

View File

@@ -10,9 +10,16 @@
class: 'button hollow float-right' %> class: 'button hollow float-right' %>
<% end %> <% end %>
<% if can?(:edit_image, @investment) %> <% if can?(:create, @image) %>
<%= link_to investment_image_button_text(@investment), <%= link_to t("images.upload_image"),
edit_image_budget_investment_path(investment.budget, investment), new_image_path(imageable_id:investment, imageable_type: investment.class.name, from: request.url),
class: 'button hollow float-right' %>
<% end %>
<% if @investment.image.present? && can?(:destroy, @investment.image) %>
<%= link_to t("images.remove_image"),
image_path(@investment.image, from: request.url),
method: :delete,
class: 'button hollow float-right' %> class: 'button hollow float-right' %>
<% end %> <% end %>
@@ -28,20 +35,7 @@
<br> <br>
<% if investment.image.exists? %> <%= render_image(investment.image, :large, true) if investment.image.present? %>
<div class="row">
<div class="small-12 column text-center">
<figure>
<%= image_tag investment.image_url(:large),
alt: investment.image.title %>
<figcaption class="text-right">
<em><%= investment.image.title %></em>
</figcaption>
</figure>
<hr>
</div>
</div>
<% end %>
<p id="investment_code"> <p id="investment_code">
<%= t("budgets.investments.show.code_html", code: investment.id) %> <%= t("budgets.investments.show.code_html", code: investment.id) %>
@@ -138,7 +132,7 @@
<%= render partial: 'shared/social_share', locals: { <%= render partial: 'shared/social_share', locals: {
share_title: t("budgets.investments.show.share"), share_title: t("budgets.investments.show.share"),
title: investment.title, title: investment.title,
image_url: investment.image.exists? ? investment_image_full_url(investment, :thumb) : '', image_url: investment.image.present? ? investment_image_full_url(investment, :thumb) : '',
url: budget_investment_url(budget_id: investment.budget_id, id: investment.id) url: budget_investment_url(budget_id: investment.budget_id, id: investment.id)
} %> } %>

View File

@@ -1,19 +0,0 @@
<div class="budget-investment-image-form row">
<div class="small-12 medium-9 column">
<%= render "shared/back_link" %>
<h1><%= t("budgets.investments.edit_image.title") %></h1>
<%= render "image_form" %>
</div>
<div class="small-12 medium-3 column">
<span class="icon-budget-investment-image float-right"></span>
<h2><%= t("budgets.investments.edit_image.recommendation_title") %></h2>
<ul class="recommendations">
<li><%= t("budgets.investments.edit_image.recommendation_one") %></li>
<li><%= t("budgets.investments.edit_image.recommendation_two") %></li>
</ul>
</div>
</div>

View File

@@ -3,8 +3,9 @@
<p class="help-text"><%= documentables_note(documentable) %></p> <p class="help-text"><%= documentables_note(documentable) %></p>
<% documentable.documents.each_with_index do |document, index| %> <% documentable.documents.each_with_index do |document, index| %>
<%= render 'documents/nested_fields', document: document, index: index, documentable: documentable %> <%= render 'documents/nested_fields', document: document, index: index %>
<% end %> <% end %>
</div> </div>
<% unless max_documents_allowed?(documentable) %> <% unless max_documents_allowed?(documentable) %>

View File

@@ -39,7 +39,7 @@
<% if document.errors.has_key?(:attachment) %> <% if document.errors.has_key?(:attachment) %>
<div class="small-12 column source-option-file"> <div class="small-12 column source-option-file">
<div class="attachment-errors"> <div class="attachment-errors">
<small class="error"><%= errors_on_attachment(document) %></small> <small class="error"><%= document_errors_on_attachment(document) %></small>
</div> </div>
</div> </div>
<% end %> <% end %>

View File

@@ -16,7 +16,7 @@
</li> </li>
<li> <li>
<%= t "documents.recommendation_two_html", <%= t "documents.recommendation_two_html",
accepted_content_types: humanized_accepted_content_types(@document.documentable) %> accepted_content_types: documentable_humanized_accepted_content_types(@document.documentable) %>
</li> </li>
<li> <li>
<%= t "documents.recommendation_three_html", <%= t "documents.recommendation_three_html",

View File

@@ -0,0 +1,20 @@
<%= form_for @image,
url: images_path(
imageable_type: @image.imageable_type,
imageable_id: @image.imageable_id,
from: params[:from]
),
html: { multipart: true, class: "imageable"},
data: { direct_upload_url: upload_images_url(imageable_type: @image.imageable_type, imageable_id: @image.imageable_id) } do |f| %>
<%= render 'shared/errors', resource: @image %>
<div class="row">
<%= render 'plain_fields', image: @image %>
<div class="actions small-12 medium-6 large-4 end column">
<%= f.submit(t("images.form.submit_button"), class: "button expanded") %>
</div>
</div>
<% end %>

View File

@@ -0,0 +1,19 @@
<div class="row">
<div class="small-12 column text-center">
<figure>
<%= image_tag image.attachment.url(version),
class: image_class(image),
alt: image.title %>
<% if show_caption %>
<figcaption class="text-right">
<em><%= image.title %></em>
</figcaption>
<% end %>
</figure>
<% if show_caption %>
<hr>
<% end %>
</div>
</div>

View File

@@ -0,0 +1,32 @@
<div id="<%= image_nested_field_wrapper_id %>" class="image">
<%= hidden_field_tag :id,
image.id,
name: image_nested_field_name(image, :id),
id: image_nested_field_id(image, :id) if image.persisted? %>
<%= hidden_field_tag :user_id,
current_user.id,
name: image_nested_field_name(image, :user_id),
id: image_nested_field_id(image, :user_id) %>
<%= hidden_field_tag :cached_attachment,
image.cached_attachment,
name: image_nested_field_name(image, :cached_attachment),
id: image_nested_field_id(image, :cached_attachment) %>
<%= label_tag :title, t("activerecord.attributes.image.title") %>
<%= text_field_tag :title,
image.title,
name: image_nested_field_name(image, :title),
id: image_nested_field_id(image, :title),
class: "image-title" %>
<% if image.errors[:title].any? %>
<small class="error"><%= image.errors[:title].join(", ") %></small>
<% end %>
<%= render_image(image, :thumb, false) if image.attachment.exists? %>
<%= render_image_attachment(image) %>
<%= render_destroy_image_link(image) %>
<p class="file-name"><%= image_attachment_file_name(image) %></p>
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>

View File

@@ -0,0 +1,16 @@
<div class="images-list">
<%= label_tag :image, t("images.form.title") %>
<p class="help-text"><%= imageables_note(imageable) %></p>
<%= render 'images/nested_fields', image: imageable.image if imageable.image.present? %>
</div>
<% if imageable.image.blank? %>
<%= link_to t("images.form.add_new_image"),
new_nested_images_path(imageable_type: imageable.class.name, index: 0),
remote: true,
id: "new_image_link",
class: "button hollow" %>
<% end %>
<hr>

View File

@@ -0,0 +1,50 @@
<div id="plain_image_fields" class="image">
<div class="small-12 column">
<%= label_tag :image_title, t("activerecord.attributes.image.title") %>
<%= text_field_tag :image_title, image.title, name: "image[title]", class: "image-title" %>
<% if image.errors.has_key?(:title) %>
<small class="error"><%= image.errors[:title].join(", ") %></small>
<% end %>
</div>
<div class="small-12 column">
<%= hidden_field_tag :cached_attachment, image.cached_attachment, name: "image[cached_attachment]" %>
<%= file_field_tag :attachment,
accept: imageable_accepted_content_types_extensions,
label: false,
class: 'image_ajax_attachment',
data: {
url: upload_images_url(imageable_type: image.imageable_type, imageable_id: image.imageable_id),
cached_attachment_input_field: "image_cached_attachment",
multiple: false,
nested_image: false
},
id: "image_attachment",
name: "image[attachment]" %>
<% if image.cached_attachment.blank? %>
<%= label_tag :image_attachment, t("images.form.attachment_label"), class: 'button hollow' %>
<% else %>
<%= link_to t('images.form.delete_button'),
destroy_upload_images_path(path: image.cached_attachment,
nested_image: false,
imageable_type: image.imageable_type,
imageable_id: image.imageable_id),
method: :delete,
remote: true,
class: "delete float-right" %>
<% end %>
<% if image.errors.has_key?(:attachment) %>
<div class="small-12 column source-option-file">
<div class="attachment-errors">
<small class="error"><%= image_errors_on_attachment(image) %></small>
</div>
</div>
<% end %>
<p class="file-name"><%= image_attachment_file_name(image) %></p>
<div class="progress-bar-placeholder"><div class="loading-bar"></div></div>
</div>
</div>

View File

@@ -0,0 +1,17 @@
<% if params[:nested_image] == "true" %>
App.Imageable.destroyNestedImage("<%= image_nested_field_wrapper_id %>", "<%= j render('layouts/flash') %>")
<% new_image_link = link_to t("images.form.add_new_image"),
new_nested_images_path(imageable_type: @image.imageable_type),
remote: true,
id: "new_image_link",
class: "button hollow" %>
App.Imageable.updateNewImageButton("<%= j new_image_link %>")
<% else %>
App.Imageable.replacePlainImage("plain_image_fields",
"<%= j render('layouts/flash') %>",
"<%= j render('plain_fields', image: @image) %>")
<% end %>

View File

@@ -0,0 +1,26 @@
<div class="image-form <%= imageable_class(@image.imageable) %> row">
<div class="small-12 medium-9 column">
<%= back_link_to params[:from] %>
<h1><%= t("images.new.title") %></h1>
<%= render "form", form_url: images_url %>
</div>
<div class="small-12 medium-3 column">
<span class="icon-images float-right"></span>
<h2><%= t("images.recommendations_title") %></h2>
<ul class="recommendations">
<li>
<%= t "images.recommendation_one_html" %>
</li>
<li>
<%= t "images.recommendation_two_html",
accepted_content_types: imageable_humanized_accepted_content_types %>
</li>
<li>
<%= t "images.recommendation_three_html",
max_file_size: imageable_max_file_size %>
</li>
</ul>
</div>
</div>

View File

@@ -0,0 +1,9 @@
<%
new_image_link = link_to t("images.form.add_new_image"),
new_nested_images_path(imageable_type: params[:imageable_type]),
remote: true,
id: "new_image_link",
class: "button hollow"
%>
App.Imageable.new("<%= j render('images/nested_fields', image: @image) %>")
App.Imageable.updateNewImageButton("<%= j new_image_link %>")

View File

@@ -0,0 +1,12 @@
<% if params[:nested_image] == "true" %>
App.Imageable.uploadNestedImage("<%= image_nested_field_wrapper_id %>",
"<%= j render('images/nested_fields', image: @image) %>",
<%= @image.cached_attachment.present? %>)
<% else %>
App.Imageable.uploadPlainImage("plain_image_fields",
"<%= j render('images/plain_fields', image: @image) %>",
<%= @image.cached_attachment.present? %>)
<% end %>

View File

@@ -46,6 +46,10 @@
<%= f.text_field :external_url, placeholder: t("proposals.form.proposal_external_url"), label: false %> <%= f.text_field :external_url, placeholder: t("proposals.form.proposal_external_url"), label: false %>
</div> </div>
<div class="images small-12 column" data-max-images="1">
<%= render 'images/nested_images', imageable: @proposal %>
</div>
<div class="documents small-12 column" data-max-documents="<%= Proposal.max_documents_allowed %>"> <div class="documents small-12 column" data-max-documents="<%= Proposal.max_documents_allowed %>">
<%= render 'documents/nested_documents', documentable: @proposal %> <%= render 'documents/nested_documents', documentable: @proposal %>
</div> </div>

View File

@@ -48,6 +48,8 @@
</div> </div>
<%= render_image(@proposal.image, :large, true) if @proposal.image.present? %>
<br> <br>
<p> <p>
<%= t("proposals.show.code") %> <%= t("proposals.show.code") %>
@@ -106,7 +108,8 @@
</div> </div>
<aside class="small-12 medium-3 column"> <aside class="small-12 medium-3 column">
<% if can_create_document?(@document, @proposal) || author_of_proposal?(@proposal) || current_editable?(@proposal) %> <% if can_create_document?(@document, @proposal) || author_of_proposal?(@proposal) || current_editable?(@proposal) ||
can?(:create, @image) || (@proposal.image.present? && can?(:destroy, @proposal.image)) %>
<div class="sidebar-divider"></div> <div class="sidebar-divider"></div>
<h2><%= t("proposals.show.author") %></h2> <h2><%= t("proposals.show.author") %></h2>
<div class="show-actions-menu"> <div class="show-actions-menu">
@@ -132,6 +135,22 @@
<%= t("proposals.show.edit_proposal_link") %> <%= t("proposals.show.edit_proposal_link") %>
<% end %> <% end %>
<% end %> <% end %>
<% if can?(:create, @image) %>
<%= link_to new_image_path(imageable_id: @proposal, imageable_type: @proposal.class.name, from: request.url),
class: 'button hollow float-right' do %>
<%= t("images.upload_image") %>
<% end %>
<% end %>
<% if @proposal.image.present? && can?(:destroy, @proposal.image) %>
<%= link_to image_path(@proposal.image, from: request.url),
method: :delete,
class: 'button hollow float-right',
data: { confirm: 'Are you sure?' } do %>
<% t("images.remove_image") %>
<% end %>
<% end %>
</div> </div>
<% end %> <% end %>

View File

@@ -40,6 +40,7 @@ data:
- config/locales/%{locale}/legislation.yml - config/locales/%{locale}/legislation.yml
- config/locales/%{locale}/community.yml - config/locales/%{locale}/community.yml
- config/locales/%{locale}/documents.yml - config/locales/%{locale}/documents.yml
- config/locales/%{locale}/images.yml
# Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom: # Locale files to write new keys to, based on a list of key pattern => file rules. Matched from top to bottom:
# `i18n-tasks normalize -p` will force move the keys according to these rules # `i18n-tasks normalize -p` will force move the keys according to these rules

View File

@@ -79,6 +79,9 @@ en:
documents: documents:
one: "Document" one: "Document"
other: "Documents" other: "Documents"
images:
one: "Image"
other: "Images"
attributes: attributes:
budget: budget:
name: "Name" name: "Name"
@@ -205,6 +208,9 @@ en:
document: document:
title: Title title: Title
attachment: Attachment attachment: Attachment
image:
title: Title
attachment: Attachment
errors: errors:
models: models:
user: user:

View File

@@ -181,6 +181,7 @@ en:
signature_sheet: Signature sheet signature_sheet: Signature sheet
document: Document document: Document
topic: Topic topic: Topic
image: Image
geozones: geozones:
none: All city none: All city
all: All scopes all: All scopes

View File

@@ -0,0 +1,33 @@
en:
images:
upload_image: Upload image
remove_image: Remove image
form:
title: Descriptive image
attachment_label: Choose image
submit_button: Upload image
delete_button: Remove image
note: "You can upload one image of following content types: %{accepted_content_types}, up to %{max_file_size} MB."
add_new_image: Add image
new:
title: Upload image
recommendations_title: File upload tips
recommendation_one_html: You can upload only one image. Proposals with image attract more attention of the users.
recommendation_two_html: You can upload <strong>%{accepted_content_types}</strong> image.
recommendation_three_html: You can upload one image up to <strong>%{max_file_size} MB</strong>.
actions:
create:
notice: Image was created successfully.
alert: Cannot create image. Check form errors and try again.
destroy:
notice: Image was deleted successfully.
alert: Cannot destroy image.
confirm: Are you sure you want to delete the image? This action cannot be undone!
buttons:
download_image: Dowload file
destroy_image: Destroy
errors:
messages:
in_between: must be in between %{min} and %{max}
wrong_content_type: content type %{content_type} does not match any of accepted content types %{accepted_content_types}

View File

@@ -79,6 +79,9 @@ es:
documents: documents:
one: "Documento" one: "Documento"
other: "Documentos" other: "Documentos"
images:
one: "Imagen"
other: "Imágenes"
attributes: attributes:
budget: budget:
name: "Nombre" name: "Nombre"
@@ -99,8 +102,7 @@ es:
title: "Título" title: "Título"
location: "Ubicación" location: "Ubicación"
organization_name: "Si estás proponiendo en nombre de una organización o colectivo, escribe su nombre" organization_name: "Si estás proponiendo en nombre de una organización o colectivo, escribe su nombre"
image: "Imagen descriptiva de la propuesta" image: "Imagen descriptiva de la propuesta de inversión"
image_title: "Título de la imagen"
comment: comment:
body: "Comentario" body: "Comentario"
user: "Usuario" user: "Usuario"
@@ -200,6 +202,9 @@ es:
document: document:
title: Título title: Título
attachment: Archivo adjunto attachment: Archivo adjunto
image:
title: Título
attachment: Archivo adjunto
errors: errors:
models: models:
user: user:

View File

@@ -0,0 +1,33 @@
es:
images:
upload_image: Subir imagen
remove_image: Eliminar imagen
form:
title: Imagen descriptiva
attachment_label: Selecciona una imagen
submit_button: Subir imagen
delete_button: Eliminar imagen
note: "Puedes subir una imagen en los formatos: %{accepted_content_types}, y de hasta %{max_file_size} MB por archivo."
add_new_image: Añadir imagen
new:
title: Subir una imagen
recommendations_title: Consejos para subir imágenes
recommendation_one_html: Puedes subir una imagen descriptiva, esta imagen se mostrará en los listados y ayudará a que tu propuesta llame más la atención de los usuarios.
recommendation_two_html: "Sólo puedes subir <strong>imágenes en los formatos: %{accepted_content_types}</strong>."
recommendation_three_html: Puedes subir una imagen de hasta <strong>%{max_file_size} MB</strong>
actions:
create:
notice: "La imagen se ha creado correctamente."
alert: "La imagen no se ha podido crear. Revise los errores del formulario."
destroy:
notice: "La imagen se ha eliminado correctamente."
alert: "La imagen no se ha podido eliminar."
confirm: "¿Está seguro de que desea eliminar la imagen? Esta acción no se puede deshacer!"
buttons:
download_image: Descargar imagen
destroy_image: Eliminar
errors:
messages:
in_between: debe estar entre %{min} y %{max}
wrong_content_type: El tipo de contenido %{content_type} de la imagen no coincide con ninguno de los tipos de contenido aceptados %{accepted_content_types}

View File

@@ -39,7 +39,6 @@ Rails.application.routes.draw do
concern :imageable do concern :imageable do
resources :images resources :images
end end
resources :debates do resources :debates do
member do member do
post :vote post :vote
@@ -84,9 +83,6 @@ Rails.application.routes.draw do
resources :investments, controller: "budgets/investments", only: [:index, :new, :create, :show, :destroy], concerns: :imageable do resources :investments, controller: "budgets/investments", only: [:index, :new, :create, :show, :destroy], concerns: :imageable do
member do member do
post :vote post :vote
get :edit_image
put :update_image
delete :remove_image
end end
collection { get :suggest } collection { get :suggest }
end end
@@ -112,6 +108,14 @@ Rails.application.routes.draw do
end end
end end
resources :images, only: [:new, :create, :destroy] do
collection do
get :new_nested
delete :destroy_upload
post :upload
end
end
resources :stats, only: [:index] resources :stats, only: [:index]
resources :legacy_legislations, only: [:show], path: 'legislations' resources :legacy_legislations, only: [:show], path: 'legislations'

View File

@@ -0,0 +1,5 @@
class AddUserIdToImages < ActiveRecord::Migration
def change
add_reference :images, :user, index: true, foreign_key: true
end
end

View File

@@ -398,9 +398,11 @@ ActiveRecord::Schema.define(version: 20170918231410) do
t.string "attachment_content_type" t.string "attachment_content_type"
t.integer "attachment_file_size" t.integer "attachment_file_size"
t.datetime "attachment_updated_at" t.datetime "attachment_updated_at"
t.integer "user_id"
end end
add_index "images", ["imageable_type", "imageable_id"], name: "index_images_on_imageable_type_and_imageable_id", using: :btree add_index "images", ["imageable_type", "imageable_id"], name: "index_images_on_imageable_type_and_imageable_id", using: :btree
add_index "images", ["user_id"], name: "index_images_on_user_id", using: :btree
create_table "legacy_legislations", force: :cascade do |t| create_table "legacy_legislations", force: :cascade do |t|
t.string "title" t.string "title"
@@ -1093,6 +1095,7 @@ ActiveRecord::Schema.define(version: 20170918231410) do
add_foreign_key "geozones_polls", "geozones" add_foreign_key "geozones_polls", "geozones"
add_foreign_key "geozones_polls", "polls" add_foreign_key "geozones_polls", "polls"
add_foreign_key "identities", "users" add_foreign_key "identities", "users"
add_foreign_key "images", "users"
add_foreign_key "legislation_draft_versions", "legislation_processes" add_foreign_key "legislation_draft_versions", "legislation_processes"
add_foreign_key "locks", "users" add_foreign_key "locks", "users"
add_foreign_key "managers", "users" add_foreign_key "managers", "users"

View File

@@ -319,14 +319,20 @@ FactoryGirl.define do
valuation_finished true valuation_finished true
end end
trait :with_descriptive_image do
association :image, factory: :image
end
end end
factory :image, class: 'Image' do factory :image do
attachment { File.new("spec/fixtures/files/clippy.jpg") } attachment { File.new("spec/fixtures/files/clippy.jpg") }
title "Lorem ipsum dolor sit amet" title "Lorem ipsum dolor sit amet"
association :user, factory: :user
trait :proposal_image do
association :imageable, factory: :proposal
end
trait :budget_investment_image do
association :imageable, factory: :budget_investment
end
end end
factory :budget_ballot, class: 'Budget::Ballot' do factory :budget_ballot, class: 'Budget::Ballot' do

View File

@@ -29,17 +29,17 @@ feature 'Budget Investments' do
end end
scenario 'Index should show investment descriptive image only when is defined' do scenario 'Index should show investment descriptive image only when is defined' do
investment = FactoryGirl.create(:budget_investment, heading: heading) investment = create(:budget_investment, heading: heading)
investment_with_image = FactoryGirl.create(:budget_investment, :with_descriptive_image, heading: heading) investment_with_image = create(:budget_investment, heading: heading)
image = create(:image, imageable: investment_with_image)
visit budget_investments_path(budget, heading_id: heading.id) visit budget_investments_path(budget, heading_id: heading.id)
within("#budget_investment_#{investment.id}") do within("#budget_investment_#{investment.id}") do
expect(page).not_to have_css("picture") expect(page).not_to have_css("img")
end end
within("#budget_investment_#{investment_with_image.id}") do within("#budget_investment_#{investment_with_image.id}") do
expect(page).to have_css("picture img[alt='#{investment_with_image.image.title}']") expect(page).to have_css("img[alt='#{investment_with_image.image.title}']")
end end
end end
@@ -370,16 +370,6 @@ feature 'Budget Investments' do
expect(page).not_to have_selector ".js-follow" expect(page).not_to have_selector ".js-follow"
end end
scenario "Show descriptive image when exists" do
user = create(:user)
login_as(user)
investment_with_image = create(:budget_investment, :with_descriptive_image, heading: heading)
visit budget_investment_path(budget_id: budget.id, id: investment_with_image.id)
expect(page).to have_css("img[alt='#{investment_with_image.image.title}']")
end
scenario "Show back link contains heading id" do scenario "Show back link contains heading id" do
investment = create(:budget_investment, heading: heading) investment = create(:budget_investment, heading: heading)
visit budget_investment_path(budget, investment) visit budget_investment_path(budget, investment)
@@ -387,241 +377,6 @@ feature 'Budget Investments' do
expect(page).to have_link "Go back", href: budget_investments_path(budget, heading_id: investment.heading) expect(page).to have_link "Go back", href: budget_investments_path(budget, heading_id: investment.heading)
end end
context "Show investment image button" do
scenario "Should show add text when investment has not image" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Add image"
end
scenario "Should show edit text when investment has already an image" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(author)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Edit image"
end
scenario "Should not be shown for anonymous users" do
investment = create(:budget_investment, heading: heading)
visit budget_investment_path(budget, investment)
expect(page).not_to have_link "Edit image", href: edit_image_budget_investment_path(budget, investment)
end
scenario "Should not be shown when current user is not investment author" do
investment = create(:budget_investment, heading: heading)
visit budget_investment_path(budget, investment)
expect(page).not_to have_link "Edit image", href: edit_image_budget_investment_path(budget, investment)
end
scenario "Should be shown when current user is investment author" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Add image", href: edit_image_budget_investment_path(budget, investment)
end
scenario "Should be shown when current user is administrator" do
administrator = create(:administrator).user
investment = create(:budget_investment, heading: heading, author: author)
login_as(administrator)
visit budget_investment_path(budget, investment)
expect(page).to have_link "Add image", href: edit_image_budget_investment_path(budget, investment)
end
end
scenario "Create new investment project should show image title validation errors when image selected and title blank" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit new_budget_investment_path(budget)
select 'Health: More hospitals', from: 'budget_investment_heading_id'
fill_in 'budget_investment_title', with: 'Build a skyscraper'
fill_in 'budget_investment_description', with: 'I want to live in a high tower over the clouds'
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
check 'budget_investment_terms_of_service'
click_button "Create Investment"
expect(page).to have_content "can't be blank, is too short (minimum is 4 characters)"
end
scenario "Create new investment project should show image validation error when image selected is too small" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit new_budget_investment_path(budget)
select 'Health: More hospitals', from: 'budget_investment_heading_id'
fill_in 'budget_investment_title', with: 'Build a skyscraper'
fill_in 'budget_investment_description', with: 'I want to live in a high tower over the clouds'
attach_file :budget_investment_image, "spec/fixtures/files/logo_header.jpg"
check 'budget_investment_terms_of_service'
click_button "Create Investment"
expect(page).to have_content "Image dimensions are too small. For a good quality please upload a larger image. Minimum width: 475px, minimum height: 475px."
end
scenario "Edit image page should show image title validation errors when image selected and title blank" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
click_button "Save image"
expect(page).to have_content "can't be blank, is too short (minimum is 4 characters)"
end
scenario "Edit image page should show image validation error when image selected is too small" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
attach_file :budget_investment_image, "spec/fixtures/files/logo_header.jpg"
click_button "Save image"
expect(page).to have_content "Image dimensions are too small. For a good quality please upload a larger image. Minimum width: 475px, minimum height: 475px."
end
scenario "Edit image page should not be accesible when there is no logged user" do
investment = create(:budget_investment, heading: heading, author: author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "You must sign in or register to continue"
end
scenario "Edit image page should redirect to investment show page if logged user is not the author" do
other_author = create(:user, :level_two, username: 'Manuel')
investment = create(:budget_investment, heading: heading, author: author)
login_as(other_author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "You do not have permission to carry out the action 'edit_image' on budget/investment."
end
scenario "Edit image page should be accesible when author is currently logged" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "Change your project image"
end
scenario "Edit image page should be accesible when there is logged any administrator" do
administrator = create(:administrator).user
investment = create(:budget_investment, heading: heading, author: author)
login_as(administrator)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "Change your project image"
end
scenario "Remove image button should not be present when investment image does not exists" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).not_to have_link "Remove image"
end
scenario "Remove image button should be present when investment has an image defined" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_link "Remove image"
end
scenario "Remove image button should be possible for administrators" do
administrator = create(:administrator).user
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(administrator)
visit edit_image_budget_investment_path(budget, investment)
click_link "Remove image"
expect(page).to have_content "Investment project image removed succesfully."
end
scenario "Remove image should be possible for investment author" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(budget, investment)
click_link "Remove image"
expect(page).to have_content "Investment project image removed succesfully."
end
scenario "Remove image should not be possible for any other logged users (except administrators and author)" do
investment = create(:budget_investment, :with_descriptive_image, heading: heading, author: author)
login_as(create(:user))
visit edit_image_budget_investment_path(budget, investment)
expect(page).to have_content "You do not have permission to carry out the action 'edit_image' on budget/investment."
end
scenario "Update image should not be possible if logged user is not the author" do
other_author = create(:user, :level_two, username: 'Manuel')
investment = create(:budget_investment, heading: heading, author: author)
login_as(other_author)
visit edit_image_budget_investment_path(investment.budget, investment)
expect(current_path).not_to eq(edit_image_budget_investment_path(investment.budget, investment))
expect(page).to have_content 'You do not have permission'
end
scenario "Update image should be possible for authors" do
investment = create(:budget_investment, heading: heading, author: author)
login_as(author)
visit edit_image_budget_investment_path(investment.budget, investment)
fill_in :budget_investment_image_title, with: "New image title"
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
click_on "Save image"
within ".budget-investment-show" do
expect(page).to have_css("img[src*='clippy.jpg']")
end
expect(page).to have_content 'Investment project image updated succesfully. '
end
scenario "Update image should be possible for administrators" do
administrator = create(:administrator).user
investment = create(:budget_investment, heading: heading, author: author)
login_as(administrator)
visit edit_image_budget_investment_path(investment.budget, investment)
fill_in :budget_investment_image_title, with: "New image title"
attach_file :budget_investment_image, "spec/fixtures/files/clippy.jpg"
click_on "Save image"
within ".budget-investment-show" do
expect(page).to have_css("img[src*='clippy.jpg']")
end
expect(page).to have_content 'Investment project image updated succesfully. '
end
context "Show (feasible budget investment)" do context "Show (feasible budget investment)" do
let(:investment) do let(:investment) do
create(:budget_investment, create(:budget_investment,
@@ -707,6 +462,8 @@ feature 'Budget Investments' do
end end
end end
it_behaves_like "imageable", "budget_investment", "budget_investment_path", { "budget_id": "budget_id", "id": "id" }
it_behaves_like "followable", "budget_investment", "budget_investment_path", { "budget_id": "budget_id", "id": "id" } it_behaves_like "followable", "budget_investment", "budget_investment_path", { "budget_id": "budget_id", "id": "id" }
it_behaves_like "documentable", "budget_investment", "budget_investment_path", {"budget_id": "budget_id", "id": "id"} it_behaves_like "documentable", "budget_investment", "budget_investment_path", {"budget_id": "budget_id", "id": "id"}

View File

@@ -1272,6 +1272,8 @@ feature 'Proposals' do
expect(Flag.flagged?(user, proposal)).to_not be expect(Flag.flagged?(user, proposal)).to_not be
end end
it_behaves_like "imageable", "proposal", "proposal_path", { "id": "id" }
it_behaves_like "followable", "proposal", "proposal_path", { "id": "id" } it_behaves_like "followable", "proposal", "proposal_path", { "id": "id" }
it_behaves_like "documentable", "proposal", "proposal_path", { "id": "id" } it_behaves_like "documentable", "proposal", "proposal_path", { "id": "id" }

View File

@@ -20,6 +20,9 @@ describe "Abilities::Administrator" do
let(:budget_investment_document) { build(:document, documentable: budget_investment) } let(:budget_investment_document) { build(:document, documentable: budget_investment) }
let(:poll_question_document) { build(:document, documentable: poll_question) } let(:poll_question_document) { build(:document, documentable: poll_question) }
let(:proposal_image) { build(:image, imageable: proposal) }
let(:budget_investment_image) { build(:image, imageable: budget_investment) }
let(:hidden_debate) { create(:debate, :hidden) } let(:hidden_debate) { create(:debate, :hidden) }
let(:hidden_comment) { create(:comment, :hidden) } let(:hidden_comment) { create(:comment, :hidden) }
let(:hidden_proposal) { create(:proposal, :hidden) } let(:hidden_proposal) { create(:proposal, :hidden) }
@@ -82,6 +85,10 @@ describe "Abilities::Administrator" do
it { should be_able_to(:create, proposal_document) } it { should be_able_to(:create, proposal_document) }
it { should be_able_to(:destroy, proposal_document) } it { should be_able_to(:destroy, proposal_document) }
it { should be_able_to(:new, proposal_image) }
it { should be_able_to(:create, proposal_image) }
it { should be_able_to(:destroy, proposal_image) }
it { should be_able_to(:new, budget_investment_document) } it { should be_able_to(:new, budget_investment_document) }
it { should be_able_to(:create, budget_investment_document) } it { should be_able_to(:create, budget_investment_document) }
it { should be_able_to(:destroy, budget_investment_document) } it { should be_able_to(:destroy, budget_investment_document) }
@@ -89,4 +96,9 @@ describe "Abilities::Administrator" do
it { should be_able_to(:new, poll_question_document) } it { should be_able_to(:new, poll_question_document) }
it { should be_able_to(:create, poll_question_document) } it { should be_able_to(:create, poll_question_document) }
it { should be_able_to(:destroy, poll_question_document) } it { should be_able_to(:destroy, poll_question_document) }
it { should be_able_to(:new, budget_investment_image) }
it { should be_able_to(:create, budget_investment_image) }
it { should be_able_to(:destroy, budget_investment_image) }
end end

View File

@@ -29,82 +29,7 @@ describe Budget::Investment do
end end
end end
describe "#image and #image_title", :investment_image do it_behaves_like "acts as imageable", "budget_investment_image"
let(:investment_with_image) { build(:budget_investment, :with_descriptive_image) }
it "should be valid" do
expect(investment_with_image).to be_valid
end
describe "file extension" do
it "should not be valid with '.png' extension" do
investment_with_image.image = File.new("spec/fixtures/files/clippy.png")
expect(investment_with_image).to_not be_valid
expect(investment_with_image.errors[:image].size).to eq(1)
end
it "should not be valid with '.gif' extension" do
investment_with_image.image = File.new("spec/fixtures/files/clippy.gif")
expect(investment_with_image).to_not be_valid
expect(investment_with_image.errors[:image].size).to eq(1)
end
it "should be valid with '.jpg' extension" do
investment_with_image.image = File.new("spec/fixtures/files/clippy.jpg")
expect(investment_with_image).to be_valid
end
end
describe "image dimmessions" do
it "should be valid when image dimmessions are 475X475 at least" do
expect(investment_with_image).to be_valid
end
it "should not be valid when image dimmensions are smaller than 475X475" do
investment_with_image.image = File.new("spec/fixtures/files/logo_header.jpg")
expect(investment_with_image).not_to be_valid
end
end
describe "title" do
it "should be valid when image and image_title are not defined" do
investment_with_image.image = nil
investment_with_image.image_title = nil
expect(investment_with_image).to be_valid
end
it "should not be valid when correct image attached but no image title provided" do
investment_with_image.image_title = ''
expect(investment_with_image).to_not be_valid
end
it "should not be valid when image title is too short" do
investment_with_image.image_title = 'a'*3
expect(investment_with_image).to_not be_valid
end
it "should not be valid when image title is too long" do
investment_with_image.image_title = 'a'*81
expect(investment_with_image).to_not be_valid
end
end
it "image destroy should remove image from file storage" do
image_url = investment_with_image.image.url
expect{ investment_with_image.image.destroy }.to change{ investment_with_image.image.url }.from(image_url).to("/images/original/missing.png")
end
end
it "sanitizes description" do it "sanitizes description" do
investment.description = "<script>alert('danger');</script>" investment.description = "<script>alert('danger');</script>"

View File

@@ -304,7 +304,7 @@ shared_examples "documentable" do |documentable_factory_name, documentable_path,
from: send(documentable_path, arguments)) from: send(documentable_path, arguments))
expect(page).to have_content "You can upload up to a maximum of #{max_file_size(documentable)} documents." expect(page).to have_content "You can upload up to a maximum of #{max_file_size(documentable)} documents."
expect(page).to have_content "You can upload #{humanized_accepted_content_types(documentable)} files." expect(page).to have_content "You can upload #{documentable_humanized_accepted_content_types(documentable)} files."
expect(page).to have_content "You can upload files up to #{max_file_size(documentable)} MB." expect(page).to have_content "You can upload files up to #{max_file_size(documentable)} MB."
end end

View File

@@ -0,0 +1,331 @@
shared_examples "imageable" do |imageable_factory_name, imageable_path, imageable_path_arguments|
include ActionView::Helpers
include ImagesHelper
include ImageablesHelper
let!(:administrator) { create(:user) }
let!(:user) { create(:user) }
let!(:arguments) { {} }
let!(:imageable) { create(imageable_factory_name, author: user) }
let!(:imageable_dom_name) { imageable_factory_name.parameterize }
before do
create(:administrator, user: administrator)
imageable_path_arguments.each do |argument_name, path_to_value|
arguments.merge!("#{argument_name}": imageable.send(path_to_value))
end
end
context "Show" do
scenario "Show descriptive image when exists", :js do
image = create(:image, imageable: imageable)
visit send(imageable_path, arguments)
expect(page).to have_css("img[alt='#{image.title}']")
end
scenario "Show image title when image exists" do
image = create(:image, imageable: imageable)
visit send(imageable_path, arguments)
expect(page).to have_content image.title
end
scenario "Should not display upload image button when there is no logged user" do
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).not_to have_link("Upload image")
end
end
scenario "Should not display upload image button when maximum number of images reached " do
create_list(:image, 3, imageable: imageable)
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).not_to have_link("Upload image")
end
end
scenario "Should display upload image button when user is logged in and is imageable owner" do
login_as(user)
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).to have_link("Upload image")
end
end
scenario "Should display upload image button when admin is logged in" do
login_as(administrator)
visit send(imageable_path, arguments)
within "##{dom_id(imageable)}" do
expect(page).to have_link("Upload image")
end
end
scenario "Should navigate to new image page when click un upload button" do
login_as(user)
visit send(imageable_path, arguments)
click_link "Upload image"
expect(page).to have_selector("h1", text: "Upload image")
end
end
context "New" do
scenario "Should not be able for unathenticated users" do
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
expect(page).to have_content("You must sign in or register to continue.")
end
scenario "Should not be able for other users" do
login_as create(:user)
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
expect(page).to have_content("You do not have permission to carry out the action 'new' on image. ")
end
scenario "Should be able to imageable author" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
expect(page).to have_selector("h1", text: "Upload image")
end
scenario "Should display file name after file selection", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/empty.pdf", make_visible: true
sleep 1
expect(page).to have_content "empty.pdf"
end
scenario "Should not display file name after file selection", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/logo_header.png", make_visible: true
sleep 1
expect(page).not_to have_content "logo_header.jpg"
end
scenario "Should update loading bar style after valid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(page).to have_selector ".loading-bar.complete"
end
scenario "Should update loading bar style after unvalid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/logo_header.png", make_visible: true
sleep 1
expect(page).to have_selector ".loading-bar.errors"
end
scenario "Should update image title with attachment original file name after file selection if no title defined by user", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(find("input[name='image[title]']").value).to eq("clippy.jpg")
end
scenario "Should not update image title with attachment original file name after file selection when title already defined by user", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
fill_in :image_title, with: "My custom title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(find("input[name='image[title]']").value).to eq("My custom title")
end
scenario "Should update image cached_attachment field after valid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg", make_visible: true
sleep 1
expect(find("input[name='image[cached_attachment]']", visible: false).value).to include("clippy.jpg")
end
scenario "Should not update image cached_attachment field after unvalid file upload", :js do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
attach_file :image_attachment, "spec/fixtures/files/logo_header.png", make_visible: true
sleep 1
expect(find("input[name='image[cached_attachment]']", visible: false).value).to eq ""
end
scenario "Should show imageable custom recomentations" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
expect(page).to have_content "You can upload only one image. Proposals with image attract more attention of the users."
expect(page).to have_content "You can upload #{imageable_humanized_accepted_content_types} image."
expect(page).to have_content "You can upload one image up to #{imageable_max_file_size} MB."
end
end
context "Create" do
scenario "Should show validation errors" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id)
click_on "Upload image"
expect(page).to have_content "3 errors prevented this Image from being saved: "
expect(page).to have_selector "small.error", text: "can't be blank", count: 2
end
scenario "Should show error notice after unsuccessfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
attach_file :image_attachment, "spec/fixtures/files/empty.pdf"
sleep 1
click_on "Upload image"
expect(page).to have_content "Cannot create image. Check form errors and try again."
end
scenario "Should show success notice after successfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
fill_in :image_title, with: "Image title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg"
sleep 1
click_on "Upload image"
expect(page).to have_content "Image was created successfully."
end
scenario "Should redirect to imageable path after successfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
fill_in :image_title, with: "Image title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg"
sleep 1
click_on "Upload image"
within "##{dom_id(imageable)}" do
expect(page).to have_selector "h1", text: imageable.title
end
end
scenario "Should show new image on imageable images tab after successfull image upload" do
login_as imageable.author
visit new_image_path(imageable_type: imageable.class.name,
imageable_id: imageable.id,
from: send(imageable_path, arguments))
fill_in :image_title, with: "Image title"
attach_file :image_attachment, "spec/fixtures/files/clippy.jpg"
sleep 1
click_on "Upload image"
expect(page).to have_content "Image title"
end
end
context "Destroy" do
let!(:image) { create(:image, imageable: imageable, user: imageable.author) }
scenario "Should show success notice after successfull deletion by an admin" do
login_as administrator
visit send(imageable_path, arguments)
click_on "Remove image"
expect(page).to have_content "Image was deleted successfully."
end
scenario "Should show success notice after successfull deletion" do
login_as imageable.author
visit send(imageable_path, arguments)
click_on "Remove image"
expect(page).to have_content "Image was deleted successfully."
end
scenario "Should not show image after successful deletion" do
login_as imageable.author
visit send(imageable_path, arguments)
click_on "Remove image"
expect(page).not_to have_selector "figure img"
end
scenario "Should redirect to imageable path after successful deletion" do
login_as imageable.author
visit send(imageable_path, arguments)
click_on "Remove image"
within "##{dom_id(imageable)}" do
expect(page).to have_selector "h1", text: imageable.title
end
end
end
end

View File

@@ -0,0 +1,76 @@
shared_examples "acts as imageable" do |imageable_factory|
let!(:image) { build(:image, imageable_factory.to_sym) }
let!(:imageable) { image.imageable }
it "should be valid" do
expect(image).to be_valid
end
describe "file extension" do
it "should not be valid with '.png' extension" do
image.attachment = File.new("spec/fixtures/files/clippy.png")
expect(image).to_not be_valid
expect(image.errors[:attachment].size).to eq(1)
end
it "should not be valid with '.gif' extension" do
image.attachment = File.new("spec/fixtures/files/clippy.gif")
expect(image).to_not be_valid
expect(image.errors[:attachment].size).to eq(1)
end
it "should be valid with '.jpg' extension" do
image.attachment = File.new("spec/fixtures/files/clippy.jpg")
expect(image).to be_valid
end
end
describe "image dimmessions" do
it "should be valid when image dimmessions are 475X475 at least" do
expect(image).to be_valid
end
it "should not be valid when image dimmensions are smaller than 475X475" do
image.attachment = File.new("spec/fixtures/files/logo_header.jpg")
expect(image).not_to be_valid
end
end
describe "title" do
it "should not be valid when correct image attached but no image title provided" do
image.title = ''
expect(image).to_not be_valid
end
it "should not be valid when image title is too short" do
image.title = 'a' * 3
expect(image).to_not be_valid
end
it "should not be valid when image title is too long" do
image.title = 'a' * 81
expect(image).to_not be_valid
end
end
it "image destroy should remove image from file storage" do
image.save
image_url = image.attachment.url
expect{ image.attachment.destroy }.to change{ image.attachment.url }.from(image_url).to("/attachments/original/missing.png")
end
end