Can you do a before create action on a rails controller - ruby-on-rails

I have a create action but I only want the create action to create IF 2 items in the form that is trying to be created are true. Is that possible to do a if statement before completing the create action or is that making my controller too fat?
I have a form that I want to submit to my db, but based on some of the questions in the form, I want the form to bring up another page (A payment page) BEFORE it puts the form info into the db. Only after the charge is successful will the controller put the info in the db if the charge goes thru.
So I want to know is it DRY or even possible to put logic that will preceed the create action in the controller?
--Additional Information--
So Im still trying to get this right...
In my form controller I have
def intermediary
if #model.who_pays == true
#amount = #model.how_much_to_pay
redirect_to charge_path, :notice => "Please make payment before proceeding"
else
redirect_to model_path #to create object
end
end
and then in my actual form I had
Model.new
but i'm changing it to
<%= form_for [#model, url: [intermediary_path]] do |f| %>
is this logic correct?

Without the extra form, this should be handled in your model with validations:
#app/controllers/your_controller.rb
class YourController < ApplicationController
def new
#model = Model.new
end
def create
#model = Model.new model_params
#model.save
end
end
In your model...
#app/models/model.rb
class Model < ActiveRecord::Base
validate :true_questions, on: :create
private
def true_questions
errors.add(:option1, "Must be true") unless option1
errors.add(:option2, "Must be true") unless option2
end
end
With the form, you'll have several choices:
An "intermediary" action with sessions / cookies
JS / Ajax
As mentioned in the comments, this is a question of "state" -- IE how you're able to keep the data integrity throughout the process.
You should check out how stripe handle this - they send the user to an authentication page (where they receive a token), are redirected back to "confirm" page, from which they're able to proceed.
-
Intermediary
If you have an intermediary action, you'll have to change your flow a little:
#config/routes.rb
resources :controller do
get :intermediary, on: :collection
end
#app/controllers/your_controller.rb
class YourController < ApplicationController
def intermediary
# perform validations in the controller
# set instance or session vars if necessary
# render payment page
end
def create
# returned from payment system - input into DB
end
end
This would work quite well, although it means having custom routes and another action.
-
Ajax
A different approach would be to use either Ajax / JS.
This would be achieved by sending data to your app (probably to your "create" action) with certain switches defined. These would give you the ability to perform the required validations before invoking the payment view.
The payment view would be where the Ajax would come in:
#app/assets/javascripts/application.js
$(document).on("submit", "#____", function(){
$.ajax({
url: "controller",
method: "POST",
data: .......
success: function(data){
// show payment page
}
});
});
#app/controllers/your_controller.rb
class YourController < ApplicationController
def create
if params[:x]
# perform validation
# return payment form
else
# capture payment data
# save to db
end
end
end

Related

how to post to form from within an action

I have a link that goes to an action, so if someone clicks:
localhost/cart/checkout?pid=123
It goes to the CartController checkout action which then displays a form.
But in some circumstances (depending on when I load the Product with id 123) I may not need to display the form, I can just load the data and then post to the form's action.
How can I programatically post to where my form was going to post with data.
class CartController < ApplicationController
def checkout
pid = params[:pid]
product = Product.find(pid)
if product....
# no need to display view, just post to handleCheckout
end
end
# checkout form posts to this action
def handleCheckout
end
end
I have not done something like this before but I have some idea so please note that none of the is tested.
If your handleCheckout action is meant to be used as a Get request then you can redirect to this action with the params. like:
class CartController < ApplicationController
def checkout
pid = params[:pid]
product = Product.find(pid)
if product....
redirect_to action: "handleCheckout", params: params
# Not sure whether you will get it as 'params' or params[:params] in handleCheckout action
end
end
# checkout form posts to this action
def handleCheckout
end
end
And if handleCheckout is meant to be used as post Then above method might not work since redirect_to will create a new http Get request to that action. so you may try something like this:
def checkout
pid = params[:pid]
product = Product.find(pid)
if product....
handleCheckout
# params since is a global hash and above method has access to it
end
end
# checkout form posts to this action
def handleCheckout
# your other code
redirect_to 'some_action' and return
# in above line you have to return with a render or redirect
# Otherwise it will render 'checkout' template with render and redirect or
# it will throw double render error if you have a simple render or redirect without explicit return
end
As I mentioned, I have not tried any of above code. There might be errors. I hope it helps.

Action show to find value from hash instead of id

I am still learning rails and have done a lot of readings, but I am not very clear about how params, 'show' actions work yet.
For example we have UsersController, 'index' action is showing all the users with the code #user = User.all, and 'show' action is looking into each users, by using the code #user = User.find(params[:id])
I understand that they are all from the database, where User is a model.
However in my scenario, what if the data I am showing in views, doesn't go through database, instead in the 'index' action it is something like this -
#user = [{name => "alex"}, {name => "peter"}, {name => "john"}]
and in my 'show' action, how can I write the code so that it finds the users by name?
In your Rails app, the data that you show in your views, do not necessarily have to come from/through the database. You can always show any data you want in your views.
For example, in your index action, if you have this:
#users = [{name => "alex"}, {name => "peter"}, {name => "john"}]
Then, in your index view, you can show only those users by looping through the #users instance variable.
Same for show page as well.
If you want to show the users by name in your show page, you have to set the users by name in an instance variable e.g. #users_by_name:
#users_by_name = User.find_by(name: user_name)
# or you can hard code the values if you want like index action
and then this #users_by_name instance variable will be available in your show view so that you can loop through that and show the user names.
Originally, the show page is designed for showing a particular user related information, but you can show whatever information you want going against the conventions.
To be able to have a route like this: localhost:3000/users/alex that will show the user alex's information, you can add a route in your routes.rb file:
get 'users/:name', to: "users#show"
And, in your controller's show action, something like this:
def show
#user = User.find(params[:name])
end
Then, show the #user information in your view page.
P.S. This is not a good idea to find user by name as there might be more than one user with same name in the database and it will create conflict/ or give wrong data in such situations.
In show action , we search the user specific record not all.
So , we have to provide some unique identifiers as parameters to find the specific record.
For eg. Your view should be similar to the params we are passing as below:
<% #user.each do |user| %><br>
<%= link_to user.name, user_show_path+"?name="+user.name %><br>
<% end %><br>
In show action , write the code
def show
#user = User.find_by(:name => params[:name])
end
Also in routes.rb , write the below code:
get 'users/:name', to: "users#show"
For the above solution, make sure that name field will be unique.
My original question is that if it is possible for 'show' action not to go through database
Sure.
Your show action can be the following if you wanted it to:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = "me"
end
end
You really don't have to do anything specific in your application, Rails is just a framework and has certain conventions if you want it to work efficiently.
What you're asking is if you can populate your #user object from a third party set of data...
... Yes you can ...
The way to do it would be in the model, not the controller:
#app/models/user.rb
class User < ActiveRecord::Base
# populates from Hash
end
You'd then be able to populate the data in the controller from the model again:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User.__________ #-> pull from your hash
end
end
finds the users by name
That's simple - just pass the name through the url: url.com/users/marine_lorphelin
This will set the :id parameter to marine_lorphelin, with which you'll be able to look up the name through your model:
#app/controllers/users_controller.rb
class UsersController < ApplicationController
def show
#user = User._______
end
end
If you were using a database with your user model, you'd be able to use the following:
def show
#user = User.find_by name: params[:id]
end
Since you're not, you'll have to attach your XML hash to your model somehow. This, I don't know without specifics such as where you're getting your data from, how you're accessing it, and which routes you're going to send to invoke it.

Ruby on Rails controller design

When I look at examples of Rails controllers, I usually see something like this:
class WidgetController < ActionController::Base
def new
#widget = Widget.new
end
def create
#widget = Widget.new(params[:id])
if #widget.save
redirect_to #widget
else
render 'new'
end
end
end
This works, but there's a couple problems:
Routes
If I add widgets to my routes.rb file:
Example::Application.routes.draw do
resources :widgets
end
GET /widgets/new will route to new and POST /widgets will route to create.
If the user enters incorrect information on the new widget page and submits it, their browser will display a URL with /widgets, but the new template will be rendered. If the user bookmarks the page and returns later or refreshes the page, the index action will be called instead of the new action, which isn't what the user expects. If there's no index action or if the user doesn't have permission to view it, the response will be a 404.
Duplication of code
As a contrived example, let's say I had some tricky logic in my new method:
def new
#widget = Widget.new
do_something_tricky()
end
Using the current approach, I'd duplicate that logic in new and create. I could call new from create, but then I'd have to modify new to check if #widget is defined:
def new
#widget ||= Widget.new
do_something_tricky()
end
Plus, this feels wrong because it reduces the orthogonality of the controller actions.
What to do?
So what's the Rails way of resolving this problem? Should I redirect to new instead of rendering the new template? Should I call new inside of create? Should I just live with it? Is there a better way?
I don't think this is a problem in "the rails way" and there is no builtin functionality to allow this without getting your hands dirty. What does a user expects when bookmarking a form they just submitted and had errors? Users don't know better, and they shouldn't bookmark a failed form.
I think redirecting to new_widget_path is the cleanest solution. Yet, you should keep the errors and display them on the form. For this I recommend you keep the params in session (which I expect to be smaller than a serialized Widget object).
def new
#widget = widget_from_session || Widget.new
end
def widget_from_session
Widget.new(session.delete(:widget_params)) if session[:widget_params].present?
end
private :widget_from_session
# Before the redirect
session[:widget_params] = params
The code is self explanatory, Widget.new will only be called when widget_from_session returns nil, this is when session[:widget_params] is present. Calling delete on a hash will return de deleted value and delete it from the original hash.
UPDATE Option 2
What about submitting the form using ajax? Your controller could benefit from:
respond_to :html, :json
...
def create
#widget = Widget.new params[:widget]
#widget
respond_with #widget, location: nil
end
Based on the response code (which is set by Rails: 201 Created or 422 Unprocessable Entity), you could show the errors (available in the body of the response when validations fail) or redirect the user to #widget
This is how StackOverflow does it: https://stackoverflow.com/questions/ask. They submit the form asynchronously.
In general, I think the Rails way of solving the problem would be to put the tricky method onto the model or as a helper method, so the controller stays "thin" and you don't have to make sure to add custom behavior to both #new and #create.
EDIT: For further reading, I'd recommend the "Rails AntiPatterns" book, as they go through a lot of these common design issues and give potential solutions.
you put do_something_tricky() in its own method and call it inside the create action (but only when you're rendering the new template, ie when validation fails).
As for the bookmark issue, I don't know a good way to prevent that but to modify the routes and set the create action to the new action but using POST
get '/users/new' => 'users#new'
post '/users/new' => 'users#create'
UPDATE: using resources
resources :platos, except: :create do
post '/new' => 'plates#create', on: :collection, as: :create
end
then you can use create_platos_path in your forms
You don't need to write same function in two action , use before_filter instead.
If you want to have "widget_new_url" after incorrect submission then in your form add url of new widget path something like :url => widget_new_path .
Rails takes the url from Form .
I have this problem before, so I use edit action instead.
Here is my code.
Routes:
resources :wines do
collection do
get :create_wine, as: :create_wine
end
end
Controller:
def create_wine
#wine = Wine.find_uncomplete_or_create_without_validation(current_user)
redirect_to edit_wine_path(#wine)
end
def edit
#wine = Wine.find(params[:id])
end
def update
#wine = Wine.find(params[:id])
if #wine.update_attributes(params[:wine])
redirect_to #wine, notice: "#{#wine.name} updated"
else
render :edit
end
end
Model:
def self.find_uncomplete_or_create_without_validation(user)
wine = user.wines.uncomplete.first || self.create_without_validation(user)
end
def self.create_without_validation(user)
wine = user.wines.build
wine.save(validate: false)
wine
end
View:
= simple_form_for #wine, html: { class: 'form-horizontal' } do |f|
= f.input :complete, as: :hidden, input_html: { value: 'true' }
What I did is create a new action 'create_wine' with get action.
If user request 'create_wine', it will create a new wine without validation and redirect to edit action with a update form for attributes and a hidden field for compele .
If user has create before but gave up saving the wine it will return the last uncompleted wine.
Which means whether use save it or not, the url will be the same to /wines/:id.
Not really good for RESTful design, but solve my problem. If there is any better solution please let me know.

Redirecting to different actions without using a query string

I am trying to figure out the best way to do the following (there are a few ways I can think of, but I want to know what the best way to handle it is):
A user is putting together a shipment, and then clicks the "Send" link, which sends him to the /shipments/:id/confirm page. The confirm action checks to see if the user has a completed ShippingAddress; if not, it sends him to the ShippingAddress#new. (If he does, it render the confirm page.
I want the user to be able to complete the ShippingAddress#new page, submit it, and then be redirect back to the /shipments/:id/confirm. How can I do that? How can I pass the :id to the ShippingAddress#new page without doing something like redirect_to new_shipping_address_path(shipment_id: #shipment.id) in the Shipment#confirm action? Or is that the best way to do that?
class ShipmentsController < ApplicationController
def confirm
#shipment = Shipment.where(id: params[:id]).first
unless current_user.has_a_shipping_address?
# Trying to avoid having a query string, but right now would do the below:
# in reality, there's a bit more logic in my controller, handling the cases
# where i should redirect to the CardProfiles instead, or where I don't pass the
# shipment_id, and instead use the default shipment.
redirect_to new_shipping_address_path(shipment_id: #shipment.id)
end
end
end
class ShippingAddressesController < ApplicationController
def new
#shipment = Shipment.where(id: params[:shipment_id]).first
end
def create
#shipment = Shipment.where(id: params[:shipment_id]).first
redirect_to confirm_shipment_path(#shipment)
end
end
[In reality, there is also a CardProfiles#new page that needs to be filled out after the shipping address is].
Try calling render instead of redirect_to, and set the id into an instance variable. Adjust the view logic to pull that instance variable if it exists.
#shipment_id = #shipment.id
render new_shipping_address_path
In the view
<%= form_for #shipment_address do |f| %>
<% if #shipment_id %>
<%= hidden_field_tag :shipment_id, #shipment_id %>
<% end %>
I don't know your view logic entirely, but giving an example.

Ruby on Rails: how to create an object without switching the view

I am incredibly new to rails, so I think my questions is pretty simple. I essentially have an object submit button on a view page (so runs create and save for the specified object) but on selecting the button I don't want the view to change at all (so just stay with the same page that was already loaded). I'm having trouble editing the create action in my object controller so that nothing happens to the view (is this even possible? or is the idea that a new view must occur for every action).
Thanks for all your help!
Lisa
This can be done by creating an action in the controller,
class YourController < ApplicationController
def index
end
def youraction
end
end
,then set a route to it in the routes.rb
and then simply set your form to point to that url and add the
remote: true
option to the form tag.
So you want to store the object and you dont want the URL to change.
In your create action, after the object is saved. Redirect to that view.
so something like:
$ if #object.save
redirect_to :action => "index"
# or
# redirect_to object_path # you can set this in config/routes.rb
# or
# redirect_to :action => "show", :id => #object.id
You can do this by using ajax request:
in your controller create an action that does the job of creating the object you want:
class SomeController < ApplicationController
def index
#this is your view controller
end
def youraction
#create an object
render :nothing => true
end
end
and then bind your button with a javascript function that does the ajax request:
HTML
<button id="submit_button">Submit</button>
Javascript
$(document).ready(function (){
$('#submit_button').click(function(){
$.ajax({
url: 'url_to_the_controller/youraction',
type: 'POST'
success: function (){
alert('success');
},
error: function (){
alert('error');
}
});
});
}

Resources