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

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');
}
});
});
}

Related

Can you do a before create action on a rails controller

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

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.

Ruby on Rails: Action without View

I have what I think is a very simple problem. I'm coming from a PhP background, and used to do this all the time, so I may be looking at this the wrong way.
I am trying to create an ajax handler in RoR. When the user clicks a button, javascript fires off a POST, and gives the user feedback using the "success:" parameter of jQuery's ajax function.
The problem is, RoR is trying to load a view for the ajax handler, when I really just need a few lines in the controller to do the database work, and echo out a status code that will be interpreted by the user's javascript.
This is all just a mailchimp subscribe holding page, so I am only using the 'home' controller.
My Routes;
map.root :controller => 'home'
map.connect '/mcsubscribe', :controller => 'home', :action => 'mcsubscribe'
My Home Controller;
class HomeController < ApplicationController
def index
# no content
end
def mcsubscribe
print params[:email]
end
end
And my testing javascript, just so you understand what's going on;
function mcSubscribe() {
var email = jQuery("#signup_input_email").val();
jQuery.ajax({
type: "POST",
url: "http://domain.com/mcsubscribe",
data: "email=" + email,
cache: false,
success: function(result) {
alert(result);
}
});
}
I thought this would be a common problem, but I've googled around and only managed to find suggestions to redirect, as the user will never visit the /mcsubscribe page, that doesn't seem appropriate.
Have a look at http://guides.rubyonrails.org/layouts_and_rendering.html#using-render
You might need render :nothing => true
EDIT: misread your question, render :text => "yourtext" should fit your needs
Using
print params[:email]
will just print that value to the application logs, not into the response.
You want this:
render :text => params[:email]
In Rails 5 you need to use 'plain':
render plain: params[:email]
You'd need something like this:
def mcsubscribe
# Do something to unsubscribe
respond_to do |format|
format.html { redirect_to(success_page) }
format.js { render :text => params[:email] }
end
end
If ajax is used, the params[:email] is send as text. If a HTML format is required (ie user clicked a link or filled in a regular form) a redirect is issued to tell the user the subscription has been successful.

Rails 3 Custom Method Location

I am currently trying to add some parsing methods to a controller method in a Rails 3 application.
I have a controller action as follows:
def control
#device = Device.find(params[:id])
<do things>
parse_return(#returned_data)
end
and I added a custom method to the controller as below (this method would not have any routes and would only be accessible to controller actions):
def parse_return
<parse data>
end
but this does not appear to allow the parse_return method to be used. Is there somewhere else in the Rails app that I can put re-usable methods?
Thanks!
At a first glance it seems that you fail to render a response. Is it true that control action doesn't have an associated view?
In this case you have to manually call render in your action. For example, to render JSON response you can do this:
def control
# ...
render :json => parse_return(#returned_data),
:content_type => 'application/json',
:layout => false
end
You should include what the errors are.
What happens if you try this?
def parse_return(returned_data)
<parse data>
end
Perhaps the method is not expecting an parameter to be passed along with it.

Rails - Controller Action can only be executed by JS call

In my view file, I use a collection_select with an :onchange => remote_function that execute another action from the same controller (the action will eventually update the page content)
I'd like this action to be only accessible when called by the JS but not when the corresponding route is directly entered as an URL in the browser.
Any idea how I could do this ?
You can use request.xhr? to check the request type, either its AJAX request or others (post, get). It returns true or false. If true you can perform the action.
You could use respond_with and respond_to
class MyController
respond_to :js
def index
respond_with(#users = User.all)
end
end

Resources