Download link on Paperclip Attachment in Rails_admin - ruby

I am using rails_admin gem together with Paperclip. My model looks like this:
class Product < ActiveRecord::Base
has_attached_file :asset,
:styles => {
:thumb => "100x100#",
:small => "150x150>",
:medium => "200x200" }
validates_attachment_content_type :asset, :content_type => /\Aimage\/.*\Z/
end
How can I include a download link into the index action? So that, on admin/products every entry in the table will have a download link? I read through the documentation, but they don't seem to specify any of these features.
[EDIT]
On my main index action which was routed here: /products I used to do:
<%= link_to "Download", product.asset.url(:original, false) %>

You just need to do.
<%= link_to "Download", product.asset(:original) %>
or
<%= link_to "Download", product.asset.url(:original) %>
They both do the same thing.
If you want to change what version of the image they download just change :original to :medium, :small or :thumb.
For Rails Admin do following:
config.model "Product" do
list do
....
field :download do
formatted_value do
bindings[:view].tag(:a, href: bindings[:object].assets(:original)) << "Download"
end
end
end
...
end

[SOLVED]
Submission model:
class Submission < ActiveRecord::Base
# Image attachment and validations
has_attached_file :file,
:url => "/files/:class/:attachment/:id/:style/:basename.:extension",
:path => ":rails_root/public/files/:class/:attachment/:id/:style/:basename.:extension"
validates_attachment_content_type :file, :content_type => 'application/pdf'
end

Related

How to use Simple Form and Direct Upload ActiveStorage

I'm having a trouble when I try to use Simple Form gem for upload video. I'm using ActiveStorage and local storage for this.
My form looks like this:
= simple_form_for #film do |f|
= f.error_notification
= f.input :title, as: :string
= f.input :description, as: :string
= f.input :cover_img, as: :file
= f.input :film_link, as: :file, direct_upload: true
= f.button :submit
= link_to 'back', :back, class: 'btn btn-secondary'
I've followed instruction on here https://edgeguides.rubyonrails.org/active_storage_overview.html
So I've included js and css files in my app. But that won't work. It looks like there's some troubles with passing direct_upload: true via simple_form.
I've also find article https://phase2online.com/blog/2018/10/03/easily-upload-files-with-active-storage-in-rails-5-2/ and pull git repo from here
An this works on when you'll use form_for on your _form. When I change form to use simple_form gem(instead of form_form use simple_form_for) it won't work.
Anyone have an idea why this is not working please?
I use ruby 2.6.3 and Rails 5.2.3 and simple_form (5.0.1)
There're is the way to make this work.
We can change f.input to f.file_field as hashrocket suggest - but then validation of simple form will not work, and perhaps we have to add class to this input plus extra div before.
We can add html attribute to the f.input.
For me this is working I hope it will help someone else too.
= simple_form_for #film do |f|
= f.error_notification
= f.input :title, as: :string
= f.input :description, as: :string
= f.input :cover_img, as: :file, input_html: { data: { direct_upload_url: '/rails/active_storage/direct_uploads' } }
= f.input :film_link, as: :file, input_html: { data: { direct_upload_url: '/rails/active_storage/direct_uploads' } }
= f.button :submit
= link_to 'back', :back, class: 'btn btn-secondary'
These days (rails 6.x), you shouldn't need to specify as: file (simple form should figure this out via reflection) and you can just pass direct_upload: true to input_html:
= simple_form_for #film do |f|
= f.error_notification
= f.input :title, as: :string
= f.input :description, as: :string
= f.input :cover_img, input_html: { direct_upload: true }
= f.input :film_link, input_html: { direct_upload: true }
= f.button :submit
= link_to 'back', :back, class: 'btn btn-secondary'
Verify it's doing the right thing by looking at the generated html. You should see an attribute like this on each input which is of type="file":
data-direct-upload-url="https://<your app server>/rails/active_storage/direct_uploads"
#anka's suggestion to create a custom input type is a good one, but you could also edit the default simple form wrappers for file types if you know all your files will be direct uploads to S3.
I would suggest to simply add a custom input for that to avoid repetition within your view files. Put this in the file app/inputs/direct_upload_file_input.rb:
# frozen_string_literal: true
class DirectUploadFileInput < SimpleForm::Inputs::FileInput
def input_html_options
super.merge({ direct_upload: true })
end
end

Rails3 devise / omniauth - password_required? and form_for :validate => true don't work together

I'm using devise, omniauth and client-side-validation like described in ryan bates railscasts.
I'm now facing a problem with the password validation which should be omitted when registering via omniauth 3rd party provider.
Registration form (html.erb):
Sign up Register
via 3rd party networks <%= form_for(resource, :as => resource_name, :url =>
registration_path(resource_name), :validate => true) do |f| %>
Register directly <%= f.label :firstname %> <%=
f.text_field :firstname %> <%= f.label :lastname %> <%=
f.text_field :lastname %> <%= f.label :email %> <%=
f.text_field :email %> <%= f.label :password %> <%=
f.password_field :password %> <%= f.label
:password_confirmation %> <%= f.password_field :password_confirmation
%> <%= f.submit "Register now", :class => "button-big" %>
<% end %>
My user model has a - see UPDATE below
validates :password, :presence => true, :confirmation =>true
clause and the password_required? definition
def password_required?
(authentications.empty? || !password.blank?)
end
When I'm registering via omniauth 3rd party provider the registration form correctly pops up and the user is asked to enter an email address. Unfortunately the user has to enter a password although he shouldn't be prompted due to registration via 3rd party.
Can anybody give me a hint, how to accomplish?
Thanks and best regards
Jan
UPDATE: to give a more specific view I added some more code snippets
AuthenticationsController:
class AuthenticationsController < ApplicationController
def index
#authentications = current_user.authentications if current_user
end
def create
omniauth = request.env["omniauth.auth"]
authentication = Authentication.find_by_provider_and_uid(omniauth['provider'], omniauth['uid'])
if authentication
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, authentication.user)
elsif current_user
current_user.authentications.create(:provider => omniauth['provider'], :uid => omniauth['uid'])
flash[:notice] = "Authentication successful."
redirect_to authentications_url
else
user = User.new
user.apply_omniauth(omniauth)
if user.save
flash[:notice] = "Signed in successfully."
sign_in_and_redirect(:user, user)
else
session[:omniauth] = omniauth.except('extra')
redirect_to new_user_registration_url
end
end
end
def destroy
#authentication = current_user.authentications.find(params[:id])
#authentication.destroy
flash[:notice] = "Successfully destroyed authentication."
redirect_to authentications_url
end
end
RegistrationsController:
class RegistrationsController < Devise::RegistrationsController
def create
super
session[:omniauth] = nil unless #user.new_record?
end
private
def build_resource(*args)
super
if session[:omniauth]
#user.apply_omniauth(session[:omniauth])
#user.valid?
end
end
end
User Model:
class User < ActiveRecord::Base
# associations
has_many :authentications
# Include default devise modules. Others available are:
# :token_authenticatable, :encryptable, :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable,
:validatable, :email_regexp => /^([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})$/i
# Setup accessible (or protected) attributes for your model
attr_accessible :firstname, :lastname, :email, :password, :password_confirmation, :remember_me
# validations
validates :firstname, :presence => true
validates :lastname, :presence => true
validates :email, :presence => true, :uniqueness => true
validates_email :email
validates :password, :presence => true, :confirmation =>true
# omniauth reference 3rd party
def apply_omniauth(omniauth)
if self.firstname.blank?
self.firstname = omniauth['info']['first_name'].presence || omniauth['info']['name'].presence || " "
end
if self.lastname.blank?
self.lastname = omniauth['info']['last_name'].presence || omniauth['info']['name'].presence || " "
end
if self.email.blank?
self.email = omniauth['info']['email'].presence
end
authentications.build(:provider => omniauth['provider'], :uid => omniauth['uid'])
end
# omit validation for omniauth session
def password_required?
(authentications.empty? || !password.blank?) && super
end
end
Authentication Model
class Authentication < ActiveRecord::Base
# associations
belongs_to :user
# Setup accessible (or protected) attributes for your model
attr_accessible :user_id, :provider, :uid
end
While debugging I found out that the 'authentications.build()' in the 'apply_omniauth(omniauth)' method produces an empty object, so that the 'password_required?' is always be true and a password must be provided.
ADDITIONAL QUESTION:
why does 'authentications.empty?' always return 'false'?
Thanks in advance
Jan
First of all, the password_required? method should be defined like this:
def password_required?
(authentications.empty? || !password.blank?) && super
end
Then, in your view, you should wrap your password fields like this:
# app/views/registrations/new.html.erb
<% if #user.password_required? %>
<%= f.label :password %>
<%= f.password_field :password %>
<%= f.label :password_confirmation %>
<%= f.password_field :password_confirmation %>
<% else %>
...
<% end %>
Then in your controller or model, when creating an authentication method for the first time, you should use build and not create or new because that way authentications.empty? will return true and you'll be asked to enter a password.
Finally, as for validations, you should take a look at the client_side_validations' wiki about custom validations. You'll probably need to override the password validation.

Paperclip "Gem" Rails 3.1 undefined method model file name

Using rails 3.1.1, Ruby 1.9.2,
Gems: gem 'haml', gem 'simple_form', gem 'aws-sdk',
gem 'paperclip', :git => "git://github.com/thoughtbot/paperclip.git"
plugin: country_select: git://github.com/rails/country_select.git
Having an issue uploading/displaying images pushed to Amazon S3 through paperclip (GEM)
Error: undefined method `avatar_file_name' for #Player:0x00000102aff228
For the most part I was following the example on the git-hub page for paperclip
https://github.com/thoughtbot/paperclip
Here is what I have in my code:
Migration: 20111224044508_create_players.rb
class CreatePlayers < ActiveRecord::Migration
def change
create_table :players do |t|
t.string :first_name
t.boolean :first_name_public, :default => false
...
t.string :website
t.boolean :website_public, :default => false
t.has_attached_file :avatar
t.timestamps
end
end
end
Model: Player.rb:
class Player < ActiveRecord::Base
attr_accessible :first_name, ... :website
validates_presence_of :username, :email
has_attached_file :avatar,
:styles => { :medium => "300x300>", :thumb => "100x100>" },
:storage => :s3,
:s3_credentials => "#{Rails.root}/config/s3.yml",
:path => ":class/:id/:style/:filename"
{Unrelated validations}
end
S3 file: s3.yml
development:
bucket: voh_development
access_key_id: *********************
secret_access_key: ********************
staging:
bucket: voh_staging
access_key_id: *********************
secret_access_key: ********************
production:
bucket: voh_production
access_key_id: *********************
secret_access_key: ********************
Controller: players_controller.rb
class PlayersController < ApplicationController
def create
#player = Player.create(params[:player])
if #player.save
redirect_to players_path, :notice => "Player Created";
else
render :action => 'new'
end
end
{basic restful}
end
Views:
Edit.html.haml + New.html.haml
= simple_form_for #player do |f|
= f.input :first_name
...
= f.input :website
= f..file_field :avatar
.input_div
= f.button :submit
index.html.haml
...
%td Avatar
%td First Name
...
%td Actions
- #players.each do |player|
%tr
%td
= image_tag #player.avatar.url(:thumb)
%td
= player.first_name
...
%td
= link_to ' Show ', player_path(player.id)
|
= link_to ' Edit ', edit_player_path(player.id)
show.html.haml
= image_tag #user.avatar.url
%br
= #player.first_name
...
Research:
I found a lot to do with the pluging and genration of the migration but it all seems old. Most of them suggest putting in the up down in the migration for the 4 attributes. However it seems that should have been replaced by the one line t.has_attached_file :avatar.
I have a rails 3.0 project and this worked. I am able to upload products and pull them back down. (had to play with the suggested image_tag #icon.avatar.url and turned it into %img{:src => URI.unescape(icon.icon.url)} but that a different question.)
TLDR: Fixed typo in index.html.haml from #player => player, Added :avatar to attr_accessible.
I woke up this morning and had a different error.
instead of: undefined method Avatar_file_name'
I got: undefined method avatar' for nil:NilClass
That error was caused buy a simple type in my code. I used an instance vairable instead of .each variable I should have been using (index.html.haml:9)
Now the app was not erring out but the file was still not uploading.
In the development log I found this. (I did not look here the first time I posted)
WARNING: Can't mass-assign protected attributes: avatar
I then went and added :Avatar to attr_accessible and everything started working.
Not sure if this is supposed to be required or not but I did see that they had updated S3 header to be a proc yesterday.
https://github.com/thoughtbot/paperclip/tree/master/test/storage
I am not going to close this out yet. There will be an edit because I am going to play with the version and quickly report my findings today. Then I will close this out.
Edit:
I tryed switch back to 2.4.5 and I am getting the error that made me switch to pulling master in the first place. When attempting to do a migration with t.has_attached_file :avatar it fails to migrate and gives the following error.
undefined method `has_attached_file' for #ActiveRecord::ConnectionAdapters::PostgreSQLAdapter::TableDefinition:0x00000105053600
I think I will stick with pulling from master.

Getting best_in_place gem to co-operate with Redcarpet markdown

For my markdown text i normally do the following on the text content(#user.content):
<%= raw Redcarpet.new(#user.content).to_html.html_safe %>
However i am now using the best_in_place gem like this
<%= best_in_place(#question, :statement, :path => {:controller => "users", :action => "update"}, :type => :textarea) %>
How can i integrate Redcarpet with this? i have tried this:
<%= Redcarpet.new(best_in_place(#question, :statement, :path => {:controller => "users", :action => "update"}, :type => :textarea)).to_html.html_safe %>
however the output is not what is expected. The best_in_place readme seems to point to a way to solve this, but i don't understand it here, under the section titled "Using custom display methods". Can someone explain to me how i can make this work?
I have never used the best_in_place gem, so I don't have tried it out. But from the documentation, you should do the following steps:
Add to the call to best_in_place the additional argument :display_as.
Implement the method referenced by :display_as in your model object.
Here are the details:
Additional argument
%= best_in_place(#question, :statement, :display_as => 'mk_statement', :path => {:controller => "users", :action => "update"}, :type => :textarea) %>
Implementation in the model
class Question
require 'redcarpet'
def mk_statement
Redcarpet.new(self.statement).to_html.html_safe
end
You can also use the display_with attribute and pass it a proc like so:
<%= best_in_place(#question, :statement, :display_with => proc { |v| Redcarpet.new(v).to_html.html_safe, :path => {:controller => "users", :action => "update"}, :type => :textarea) %>

Configure the label of Active Admin has_many

Well I have a two models related with a on-to-many assoc.
#models/outline.rb
class Outline < ActiveRecord::Base
has_many :documents
end
#models/document.rb
class Document < ActiveRecord::Base
belongs_to :outline
end
#admin/outlines.rb
ActiveAdmin.register Outline do
form do |f|
f.inputs "Details" do
f.input :name, :required => true
f.input :pages, :required => true
...
f.buttons
end
f.inputs "Document Versions" do
f.has_many :documents, :name => "Document Versions" do |d|
d.input :file, :as => :file
d.buttons do
d.commit_button :title => "Add new Document Version"
end
end
end
end
end
Well as you can see in the admin/outlines.rb I already tried setting up the :name, in the has_many :documents, and the :title in the commit_button, but neither of that options work, I also tried with :legend, :title, and :label, instead of :name in the .has_many. Not working.
This is the result of that code:
Screenshot
What I want to display is "Document Versions" instead of "Documents", and "Add new Document Version" instead of "Add new Document"
If someone can have a solution it would be great
To set has_many header you can use
f.has_many :images, heading: 'My images' do |i|
i.input :src, label: false
end
See here
Looking at ActiveAdmin tests("should translate the association name in header"), there may be another way of doing this. Use your translation file.
If you look at ActiveAdmin has_many method (yuck!!! 46 lines of sequential code), it uses ActiveModel's human method.
Try adding this to your translation file
en:
activerecord:
models:
document:
one: Document Version
other: Document Versions
One quick hack is that you can hide the h3 tag through its style.
assets/stylesheets/active_admin.css.scss
.has_many {
h3 {
display: none;
}}
This will hide any h3 tag under a has_many class.
You can customise the label of the "Add..." button by using the new_record setting on has_many. For the heading label you can use heading:
f.has_many :documents,
heading: "Document Versions",
new_record: "Add new Document Version" do |d|
d.input :file, :as => :file
end
Sjors answer is actually a perfect start to solving the question. I monkeypatched Active Admin in config/initializers/active_admin.rb with the following:
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
def titled_has_many(association, options = {}, &block)
options = { :for => association }.merge(options)
options[:class] ||= ""
options[:class] << "inputs has_many_fields"
# Set the Header
header = options[:header] || association.to_s
# Add Delete Links
form_block = proc do |has_many_form|
block.call(has_many_form) + if has_many_form.object.new_record?
template.content_tag :li do
template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
end
else
end
end
content = with_new_form_buffer do
template.content_tag :div, :class => "has_many #{association}" do
form_buffers.last << template.content_tag(:h3, header.titlecase) #using header
inputs options, &form_block
# Capture the ADD JS
js = with_new_form_buffer do
inputs_for_nested_attributes :for => [association, object.class.reflect_on_association(association).klass.new],
:class => "inputs has_many_fields",
:for_options => {
:child_index => "NEW_RECORD"
}, &form_block
end
js = template.escape_javascript(js)
js = template.link_to I18n.t('active_admin.has_many_new', :model => association.to_s.singularize.titlecase), "#", :onclick => "$(this).before('#{js}'.replace(/NEW_RECORD/g, new Date().getTime())); return false;", :class => "button"
form_buffers.last << js.html_safe
end
end
form_buffers.last << content.html_safe
end
end
end
Now in my admin file I call titled_has_many just like has_many but I pass in :header to override the use of the Association as the h3 tag.
f.titled_has_many :association, header: "Display this as the H3" do |app_f|
#stuff here
end
Does not deserve a prize but you could put this in config/initializers/active_admin.rb . It will allow you to tweak the headers you want using a config/locales/your_file.yml (you should create the custom_translations entry yourself). Dont forget to restart the server. And use the f.hacked_has_many in your form builder.
module ActiveAdmin
class FormBuilder < ::Formtastic::FormBuilder
def hacked_has_many(association, options = {}, &block)
options = { :for => association }.merge(options)
options[:class] ||= ""
options[:class] << "inputs has_many_fields"
# Add Delete Links
form_block = proc do |has_many_form|
block.call(has_many_form) + if has_many_form.object.new_record?
template.content_tag :li do
template.link_to I18n.t('active_admin.has_many_delete'), "#", :onclick => "$(this).closest('.has_many_fields').remove(); return false;", :class => "button"
end
else
end
end
content = with_new_form_buffer do
template.content_tag :div, :class => "has_many #{association}" do
# form_buffers.last << template.content_tag(:h3, association.to_s.titlecase)
# CHANGED INTO
form_buffers.last << template.content_tag(:h3, I18n.t('custom_translations.'+association.to_s))
inputs options, &form_block
# Capture the ADD JS
js = with_new_form_buffer do
inputs_for_nested_attributes :for => [association, object.class.reflect_on_association(association).klass.new],
:class => "inputs has_many_fields",
:for_options => {
:child_index => "NEW_RECORD"
}, &form_block
end
js = template.escape_javascript(js)
_model = 'activerecord.models.' + association.to_s.singularize
_translated_model = I18n.t(_model)
js = template.link_to I18n.t('active_admin.has_many_new', :model => _translated_model), "#", :onclick => "$(this).before('#{js}'.replace(/NEW_RECORD/g, new Date().getTime())); return false;", :class => "button"
form_buffers.last << js.html_safe
end
end
form_buffers.last << content.html_safe
end
end
end
If you have issues with locale files not being loaded good in staging/production mode, adding this to your application.rb might help (substitute :nl for the right locale)
config.before_configuration do
I18n.load_path += Dir[Rails.root.join('config','locales','*.{rb,yml}').to_s]
I18n.locale = :nl
I18n.default_locale = :nl
config.i18n.load_path += Dir[Rails.root.join('config','locales','*.{rb,yml}').to_s]
config.i18n.locale = :nl
config.i18n.default_locale = :nl
I18n.reload!
config.i18n.reload!
end
config.i18n.locale = :nl
config.i18n.default_locale = :nl

Resources