How to use Simple Form and Direct Upload ActiveStorage - ruby

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

Related

Rails 5.2 ActionMailer Mail won't send. Can't see any error in server

Trying to send a volunteering form through SMTP using Gmail, Sendgrid or anything really however I have Action Mailer all set up and I presume I should really be seeing the email come through when I look at the email. I can see this error below but cannot see any error or receive any email at all. I've tried writing puts in my create action but I do not see those either so this is hopefully an easy fix as I'm missing something or have something not follow the usual Rails convention.
I'm using rails 5.2
What I see in the server:
Started GET "/volunteer?utf8=%E2%9C%93&authenticity_token=AZ%2FAflVf%2BlqRUYm45Jo82wAMpq%2B%2BY%2F93piLbeRXdK5n%2FQIWhuaUL3Oe2%2FSwzR%2FCLvj%2FAKW%2BBgD8dPd8vJYRDBA%3D%3D&volunteer_message%5Bname%5D=test+name&volunteer_message%5Bemail%5D=email%40test.com&volunteer_message%5Bphone_number%5D=88888888&volunteer_message%5Bbody%5D=test+email&commit=Send" for 127.0.0.1 at 2018-10-10 17:29:42 +0100
Processing by PagesController#volunteer as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"AZ/AflVf+lqRUYm45Jo82wAMpq++Y/93piLbeRXdK5n/QIWhuaUL3Oe2/SwzR/CLvj/AKW+BgD8dPd8vJYRDBA==", "volunteer_message"=>{"name"=>"test name", "email"=>"email#test.com", "phone_number"=>"88888888", "body"=>"test email"}, "commit"=>"Send"}
Ok so here's my code and the way it's all layed out.
PagesController.rb contains a page called volunteer and this contains the partial (Called _new.html.erb) which is the form which is in it's own folder called volunteer_messages
views/volunteer_messages/_new.html.erb
<%= simple_form_for #volunteer_message, url: create_volunteer_message_url do |f| %>
<%= #volunteer_message.errors.full_messages.join(', ') %>
<%= f.input :name, placeholder: 'name' %>
<%= f.input :email, placeholder: 'email' %>
<%= f.input :phone_number, placeholder: 'phone number' %>
<%= f.input :body, placeholder: 'body' %>
<%= f.submit 'Send' %>
<% end %>
routes created
# is this causing a problem do you think?
get '/volunteer' => 'pages#volunteer'
# volunteer message routes
get '/volunteer', to: 'volunteer_message#new', as: 'new_volunteer_message'
post '/volunteer', to: 'volunteer_message#create', as: 'create_volunteer_message'
models/volunteer_message.rb
class VolunteerMessage
include ActiveModel::Model
attr_accessor :name, :email, :phone_number, :body
# validates :name, :email, :phone_number, :body, presence: true
end
mailers/volunteer_message_mailer.rb
class VolunteerMessageMailer < ApplicationMailer
# Subject can be set in your I18n file at config/locales/en.yml
# with the following lookup:
#
# en.volunteer_message_mailer.volunteer.subject
#
def volunteer(volunteer_message)
#body = volunteer_message.body
#subject = "Volunteer Request sent from www.myapp.com"
mail to: "myemail#example.com", from: volunteer_message.email
end
end
volunteer_messages_controller.rb
class VolunteerMessagesController < ApplicationController
def new
#volunteer_message = VolunteerMessage.new
end
def create
#volunteer_message = VolunteerMessage.new volunteer_message_params
if #volunteer_message.valid?
VolunteerMessageMailer.volunteer(#volunteer_message).deliver_now
redirect_to new_volunteer_message_url
flash.now[:notice] = "We have received your volunteer request and will be in touch soon!"
else
flash.now[:notice] = "There was an error sending your volunteer request. Please try again."
render :new
end
end
private
def volunteer_message_params
params.require(:volunteer_message).permit(:name, :email, :phone_number, :body)
end
end
pages_controller.rb
class PagesController < ApplicationController
def volunteer
#lead = Lead.new
#volunteer_message = VolunteerMessage.new
end
end
development.rb
# Don't care if the mailer can't send.
config.action_mailer.raise_delivery_errors = true
config.action_mailer.perform_deliveries = true
config.action_mailer.default_url_options = { host: 'localhost', port: 3000 }
config.action_mailer.delivery_method = :smtp
config.action_mailer.smtp_settings = {
address: 'smtp.gmail.com',
port: 587,
domain: 'gmail.com',
enable_starttls_auto: true,
user_name: 'me#gmail.com',
password: 'gmailapppassword',
authentication: 'plain'
}
Not sure what else I can add here. I do not think I even need he stmp_settings to see action mailer actually show the template of the email being sent?
Also I'm guessing it is somehting to do with the form and the route because ive added puts messages into the create action on the volunteer_message controller and do not see them in my server log.
I'm not getting any errors when pressing submit. I do notice the params going into the url but that's it. it stays there and when refreshes stays on the same screen.
example:
http://localhost:3000/volunteer?utf8=%E2%9C%93&authenticity_token=AZ%2FAflVf%2BlqRUYm45Jo82wAMpq%2B%2BY%2F93piLbeRXdK5n%2FQIWhuaUL3Oe2%2FSwzR%2FCLvj%2FAKW%2BBgD8dPd8vJYRDBA%3D%3D&volunteer_message%5Bname%5D=test+name&volunteer_message%5Bemail%5D=email%40test.com&volunteer_message%5Bphone_number%5D=88888888&volunteer_message%5Bbody%5D=test+email&commit=Send
Is the clue in the server text in the image that shows params going to the pages controller. I do not really want that. Just want the partial to be there but the action to work of the create action in the volunteer_messages controller.
Any help be much appreciated. Thanks.

String interpolation in form inputs

In Rails, I'm trying to create video uploader forms for multiple video types.
I ended with code like this:
= semantic_form_for element, url: form_url, method: form_method, remote: true do |f|
// there will be some form should return once
- ['webm', 'mp4', 'ogv'].each do |ext|
.video-item-uploader
= f.input :"#{ext}", hint: [ f.object."#{ext}"? ? "#{I18n.t('uploaded')}" : '' ].join.html_safe
= f.input :"#{ext + '_cache'}", as: :hidden
= f.input :_destroy, as: :boolean, label: "#{I18n.t('do_delete')}"
With code like that, I got syntax errors. It works if I replace "#{ext}" with any text (without quotes) from array.
Is there any possibility to insert a variable inside a form input like that?
= semantic_form_for element, url: form_url, method: form_method, remote: true do |f|
// there will be some form should return once
- %w(webm mp4 ogv).each do |ext|
.video-item-uploader
= f.input :"#{ext}", hint: [ f.object.send("#{ext}?")? ? "#{I18n.t('uploaded')}" : '' ].join.html_safe
= f.input :"#{ext + '_cache'}", as: :hidden
= f.input :_destroy, as: :boolean, label: "#{I18n.t('do_delete')}"
try this

Rails 4.0.2 haml version of form_for drive me crazy

I am quite new to Rails world and I am trying to create a form for my ticket model
I have this following in my routes.rb
resources :tickets
In my tickets_controller, I have the following
def new
#ticket = Ticket.new
end
And in my tickets/new.html.haml
= form_for(#ticket, :url => { :action => "create" }) do |f| # <- this line is causing error
...
My error is syntax error, unexpected ')'
I don't understand cause similar template file is working. Also, if I try to write the following tickets/new.html.erb, it is working
<%= form_for(:ticket, :url => { :action => "create" }) do |f| %>
<% end %>
Could you please help me to understand what i am exactly doing wrong ?
Thank you very much !
I found your problem, one was related to indentation(if-block) and other was an extra comma(submit button).
%h1{:class => ["page-header"]} New Ticket
= form_for(#ticket, :url => { :action => "create" }) do |f|
= if #ticket.errors.any?
%h2 #{pluralize(#ticket.errors.count, "error")} + " prohibited this post from being saved:" ### code indentation done as it belongs to if block
%ul
= #ticket.errors.full_messages.each do |msg|
%li = msg
%p
= f.label :subject
= f.text_field :subject
%p
= f.label :body
= f.text_area :body
%p
= f.submit :class => "btn btn-primary" ### Removed comma
= link_to 'Back', tickets_path

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.

omniauth identity not working on heroku with simple_form_for

I'm having trouble with Heroku not playing ball and throwing me this error
ActionController::RoutingError (No route matches [POST] "/auth/identity/register"):
I have the following working in dev.
model/user.rb
class User < OmniAuth::Identity::Models::ActiveRecord
has_many :services
controllers/users_controller.rb
def new
#user = env['omniauth.identity'] || User.new
end
users/new.html.erb
<%= simple_form_for #user, url: "/auth/identity/register" do |f| %><fieldset>
<legend><%= controller.action_name.capitalize %> User</legend>
<%= f.input :name, input_html: { name: "name" } %>
<%= f.input :email, input_html: { name: "email" } %>
<%= f.input :password, input_html: { name: "password" } %>
<%= f.input :password_confirmation, input_html: { name: "password_confirmation" } %>
<div class="form-actions">
<%= f.button :submit %>
<%= link_to 'Cancel', users_path, :class => 'btn' %>
</div></fieldset><% end %>
routes.rb
match "/auth/:service/callback" => 'services#create'
match "/auth/failure" => 'services#failure'
resources :users
This all works Perfectly on my machine but Heroku doesn't like it.
environments/development.rb and production.rb are the defaults created with "rails new..." with the following added:-
Rails.application.config.middleware.use OmniAuth::Builder do
require 'openid/store/filesystem'
provider :identity, fields: [:name, :email],
model: User,
on_failed_registration: lambda { |env|
UsersController.action(:new).call(env)
}
# generic openid
provider :open_id, :store => OpenID::Store::Filesystem.new('./tmp'), :name => 'openid'end
Hope this all makes sense and someone has the answer. Any and all help muchly appreciated.
Regards
Ok, I found the answer, just not sure why this is so though. Could be another question later if I don't find it here in stackoverflow.
Anyways, I was following this Excellent guide on www.communityguides.eu and put the omniauth configurations in development.rb and production.rb. Which made perfect sense as the OpenID::Store paths were different being:-
development.rb
OpenID::Store::Filesystem.new('/tmp')
production.rb
OpenID::Store::Filesystem.new('./tmp')
The Answer...
Put the omniauth configurations in initializers/Omniauth.rb
This means I now have to change this file every time I push to Heroku, but Heroku thanks me and allows my app to run nicely.
The question this raises for me (which I'm sure is answered elsewhere). What is the difference in the load order from Heroku and our Dev environments? Heroku appears to be loading initializers before the environment configs. And one more...anyone know a workaround that will save me having to update the Omniauth initializer file every time I want to push to Heroku ? :)
Thanx for reading and I hope this helps save someone some time.

Resources