Instance variables are purged when user fails his authentication - ruby-on-rails

I have trouble getting instances variables while overriding Devise::SessionsController.
When an unauthenticated user add an item to its cart, I want to redirect him to the sign in page and apply the addition after he's authenticated.
1 . I pass the item_id as a URL parameters:
localhost:3000/user_accounts/sign_in?item_id=42
I get back in a hidden field in the form, I can have it through the session creation form:
<%= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f| %>
<%= f.hidden_field :item_id, value: params[:item_id] || #item_id %>
...
<% end %>
When Rails starts his form flow, the URL parameters are removed:
Before:
localhost:3000/user_accounts/sign_in?item_id=42
After:
localhost:3000/user_accounts/sign_in
I don't want to loose the item_id when the user fails his authentication, so I ensure #item_id is exposed as an instance variable, so I can inject it back to the form.
However, on failure, Rails(?) / Devise(?) / Warden(?) seems to purge the instance variables (for security reasons?). As a result, #item_id is nil.
Note that works well when the user successfully authenticate (through URL parameters). Also, I use this very same technique in the registration process and it works in both success (URL parameters) and failure (instances variables) case.
Here is my implementation of SessionsController:
class MySessionsController < Devise::SessionsController
def create
#item_id = parse_id(params[:user_account][:item])
super do |account|
add_to_cart(account, #item_id) if #item_id
end
flash.delete(:notice)
end
end
Do you know how I can fix this ?
I am open to alternatives as well.

I was wrong. Rails, Devise and Warden aren't doing shady things to my instance variables. I found out that a redirection was made, explaining why the context was lost.
In the end, I used cookies to fix the problem as #nattfodd suggested it. This was trivial I don't know why I didn't think of it.
class MySessionsController < Devise::SessionsController
def create
item_id = parse_id(cookies.encrypted[:item])
super do |account|
add_to_cart(account, item_id) if item_id
cookies.encrypted[:item] = ""
end
flash.delete(:notice)
end
end
Cleaner, no hack. Good.

Related

Unpermitted parameter error when adding request parameter while using Administrate

I'm using Administrate v0.11.0 with search_term textbox,
it works totally fine,
and now I want to add a request parameter my_search_condition_flag which is a boolean flag value that affects search condition.
In my index action of controller,
I added the following line, so that requests with this parameter pass the Strong Parameters validation.
params.permit(:search, :my_search_condition_flag)
The rest of the code in index action is simply copied from ApplicationController.rb of Administrate.
When I make a HTTP request with request parameter my_search_condition_flag=1 ,
my index action is processed just fine,
but HTTP response returns following error:
ActionController::UnpermittedParameters in Admin::MyPage#index
Showing /usr/local/bundle/gems/administrate-0.11.0/app/views/administrate/application/_search.html.erb where line #19 raised:
found unpermitted parameter: :my_search_condition_flag
which is raised from rendering method of search_term textbox inside index.html.erb
<% if show_search_bar %>
<%= render(
"search",
search_term: search_term,
resource_name: display_resource_name(page.resource_name)
) %>
<% end %>
I've already tried the following to my Dashboard class, introduced here:
# -- Overwrite the method to add one more to the permit list
def permitted_attributes
super + [:my_search_condition_flag] # -- Adding our now removed field to thepermitted list
end
How can I tell Administrate to permit a parameter which I want to add?
Do I have to use request body instead? (which I don't want)
You were on the right track there. The exception originates at /app/views/administrate/application/_search.html.erb:19, as you mention. If you look there, you'll see it uses the method clear_search_params, which also uses strong_parameters to allow/deny query params. You can override this with a helper of your own. For example:
module Admin
module ApplicationHelper
def clear_search_params
params.except(:search, :page, :my_required_condition_flag).permit(
:per_page, resource_name => %i[order direction]
)
end
end
end
If you do this, you'll get a new, related error. This time from /app/helpers/administrate/application_helper.rb:48. The method there is called sanitized_order_params, and can be overriden similarly:
module Admin
module ApplicationHelper
# ...
def sanitized_order_params(page, current_field_name)
collection_names = page.item_includes + [current_field_name]
association_params = collection_names.map do |assoc_name|
{ assoc_name => %i[order direction page per_page] }
end
params.permit(:search, :my_required_condition_flag, :id, :page, :per_page, association_params)
end
end
end
And with that, you should be clear of errors.
Admittedly, this is not very nice fix. Ideally Administrate should be providing some better way to override this list of allowed search params. Fancy submitting a PR? ;-)

Converting a string into a controller method call

I'm trying to create a generic breadcrumbs method in my application controller to assign the breadcrumbs based on the current controller. If I wanted the breadcrumbs for the index of 'Thing', I would need in the view:
<%= breadcrumb :things, things %>
And for edit or show:
<%= breadcrumb :thing, thing %>
Where things is a method in the things controller that returns all things, and thing is a method returning the relevant thing.Both are exposed, and I have in my application layout:
<%= breadcrumb crumb, crumb_resource %>
And in my application controller:
def crumb
return controller_name.singularize.to_sym if edit_or_show_action
controller_name.to_sym
end
def crumb_resource
resource = controller_name
resource = controller_name.singularize if edit_or_show_action
end
def edit_or_show_action
action_name == 'edit' || 'show'
end
This obviously returns a string for crumb_resource, rather than the call to the controller method. From what I can find I believe it has something to do with send, however
controller.send(resource)
obviously doesn't work. How can I convert the string that is returned into a controller method call?
If you're using Gretel, then I think what you might be looking for is this:
def crumb_resource
resource = controller_name
resource = controller_name.singularize if edit_or_show_action
self.instance_variable_get("##{resource}")
end
This is assuming you have stored the relevant resource into #resource_name during the edit/show/index action.
I accepted the answer given as I'm assuming it works for people using instance variables to access models in their view, however in the end this worked for me:
breadcrumb crumb, eval(crumb_resource)
where eval evaluates the string, basically reverse interpolation which sounds pretty cool.

Email compose view in rails

I have created and (hopefully set up) a mailer.
Instead of sending templates, I would like to email the content of a form textarea in a view.
I need a view to edit the message, which should be sent to the controller which in turn calls the send_mail method in my mailer.
Class Notifier < ActionMailer::Base
default from: "from#example.com"
def send_email( email, subject, body )
mail(
:to => email,
:subject => subject
) do |format|
format.text { render :text => body }
end
end
end
This is my view:
<%= form_for(:post, :url => {:action => 'send'}) do |f| %>
<%= f.text_field(:title, class: 'form-control')%>
<%= f.text_area(:content, rows: 15)%>
<%= f.button "Submit", type: 'submit' %>
<% end %>
The problem is that when generating a mailer with rails g mailer Notifier
I get a notifier.rb in mailers and a notifier folder in views. However I have no view controller for the notifier views.
Question: How do I make the link between my view and sending the input text as email?
You need to create a controller which handles your view, and in that controller you need to call the mailer somewhat like this: (you'll need to change the names of your form fields to match the params in the call or vice versa)
Notifier::send_email( params[:email], params[:subject], params[:body]).deliver
I'd recommend to check out these RailsCasts:
http://railscasts.com/episodes/61-sending-email-revised
http://railscasts.com/episodes/61-sending-email
http://railscasts.com/episodes/206-action-mailer-in-rails-3
This might be a good place to make a non-ActiveRecord model. I understand that right now a problem is solved and this is a bit beyond the scope, but it's useful, so why not?
I suggest you look at pattern 3 in this article and build a form model (Notification?) that encapsulates the process of storing form contents, validating them and sending the actual email. Note that the implementations in the article are pretty much out of date, Rails 4 introduced ActiveModel::Model that facilitates the process.
Pros:
Another class defined mostly in declarative style, easy to read and find
Can be easily and cleanly laid out by SimpleForm or Rails' form helpers
Gets all the benefits of a traditional Rails model, like validations (and errors if they fail)
Semantic, code looks consistent with the rest of the app working with DB or whatever
Cons:
Another class, can be considered overengineering
More code, some more work, ease of maintenance is arguable
Another object to create in controllers that render this form
Once it's done, the process of making it work is pretty much the same as making any other resource to work. And I assume, that this mailer sits on its separate page.
Routes:
resource :notification, only: [:create] do
get :new, path: "" # A matter of taste, really
# You may use the default `new` route
# with a path `notifications/new`
end
Controller:
class NotificationsController
def new
#notification = Notification.new
end
def create
#notification = Notification.new(notification_params)
if #notification.send
# success! redirect somewhere?
else
render :new # render the form again with the errors
end
end
private
def notification_params
params.require(:notification).permit(:email, :subject, :body)
end
end
You will also need a view for the new action that renders the #notification into a form. Only new, create doesn't need its own. And now for the fun part, model:
class Notification # Yep, it inherits nothing!
include ActiveModel::Model
attr_reader :email, :subject, :body
validates :email,
presence: true # You might want to validate its format?
validates :subject,
presence: true, length: {in: 0..100} # Too long subjects are annoying
validates :body,
presence: true
def persisted?
false # I have no idea why, but it's defined in the article, no harm done
# I'd love to hear the explaination about this though
end
def send
if valid? # no objections from validations?
# Alright, send it already!
Notifier.send_mail(email, subject, body).deliver
# thanks for this line go to #Daniel and his answer
true
else
false
end
end
end
And, finally, a pro tip: Rails 4.2 (bleeding edge right now!) introduced ActiveJob, that's integrated with mailers. By replacing a call to deliver method with a call to deliver_later you will enqueue the email for sending by the background task processor as described here (edge guides, subject to change quite soon). I don't really think it's about time to use it everywhere (too new), but consider this for future projects.
Do I really think it's good? Yeah, I really do, I've refactored a user password changer to look this way, the code has become easier to navigate and look at.
So think of your notifier.rb as a controller. In which you defined the #send_mail. This means that in views/notifier you should add a send_mail.html.haml (erb/slim... matter of taste) which will be the body of the mail.
Now from the controller that receives your form you just need to call
Notifier.send_mail(email, subject, body).deliver

Differentiate between multiple registration pages for same user model

I have a fairly vanilla registration system for my Rails 4 app, and I'd like to add a strange feature. I'd like to provide multiple registration forms, and secretly set a value on the new User object, based on which page the data is being submitted from.
My current way of doing it is bad for security reasons. It works fine, but a savvy user could use a browser's dev tools to change the value of the hidden field and indicate that they came in from a different page.
routes.rb
get '/signup', to: 'users#new'
get '/red-signup', to: 'users#new', color: 'red'
get '/blue-signup', to: 'users#new', color: 'blue'
users_controller.rb
def new
#user = User.new
#color = params[:color] || 'grey' # default of grey
end
users/new.html.erb
<%= form_for(#user) do |f| %>
...
<%= f.hidden_field :color, value: #color %> # VULNERABLE TO TAMPERING
<%= f.submit "Create my account" %>
<% end %>
So, I'd like a tamper-proof way to differentiate between new registrations that came from different pages. I assume that means keeping any signifying tokens out of the form data.
Any suggestions for an approach to explore?
If your aim is to pass the #color value safely between new and create action, you can use the session for this purpose. Inside your new action you set the color:
def new
#user = User.new
session[:color] = params[:color] || 'grey' # default of grey
end
Then in the create action you can retrieve it:
def create
#color = session[:color]
end
Rails session data is stored in Cookies by default and is cryptographically signed to make it tamper-proof. It is also encrypted so anyone with access to it can't read its contents.
What is the final purpose?
If it is to know which registration forms has a better conversion rate, you are reinventing the wheel and you should have a look on A/B testing and those gems for doing it right.

Rails Forms for custom actions

I'm trying to link the input of a form to a specific action in my rails app.
Currently if I go to www.myapp.com/check/:idNumber, I'll be able to trigger the action just fine (which means routes is setup properly?). This action is basically a function call to a ruby/rails script with the parameter "idNumber" being passed to it. If the function is successful, it would return a newly created "Person" object and nil otherwise. This is different than the standard new operation as it determines the the attributes based on some information that it obtained from a database somewhere else.
Rake routes does give me the following:
check /check/:idNumber(.:format) person#check {:id=>/\d+/}
What I'm having trouble implementing is the form itself.
<%= form_tag("/check", :method => "get") do %>
<%= text_field_tag(:idNumber) %>
<% end %>
Controller action:
def check
regCheck = RegCheck.new
#person = regCheck.check_id(params[:idNumber])
if #person.name == nil
redirect_to root_path
end
end
submitting the form above would bring me to myapp.com/check?utf8=✓&idNumber=1234 instead. Can someone tell me what am I doing wrong?
I believe that using the check_path helper that is generated from the routes file is your best bet.
The form should look like this then.
<%= form_tag(check_path) do %>
<%= text_field_tag(:idNumber) %>
<% end %>
Rails forms can be finicky, especially when trying to build really customized forms.
This line
= form_for [#object]
Determines where the form goes, as well as the object that is being implemented. If you want to route the form to a different place, you can user the :url option. This options determines the path of the form, however you must keep in mind that the method is determined by the #object. If it is a new object, the method will be POST, an existing object will use a PUT method.
Let's suppose you want to update an existing object, but you want to send in data for a new object belonging to the existing object. That would look like
= form_for [#object], :as => #child_object, :url => my_optional_custom_path do |f|
# etc...
This generates a form sending a PUT request to the custom url (or the update path for #object if no custom url is supplied. The PUT request is sent with the parameter params[:child_object].
Hopefully this helps!
Best,
-Brian
I don't think it's possible the way you're trying.. The URL for the form is created before the user inputs any data.. So you need to remove the :idNumber from your routing..
If you do you get the following route:
check /check(.:format) person#check
Because the regex is removed now, you need to do this in you're controller:
def check
# Make sure ID is digits only
idNumber = params[:idNumber].gsub(/[^\d]/, '')
regCheck = RegCheck.new
#person = regCheck.check_id(idNumber)
if #person.name == nil
redirect_to root_path
end
end
You're form is allright, but you may want to use check_path like TheBinaryhood suggests..
If you really want it to be check/:idNumber you may also be able to submit the form to another action and redirect it to the right path from there..

Resources