Configuring comment form in Rails - ruby-on-rails

I've inherited a static website, with a typical contact form on the main page which takes a name, email, phone and message and should send an email to a set email address on submit. The JS and Slim partial below were given to me. I'm now trying to hook up to Rails using ActionMailer, but I'm doing something wrong with the form params and/or building of the email object. I'm not looking to store it in the database (tried with and without a model).
Routes:
resources :contacts, only: [:new, :create]
_contact_form.slim
(all I've changed is the action value)
form#contactform.form-horizontal action="/contacts" method="post" name="contactform" role="form"
/! Field 1
.row
.col-md-6
h3 name:
.input-text.form-group.left-field
input.input-name.form-control name="contact_name" type="text" /
.col-md-6
h3 Phone:
.input-text.form-group
input.input-name.form-control name="contact_phone" type="text" /
/! Field 2
h3 Email:
.input-email.form-group
input.input-email.form-control name="contact_email" type="email" /
/! Field 3
h3 Message:
.textarea-message.form-group
textarea.textarea-message.form-control name="contact_message" rows="7"
/! Button
button.btn.btn-default type="submit"
| Submit
i.flaticon-arrow209
Javascript:
(I've set the url and added the beforeSend)
/* --------------------------------------------
Contact Form
-------------------------------------------- */
$(function($) {
$('#contactform').bootstrapValidator({
message: '',
feedbackIcons: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
},
fields: {
contact_name: {
validators: {
notEmpty: {
message: ''
}
}
},
contact_email: {
validators: {
notEmpty: {
message: ''
},
emailAddress: {
message: ''
}
}
},
contact_message: {
validators: {
notEmpty: {
message: ''
}
}
}
},
submitHandler: function(validator, form, submitButton) {
$('.contact-form').addClass('ajax-loader');
var data = $('#contactform').serialize();
$.ajax({
type: "POST",
url: "/contacts",
beforeSend: function(xhr) {xhr.setRequestHeader('X-CSRF-Token', $('meta[name="csrf-token"]').attr('content'))},
data: $('#contactform').serialize(),
success: function(msg) {
$('.contact-form').removeClass('ajax-loader');
$('.form-message').html(msg);
$('.form-message').show();
submitButton.removeAttr("disabled");
resetForm($('#contactform'));
},
error: function(msg) {
$('.contact-form').removeClass('ajax-loader');
$('.form-message').html(msg);
$('.form-message').show();
submitButton.removeAttr("disabled");
resetForm($('#contactform'));
}
});
return false;
},
});
function resetForm($form) {
$form.find('input:text, input:password, input, input:file, select, textarea').val('');
$form.find('input:radio, input:checkbox').removeAttr('checked').removeAttr('selected');
}
});
contact.rb
class Contact
include ActiveModel::Model
include ActiveModel::Conversion
include ActiveModel::Validations
attr_accessor :contact_name, :contact_phone, :contact_email, :contact_message
validates :contact_name, :contact_email, :contact_message, presence: true
end
contacts_controller.rb:
class ContactsController < ApplicationController
def create
#contact = Contact.new(contact_params)
if #contact.valid?
ContactMailer.contact_email(#contact).deliver
redirect_to new_contact_path, notice: "Thank you for your message."
else
flash[:alert] = "An error occurred while delivering this message."
render :new
end
end
private
def contact_params
params.require(:contact).permit(:contact_name, :contact_phone, :contact_email, :contact_message)
end
end
contact_mailer.rb
class ContactMailer < ActionMailer::Base
default to: "me#myemailaddress.com"
def contact_email(contact)
#contact = contact
mail(from: email: #contact.contact_email, body: #contact.contact_message)
end
end
contact_email.text.slim
| New contact
= #contact.contact_name
= #contact.contact_email
| wrote:
= #contact.contact_message
Current output from Rails Server
I'm currently getting a syntax error in contact_mailer.rb on the #contact object, but prior to that the Rails server logs were telling me that the Contact object was uninitialized. I've tried including the form_for/form_tag Rails helpers but this hasn't seemed to help. I've also added an index action for the contact to the HomeController having read this suggestion elsewhere.
I can keep playing 'change the error message' but I imagine I'm getting something fundamental wrong (still a Rails newbie) so would really appreciate any pointers. I've read through the ActionMailer docs and everything else I can find.

You need to have .deliver after your contact_mailer.rb call don't you?
Also, in your contact.rb try adding
after_create :send_email
private
def send_email
ContactMailer.contact_email(self).deliver
end

Related

cause of JSON::ParserError (784: unexpected token at '{

I upload some files through Uppy, which saves the related JSON to a hidden form element. My create action submits this as a param called uppyResult. The input element looks like this once uploads are complete:
<input
name="uppyResult"
type="hidden"
value="[
{
"successful":[
{"source":"Dashboard","id":"uppy-e5vuacxwuamsw4u/png-1e-image/png-56448-1625714886789","name":"E5vUACXWUAMsW4u.png","extension":"png","meta":{"relativePath":null,"name":"E5vUACXWUAMsW4u.png","type":"image/png","key":"cache/663ecb0c8cac3ea40327f625f46cecdd.png","Content-Disposition":"inline; filename=\"E5vUACXWUAMsW4u.png\"; filename*=UTF-8''E5vUACXWUAMsW4u.png","Content-Type":"image/png","policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wNy0xNVQyMjozNzoxN1oiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJmcmFjdGlvbmNsdWItZGV2MiJ9LHsia2V5IjoiY2FjaGUvNjYzZWNiMGM4Y2FjM2VhNDAzMjdmNjI1ZjQ2Y2VjZGQucG5nIn0seyJDb250ZW50LURpc3Bvc2l0aW9uIjoiaW5saW5lOyBmaWxlbmFtZT1cIkU1dlVBQ1hXVUFNc1c0dS5wbmdcIjsgZmlsZW5hbWUqPVVURi04JydFNXZVQUNYV1VBTXNXNHUucG5nIn0seyJDb250ZW50LVR5cGUiOiJpbWFnZS9wbmcifSxbImNvbnRlbnQtbGVuZ3RoLXJhbmdlIiwwLDEwNDg1NzYwXSx7IngtYW16LWNyZWRlbnRpYWwiOiJBS0lBM0pFWlpGQU5PM0I2UjRUTy8yMDIxMDcxNS91cy13ZXN0LTEvczMvYXdzNF9yZXF1ZXN0In0seyJ4LWFtei1hbGdvcml0aG0iOiJBV1M0LUhNQUMtU0hBMjU2In0seyJ4LWFtei1kYXRlIjoiMjAyMTA3MTVUMjEzNzE3WiJ9XX0=","x-amz-credential":"AKIA3JEZZFANO3B6R4TO/20210715/region/s3/aws4_request","x-amz-algorithm":"AWS4-HMAC-SHA256","x-amz-date":"20210715T213717Z","x-amz-signature":"d95631770c5ad9aad4e21c59966463b7b7e29f55946a52145ae934d8cf2d8237"},"type":"image/png","data":{},"progress":{"uploadStarted":1626385018193,"uploadComplete":true,"percentage":100,"bytesUploaded":58322,"bytesTotal":58322,"postprocess":null},"size":56448,"isRemote":false,"remote":"","preview":"blob:https://example.com/ae686b0e-1cae-4b43-9589-b7c01446b7f0","xhrUpload":{"method":"post","formData":true,"endpoint":"https://bucket.s3.region.amazonaws.com","metaFields":["key","Content-Disposition","Content-Type","policy","x-amz-credential","x-amz-algorithm","x-amz-date","x-amz-signature"],"headers":{}},"response":{"status":204,"body":{"location":null},"uploadURL":null},"uploadURL":null,"isPaused":false},
{"source":"Dashboard","id":"uppy-91920f5bd03f91282b38941096ec4ffc/jpg-1e-image/jpeg-44471-1626247994037","name":"91920f5bd03f91282b38941096ec4ffc.jpg","extension":"jpg","meta":{"relativePath":null,"name":"91920f5bd03f91282b38941096ec4ffc.jpg","type":"image/jpeg","key":"cache/7ed930a77fb05ed135624e3c74187c84.jpg","Content-Disposition":"inline; filename=\"91920f5bd03f91282b38941096ec4ffc.jpg\"; filename*=UTF-8''91920f5bd03f91282b38941096ec4ffc.jpg","Content-Type":"image/jpeg","policy":"eyJleHBpcmF0aW9uIjoiMjAyMS0wNy0xNVQyMjozNzoxN1oiLCJjb25kaXRpb25zIjpbeyJidWNrZXQiOiJmcmFjdGlvbmNsdWItZGV2MiJ9LHsia2V5IjoiY2FjaGUvN2VkOTMwYTc3ZmIwNWVkMTM1NjI0ZTNjNzQxODdjODQuanBnIn0seyJDb250ZW50LURpc3Bvc2l0aW9uIjoiaW5saW5lOyBmaWxlbmFtZT1cIjkxOTIwZjViZDAzZjkxMjgyYjM4OTQxMDk2ZWM0ZmZjLmpwZ1wiOyBmaWxlbmFtZSo9VVRGLTgnJzkxOTIwZjViZDAzZjkxMjgyYjM4OTQxMDk2ZWM0ZmZjLmpwZyJ9LHsiQ29udGVudC1UeXBlIjoiaW1hZ2UvanBlZyJ9LFsiY29udGVudC1sZW5ndGgtcmFuZ2UiLDAsMTA0ODU3NjBdLHsieC1hbXotY3JlZGVudGlhbCI6IkFLSUEzSkVaWkZBTk8zQjZSNFRPLzIwMjEwNzE1L3VzLXdlc3QtMS9zMy9hd3M0X3JlcXVlc3QifSx7IngtYW16LWFsZ29yaXRobSI6IkFXUzQtSE1BQy1TSEEyNTYifSx7IngtYW16LWRhdGUiOiIyMDIxMDcxNVQyMTM3MTdaIn1dfQ==","x-amz-credential":"AKIA3JEZZFANO3B6R4TO/20210715/region/s3/aws4_request","x-amz-algorithm":"AWS4-HMAC-SHA256","x-amz-date":"20210715T213717Z","x-amz-signature":"c9566b00a78bc465de654768745f39275c1a51038b98095dc9eb1e2f51d24cd2"},"type":"image/jpeg","data":{},"progress":{"uploadStarted":1626385018193,"uploadComplete":true,"percentage":100,"bytesUploaded":46446,"bytesTotal":46446,"postprocess":null},"size":44471,"isRemote":false,"remote":"","preview":"blob:https://example.com/99a4ad8f-0112-442f-ab57-fdf89eeed805","xhrUpload":{"method":"post","formData":true,"endpoint":"https://bucket.s3.region.amazonaws.com","metaFields":["key","Content-Disposition","Content-Type","policy","x-amz-credential","x-amz-algorithm","x-amz-date","x-amz-signature"],"headers":{}},"response":{"status":204,"body":{"location":null},"uploadURL":null},"uploadURL":null,"isPaused":false}
],
"failed":[],
"uploadID":"ckr5fnbkf00013g631ll6q1yk"
}
]"
>
My create action then does this:
def create
#item = current_user.owned_items.create(item_params)
successful_results.each do |file|
media_file_params = [{
fileable_type: "Item",
fileable_id: #item.id,
file_data: {
id: file["meta"]["key"].sub!("cache/", "store/"),
storage: "store",
metadata: {
filename: file["name"],
size: file["size"],
mime_type: file["type"]
}
}
}]
MediaFile.create(media_file_params)
end
end
private
def successful_results
uppy_result[0]['successful']
end
def uppy_result
JSON.parse(params["uppyResult"])
end
def item_params
params.require(:item).permit(:info)
end
This is throwing an error: JSON::ParserError (784: unexpected token at '{:id=>"store/8116adea58af1759f3865d315695ed8e.png", :storage=>"store", :metadata=>{:filename=>"E5vUACXWUAMsW4u.png", :size=>56448, :mime_type=>"image/png"}}'):
I thought that maybe the symbols were supposed to be strings, but that's definitely not it.

Rails 5 API + Vue.js frontend: form to invite users with devise_invitable

I am writing an invitation form with Vue.js. The invites should POST to a Rails 5 API. I am using devise_invitable for the invitation logic and email dispatch. However, I am having a problem with intercepting the create method, as I will be sending the invitation to multiple users so I want to perform a User.invite! for each user in the params.
My invite.vue file, which contains the form to post:
<template>
<b-row>
<b-col>
Invite {{form.length}} members
<b-form #submit.prevent="submitStaffInvite" id='staffInvite'>
<div v-for="(row, idx) in form">
<b-row>
<b-col cols="3">
<b-form-group id="firstNameGroup" label="First name">
<b-form-input id="firstNameInput" name="user[first_name][]" type="text" v-model="row.firstName" autofocus></b-form-input>
</b-form-group>
</b-col>
<b-col cols="3">
<b-form-group id="lastNameGroup" label="Last name">
<b-form-input id="lastNameInput" name="user[last_name][]" type="text" v-model="row.lastName"></b-form-input>
</b-form-group>
</b-col>
<b-col cols="3">
<b-form-group id="emailGroup" label="Staff email">
<b-form-input id="emailInput" name="user[email][]" type="text" v-model="row.email"></b-form-input>
</b-form-group>
</b-col>
<b-col cols="3">
<b-button #click='removeRow(idx)'>Remove invitation</b-button>
</b-col>
</b-row>
</div>
<br />
<b-button-group>
<b-button #click.prevent='addRow'>Add invitation</b-button>
</b-button-group>
<br />
<b-button-group>
<b-button type="submit" variant="primary">Submit</b-button>
</b-button-group>
</b-form>
</b-col>
</b-row>
</template>
<script>
export default {
data: () => {
return {
form: [
{
email: '',
firstName: '',
lastName: ''
}
]
}
},
methods: {
addRow: function () {
this.form.push({
email: '',
firstName: '',
lastName: ''
})
},
removeRow: function (idx) {
this.form.splice(idx, 1)
},
submitStaffInvite: function () {
this.$axios.post('http://localhost:3001/auth/invitation', this.form)
.then((res) => {
if (res.status === 200) {
this.$notify({
text: res.data.message,
group: 'alerts',
type: 'success'
})
}
})
.catch(function (error) {
error.response.data.errors.forEach((err) => {
this.$notify({
text: err,
group: 'alerts',
type: 'warning'
})
})
})
}
}
}
</script>
my users/invitations_controller.rb
class Users::InvitationsController < Devise::InvitationsController
before_action :configure_permitted_parameters
def create
# TODO
# Send email to each user in the form.
end
def edit
sign_out send("current_#{resource_name}") if send("#{resource_name}_signed_in?")
set_minimum_password_length
resource.invitation_token = params[:invitation_token]
redirect_to "http://localhost:3001/auth/invitation/accept?invitation_token=#{params[:invitation_token]}"
end
def update
super do |resource|
if resource.errors.empty?
render json: { status: "Invitation Accepted!" }, status: 200
else
render json: resource.errors, status: 401
end
end
end
protected
def configure_permitted_parameters
devise_parameter_sanitizer.permit(:accept_invitation, keys: [:first_name, :last_name])
end
end
my routes.rb
Rails.application.routes.draw do
# Using devise_token_auth for the API
mount_devise_token_auth_for 'User', at: 'auth',
defaults: { format: :json },
controllers: {
invitations: 'users/invitations'
}
...
end
I can see that the following routes exist:
PUT /auth/invitation(.:format) devise/invitations#update
POST /auth/invitation(.:format) devise/invitations#create
The problem I can see here though is that I'd expect the routes to look like this:
PUT /auth/invitation(.:format) users/invitations#update
POST /auth/invitation(.:format) users/invitations#create
This behavior might be caused by the devise_token_auth gem, which I'm not sure how to correct.
Again, when I submit this form I'd expect to be able to intercept the create method, so to then call User.invite! on all the users that are listed in the form. I've tried adding a byebug or binding.pry inside the create method but the code doesn't appear to execute, which means it's being bypassed.
The error message I see is:
Processing by Devise::InvitationsController#create as HTML
Parameters: {"_json"=>[{"email"=>"asd#ad.com", "firstName"=>"Test", "lastName"=>"Last"}], "invitation"=>{"_json"=>[{"email"=>"asd#ad.com", "firstName"=>"Test", "lastName"=>"Last"}]}}
Completed 500 Internal Server Error in 0ms (ActiveRecord: 0.0ms)
ArgumentError (wrong number of arguments (given 1, expected 0)):
Am I not supposed to pass the form data to the devise_invitable's create method? Any help is much appreciated.
Thanks in advance!
Try changing your routes to
mount_devise_token_auth_for 'User', at: 'auth', skip: [:invitations]
devise_for :users, path: "auth", only: [:invitations],
controllers: { invitations: 'users/invitations' }
For more information you can refer to this article

Rails 4.2.7 & Devise 4.1.1 - Change password with current password required in user preferences page with AJAX

My application has three different user types (i.e. Devise models): SuperAdminUser, AdminUser and StandardUser. Every user, regardless of type, has a preferences page where they will be able to update a number of attributes (e.g. :email, :password, :username, etc.), with two of the attributes requiring the user to enter their current password to update (:email, :password), while the remaining attributes will not require the current password to update.
Now, in my ideal world, the preference updates will utilize AJAX so that only the attribute changed will refresh in the page rather than an entire page reload. Currently I have this working for all of the attributes that don't require the user to enter their current password, but I've been unable to get it to work with :email and :password. Oddly enough, I have it working in a previous prototype, but that had only one Devise user model, not three, and that single model didn't have it's own registration controller overriding the Devise registration controller, which the three user models in the current app do, although I don't see anything in these overriding registration controllers that would be impacting the issue here.
The only error message I'm getting is my own, on the preferences page:
Sorry, but we were unable to update your password.
The problem appears to be that the :current_password param value is not being captured from the form. The console log statement shows it as a blank string, while the :new_password and :new_password_confirmation values are captured without problem. I haven't been able to determine why this is happening. As an alternate approach, I also tried to mimic as closely as I could the change password example in the Devise wiki (the last example) (here: https://github.com/plataformatec/devise/wiki/How-To:-Allow-users-to-edit-their-password), but to no avail. Any suggestions would be greatly appreciated.
Additionally, I guess an important question to ask is if should I be using AJAX with the password attribute. That is, is it inherently insecure passing these password values via AJAX? If so, is there a way to do it securely (e.g. encrypted)?
Here is my relevant code:
app/controllers/super_admin_users/preferences_controller.rb
class SuperAdminUsers::PreferencesController < ApplicationController
include ApplicationHelper
before_action :authenticate_super_admin_user!
.
.
.
def change_password
#super_admin_user = SuperAdminUser.find_by(id: current_super_admin_user.id)
if resource.update_with_password(params.permit(:password,
:password_confirmation,
:current_password))
sign_in resource_name, self.resource, bypass: true
status_message = 'Your password has been updated successfully!'
else
status_message = 'Sorry, but we were unable to update your password.'
end
respond_to do |format|
format.json { render json: { status_message: status_message } }
end
end
def password_section_partial
#change_password_status_message = params[:change_password_status_message] || ""
render 'super_admin_users/preferences/password/password_section_partial'
end
.
.
.
end
end
app/views/super_admin_users/preferences/password/_change_password_form.html.haml
= form_tag super_admin_user_prefs_change_password_path, id: 'prefs-change-password-form' do
.prefs-label-and-input-wrapper
#prefs-new-password-label-wrapper.prefs-input-label-wrapper
= label_tag 'password', 'New Password', id: 'prefs-new-password-label',
class: 'prefs-input-label'
#prefs-new-password-input-wrapper.prefs-input-wrapper
= password_field_tag 'password', nil, autofocus: true,
autocomplete: 'off',
id: 'prefs-new-password-input',
class: 'prefs-input'
.prefs-label-and-input-wrapper
#prefs-new-password-confirmation-label-wrapper.prefs-input-label-wrapper
= label_tag 'password_confirmation', 'New Password Confirmation', id: 'prefs-new-confirmation-password-label',
class: 'prefs-input-label'
#prefs-new-password-confirmation-input-wrapper.prefs-input-wrapper
= password_field_tag 'password_confirmation', nil, autocomplete: 'off',
id: 'prefs-new-password-confirmation-input',
class: 'prefs-input'
.prefs-label-and-input-wrapper
#prefs-current-password-label-wrapper.prefs-input-label-wrapper
= label_tag 'current_password', 'Current Password', id: 'prefs-current-password-label',
class: 'prefs-input-label'
#prefs-current-password-input-wrapper.prefs-input-wrapper
= password_field_tag 'current_password', nil, autocomplete: 'off',
id: 'prefs-current-password-input',
class: 'prefs-input'
#prefs-change-password-form-buttons-wrapper
#prefs-change-password-form-submit-btn-wrapper
= link_to 'Update', 'javascript:;', id: 'prefs-change-password-form-submit-btn', class: 'btn btn-sm btn-success btn-submit'
-#%input{ id: 'prefs-change-password-form-submit-btn', class: 'btn btn-sm btn-success', type: 'submit', value: 'Update' }
#prefs-change-password-form-cancel-btn-wrapper
= link_to 'Cancel', 'javascript:;', id: 'prefs-change-password-form-cancel-btn', class: 'btn btn-sm btn-secondary btn-cancel'
app/views/super_admin_users/preferences/password/_change_password_status_message.html.haml
#prefs-change-password-status-message.prefs-status-message= change_password_status_message
app/views/super_admin_users/preferences/password/_password_section.html.haml
#prefs-password-section-header.prefs-section-header
#prefs-password-headline-wrapper
.prefs-section-headline Password
#prefs-password-edit-btn-wrapper
= link_to 'Edit', 'javascript:;', id: 'prefs-password-edit-btn', class: 'btn btn-sm btn-primary btn-edit'
#prefs-change-password-form-wrapper.prefs-form-wrapper.no-display
= render partial: 'super_admin_users/preferences/password/change_password_form'
#prefs-change-password-status-message-wrapper.prefs-status-message-wrapper.no-display
app/views/super_admin_users/preferences/password/password_section_partial.js.haml
$('#prefs-change-password-status-message-wrapper').empty();
$('#prefs-change-password-form-wrapper').addClass('no-display');
$('#prefs-change-password-form').trigger('reset');
- if #change_password_status_message.present?
$('#prefs-change-password-status-message-wrapper').append("#{ escape_javascript(render(partial: 'super_admin_users/preferences/password/change_password_status_message', locals: { change_password_status_message: #change_password_status_message })) }");
$('#prefs-change-password-status-message-wrapper').removeClass('no-display');
$('#prefs-password-edit-btn').removeClass('no-display');
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
include ApplicationHelper
include DeviseParamSanitizerOverrides
def get_current_user_type
respond_to do |format|
format.json { render json: { current_user_type: resource_name.to_s } }
end
end
def get_current_environment
respond_to do |format|
format.json { render json: { current_environment: Rails.env } }
end
end
protected
def devise_parameter_sanitizer
if resource_class.to_s == 'SuperAdminUser'
SuperAdminUser::ParameterSanitizer.new(SuperAdminUser, :super_admin_user, params)
elsif resource_class.to_s == 'AdminUser'
AdminUser::ParameterSanitizer.new(AdminUser, :admin_user, params)
elsif resource_class.to_s == 'StandardUser'
StandardUser::ParameterSanitizer.new(StandardUser, :standard_user, params)
end
end
end
app/controllers/concerns/devise_param_sanitizer_overrides.rb
module DeviseParamSanitizerOverrides
extend ActiveSupport::Concern
class SuperAdminUser::ParameterSanitizer < Devise::ParameterSanitizer
def initialize(*)
super
permit(:sign_up, keys: [:email, :first_name, :last_name, :username])
permit(:sign_in, keys: [:email, :username])
permit(:account_update, keys: [:current_password, :email, :first_name, :last_name, :time_zone, :username])
end
end
.
.
.
end
app/helpers/application_helper.rb
module ApplicationHelper
def resource
if super_admin_user_signed_in?
#super_admin_user ||= SuperAdminUser.new
elsif admin_user_signed_in?
#admin_user ||= AdminUser.new
elsif standard_user_signed_in?
#standard_user ||= StandardUser.new
end
end
def resource_name
if super_admin_user_signed_in?
:super_admin_user
elsif admin_user_signed_in?
:admin_user
elsif standard_user_signed_in?
:standard_user
end
end
def resource_class
if super_admin_user_signed_in?
SuperAdminUser
elsif admin_user_signed_in?
AdminUser
elsif standard_user_signed_in?
StandardUser
end
end
def devise_mapping
if super_admin_user_signed_in?
#devise_mapping ||= Devise.mappings[:super_admin_user]
elsif admin_user_signed_in?
#devise_mapping ||= Devise.mappings[:admin_user]
elsif standard_user_signed_in?
#devise_mapping ||= Devise.mappings[:standard_user]
end
end
def resource_authenticated_root
if super_admin_user_signed_in?
authenticated_super_admin_user_root
elsif admin_user_signed_in?
authenticated_admin_user_root
elsif standard_user_signed_in?
authenticated_standard_user_root
end
end
end
app/assets/javascripts/preferences.js
var currentUserType;
var currentEnvironment;
getCurrentUserType('/get_current_user_type'
).done(function(getCurrentUserTypeResponse) {
currentUserType = getCurrentUserTypeResponse.current_user_type;
});
getCurrentEnvironment('/get_current_environment'
).done(function(getCurrentEnvironmentResponse) {
currentEnvironment = getCurrentEnvironmentResponse.current_environment;
});
$(document).ready(function() {
$('#prefs-password-edit-btn').click(function (e) {
$('#prefs-password-edit-btn').addClass('no-display');
$('#prefs-change-password-form').trigger('reset');
$('#prefs-change-password-form-wrapper').removeClass('no-display');
});
$(function() {
return $('body').on('click', '#prefs-change-password-form-submit-btn', function() {
$('#prefs-change-password-form-wrapper').addClass('no-display');
changePassword('/' + currentUserType + 's/preferences/change_password?password=' + $('#prefs-new-password-input').val() +
'&password_confirmation=' + $('#prefs-new-password-confirmation-input').val() +
'&current_password=' + $('#prefs-current-password-input').val()
).done(function(changePasswordResponse) {
$.ajax({url: '/' + currentUserType + 's/preferences/password_section_partial?change_password_status_message=' + changePasswordResponse.status_message});
});
});
});
$(function() {
return $('body').on('click', '#prefs-change-password-form-cancel-btn', function() {
$('#prefs-change-password-form-wrapper').addClass('no-display');
$('#prefs-change-password-form').trigger('reset');
$('#prefs-password-edit-btn').removeClass('no-display');
});
});
function getCurrentUserType(url) {
return $.ajax({
url: url,
type: 'get',
dataType: 'json'
})
.fail(function() {
if (currentEnvironment === 'development') {
alert('AJAX Get Current User Type Error');
}
})
}
function getCurrentEnvironment(url) {
return $.ajax({
url: url,
type: 'get',
dataType: 'json'
})
.fail(function() {
alert('AJAX Get Current Environment Error');
})
}
function changePassword(url) {
return $.ajax({
url: url,
type: 'get',
dataType: 'json'
})
.fail(function() {
if (currentEnvironment === 'development') {
alert('AJAX Change Password Error');
}
})
}
config/routes.rb
Rails.application.routes.draw do
get '/get_current_user_type', to: 'application#get_current_user_type'
get '/get_current_environment', to: 'application#get_current_environment'
.
.
.
devise_for :super_admin_users, controllers: { registrations: 'super_admin_users/registrations' }
authenticated :super_admin_user do
root to: 'super_admin_users/dashboard#index', as: 'authenticated_super_admin_user_root'
end
as :super_admin_user do
get 'super_admin_users/preferences/password_section_partial', to: 'super_admin_users/preferences#password_section_partial',
as: :super_admin_user_password_section_partial
get 'super_admin_users/preferences/change_password', to: 'super_admin_users/preferences#change_password',
as: :super_admin_user_prefs_change_password
end
end

Show Associated Model Data with React Rails

I'm using the react-rails gem and have two models: Message and User. User has_many :messages.
In my message.js.jsx, I'd like to show the User of that message. In regular erb, it'd just be <%= message.user.name %>. How would I do this in the message.js.jsx component?
You could rename your component to message.js.jsx.erb and use ERB in it, but it will only be compiled once when Rails starts up.
A more React-ish way to handle is to AJAX load the user data in componentDidMount (or a Store, if using Flux).
message.js.jsx
getInitialState: function() {
return { user: { name: '' } };
},
componentDidMount: function() {
$.getJSON('/users/'+ this.props.id +'.json', function(userData) {
if (this.isMounted()) {
this.setState({ user: userData })
}
});
},
You can create a Rails endpoint to return userData as JSON something like this:
users_controller.rb
def show
#user = User.find(params[:id])
respond_to do |format|
format.html # default html response
format.json { render json: #user.to_json(only: [:id, :name]) }
end
end
See Facebook's page on this for more details
I agree with Unixmonkey that is the react way. You can also do it a few more ways.
#user = JSON.parse user.to_json(include: [:messages], only: [:id, :name])
As well as using componentDidMount to hit a JSON endpoint using jbuilder which you can put a timeout on if you want to update dynamically.
componentDidMount: function() {
$.getJSON('/users/'+ this.props.id +'.json', function(user) {
if (this.isMounted()) {
this.setState({ user: user })
}
});
},
Your show.json.jbuilder under user views would look something like this:
json.id #user.id
json.name #user.name
json.messages #user.messages do |message|
json.id message.id
json.content message.content
json.created_at message.created_at
end

Simple Auth Devise giving unauthenticated error in Ember Rails

I am setting up an ember app that is backed by ruby on rails. I am running into issues with my sign in action using simple-auth and simple-auth-devise. I successfully retrieve the sessions authentication token and username when I submit a correct username and password, but I am still given a 401 access denied error and I can't figure out why. I suspect that it may have to do with the naming of email versus user_email and token vs user_token business. I am taking this code mostly from dayjot, so you'd think it would be trivial to track down this bug but I am having tons of issues finding the exact issue. Thanks for any help you can give me!
The exact error I get in the rails server is:
Started GET "/users/me" for 127.0.0.1 at 2015-02-17 10:25:31 -0600
Processing by UsersController#me as JSON
Parameters: {"user"=>{}}
Filter chain halted as :authenticate_user! rendered or redirected
Completed 401 Unauthorized in 5ms (Views: 4.1ms | ActiveRecord: 0.0ms)
In rails, this is my application controller:
This is my application controller:
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
# protect_from_forgery with: :null_session
before_action :authenticate_user_from_token!, :handle_html
around_action :user_time_zone, if: :current_user
def index
render file: 'public/index.html'
end
protected
def authenticate_user!
render(json: {}, status: 401) unless current_user
end
private
def authenticate_user_from_token!
authenticate_with_http_token do |token, options|
user_email = options[:user_email].presence
user = user_email && User.find_by_email(user_email)
if user && Devise.secure_compare(user.authentication_token, token)
request.env['devise.skip_trackable'] = true
sign_in user, store: false
end
end
end
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end
# If this is a get request for HTML, just render the ember app.
def handle_html
render 'public/index.html' if request.method == 'GET' && request.headers['Accept'].match(/html/)
end
end
My sessions controller looks like this:
class SessionsController < Devise::SessionsController
def create
self.resource = warden.authenticate!(auth_options)
sign_in(resource_name, resource)
data = {
user_token: self.resource.authentication_token,
user_email: self.resource.email
}
render json: data, status: 201
end
def destroy
sign_out :user
render json: {}, status: :accepted
end
end
My serializers are these:
class UserSerializer < ActiveModel::Serializer
attributes :id, :password, :user_email, :email, :user_token, :passwordConfirmation
end
class UserSerializer < ActiveModel::Serializer
attributes :id, :email, :email_times, :last_export_time, :plan,
:plan_started, :plan_canceled, :plan_status, :trial_end,
:time_zone, :status, :created_at, :include_email_memory
end
My route is:
Rails.application.routes.draw do
# PLANS
post 'update_plan' => 'plans#update_plan', as: :update_plan
post 'update_card' => 'plans#update_card', as: :update_card
post 'cancel_plan' => 'plans#cancel_plan', as: :cancel_plan
# PASSWORDS
post 'start_password_reset' => 'users#start_password_reset'
put 'finish_password_reset' => 'users#finish_password_reset'
get 'password-reset' => 'application#index', as: :edit_user_password
# USERS
devise_for :users, controllers: { sessions: 'sessions' }, :skip => [:passwords]
resources :users, only: [:create, :update] do
get 'me' => 'users#me', on: :collection
end
# background processing admin
match "/delayed_job" => DelayedJobWeb, :anchor => false, via: [:get, :post]
# catch-all for ember app
get '*path' => 'application#index', :constraints => { :format => 'html' }
end
In the ember-cli app itself, my login controller is:
import Ember from "ember";
export default Ember.Controller.extend({
authenticator: 'simple-auth-authenticator:devise',
identification: null,
password: null,
error: null,
working: false,
actions: {
authenticate: function() {
var _this = this,
data = this.getProperties('identification', 'password');
this.setProperties({
working: true,
password: null,
error: null
});
this.get('session').authenticate('simple-auth-authenticator:devise', data).then(function() {
// authentication was successful
}, function(data) {
_this.set('working', false);
_this.set('error', data.error);
});
}
}
});
My application route is:
// ember-simple-auth
import Ember from "ember";
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
import Notify from 'ember-notify';
import ENV from 'front-end/config/environment';
export default Ember.Route.extend(ApplicationRouteMixin, {
beforeModel: function(transition) {
this._super(transition);
return this.setCurrentUser();
},
actions: {
sessionAuthenticationFailed: function(data) {
this.controllerFor('login').set('working', false);
this.controllerFor('login').set('loginErrorMessage', data.message);
},
sessionInvalidationSucceeded: function() {
this.transitionTo('index');
},
sessionAuthenticationSucceeded: function() {
var _this = this;
this.controllerFor('login').set('working', false);
this.setCurrentUser().then(function() {
if (_this.get('session.currentUser.mustSubscribe')) {
_this.transitionTo('plans');
} else {
_this.transitionTo('courses');
}
});
},
authorizationFailed: function() {
Notify.error("Could not be authenticated.. signing out.", {closeAfter: 5000});
this.get('session').invalidate();
}
},
setCurrentUser: function() {
var _this = this,
adapter = this.get('store').adapterFor('user');
if (this.get('session.isAuthenticated')) {
return new Ember.RSVP.Promise(function(resolve) {
adapter.ajax(ENV.APP.API_HOST + "/users/me", "GET", {}).then(
function(response){
_this.store.pushPayload(response);
var user = _this.store.find('user', response.user.id);
resolve(user);
},
function(response){
resolve(response);
}
);
}).then(function(user) {
_this.set('session.currentUser', user);
}, function() {
Notify.error("Could not be authenticated.. signing out.", {closeAfter: 5000});
_this.get('session').invalidate();
});
} else {
return new Ember.RSVP.Promise(function(resolve){ resolve(); });
}
}
});
Finally my login route is:
import Ember from "ember";
export default Ember.Route.extend({
activate: function() {
if (this.get('session').isAuthenticated) {
this.transitionTo('courses');
}
}
});
And Template is:
<form {{action 'register' on='submit'}} class='d-auth-form fade-in'>
{{#each errors}}
<div class="d-error">
{{this}}
</div>
{{/each}}
{{input placeholder='Email' type='email' value=email autocomplete='off' autocapitalize="none"}}
{{input placeholder='Password' type='password' value=password autocomplete='off'}}
<button type="submit" class='d-btn d-btn--success' {{bind-attr disabled=working}}>
{{#if working}}
Registering..
{{else}}
Sign up for DayJot for free
{{/if}}
</button>
<ul class='d-links'>
<li>{{#link-to 'login'}}Login to existing account{{/link-to}}</li>
</ul>
</form>
The important parts of environment.js are:
'simple-auth': {
crossOriginWhitelist: ['http://localhost:3000','http://localhost:4202','https://api.dayjot.com'],
authorizer: 'simple-auth-authorizer:devise',
authenticationRoute: 'index'
}
and
ENV['simple-auth-devise'] = {
serverTokenEndpoint: ENV.APP.API_HOST+'/users/sign_in',
identificationAttributeName: 'email'
}
Checkout the README - Ember Simple Auth Devise expects the token to be returned as token, you're using user_token however. Thus, the session will never actually be authenticated in Ember and the token won't be included in requests which leads to the 401 response.

Resources