Check for save after update Rails 4 - ruby-on-rails

An update method for one of my controllers looks like this:
def update
#node = Node.find(params[:id])
#video = #node.media
#node.update(node_params)
#video.update(title: #node.name, description: #node.description)
end
I need a callback to make sure that the #node and #video updated. What's the best way to do this?

You can test if the update succeeds...
if #node.update(node_params)
# handle success
if #video.update(title: #node.name, description: #node.description)
# etc...
end
else
# handle fail
end
In an ajax request, you can put the condition in the view:
<% if #node.errors.any? %>
alert('<%= j(#node.errors.full_messages.join("\n")) %>');
<% else %>
alert('Success')
<% end %>

Based on what you said, I think this is what you might want
def update
#node = Node.find(params[:id])
#video = #node.media
#node_saved = true
#video_saved = true
begin
#node.update_attirbutes!(node_params)
rescue ActiveRecord::RecordInvalid
#node_saved = false
end
begin
#video.update_attributes!(title: #node.name, description: #node.description)
rescue ActiveRecord::RecordInvalid
#video_saved = false
end
render "mypage"
end
Inside your update.js.erb
...
if (<%= #node_saved %> === false) {
// trigger flash message
};
if (<%= #video_saved %> === false) {
// trigger flash message
};
I may still be misunderstanding what you want, but this or #Swards answer should answer your question.

Rails' .save and .update return true/false (boolean) depending on whether the action was successful.
The standard way of utilizing that in your flow is as follows:
def update
#node = Node.find params[:id]
respond_to do |format|
if #node.update(node_params)
format.js #-> invokes /views/nodes/update.js.erb
format.json { render json: #node.to_json }
format.html
else
format.js #-> invokes /views/nodes/update.js.erb
format.json
format.html
end
end
end
You shouldn't be using the #video.update using the exact same data as #node - you can do that in the model. You'll be best using associations etc, which I can explain if required.
#app/models/node.rb
class Node < ActiveRecord::Base
belongs_to :media #?
before_save :set_media, on: :update
private
def set_media
self.media.name = self.name
self.media.description = self.description
end
end
Ajax
My front-end UI developer has some jQuery that I need a callback for
Since I don't know the spec for this, I cannot give you any specific code.
What I can tell you is that if you're using ajax, you have to make sure you understand the difference between server-side & client-side code.
I don't want to insult your intelligence but I'll explain it for posterity's sake...
Frontend ajax will look like this:
#app/assets/javascripts/application.js
$(document).on("click", ".element", function(){
$.ajax({
url: "...",
data: ....,
success: function(data) { },
error: function(response) {}
});
});
... since this is client side code, you can only deal with data returned from the server. A common misunderstanding is many people thinking they'll somehow be able to use Ruby code/methods in this. No.
You can only send back pre-packaged data to this, so if you wanted your data to be condition, you'd do something like this:
def update
#node = Node.find params[:id]
#returned = "yes" if #node.update(node_params)
render [x], layout: !request.xhr?
end
This will allow you to send back conditional data, with which you'll be able to use the front-end JS to manipulate:
$.ajax({
...
success: function(data) {
... do something with data
}
--
If you wanted to use server side code, you'll be better using the inbuilt format.js functionality:
def update
#node = Node.find params[:id]
respond_to do |format|
format.js #-> app/views/nodes/update.js.erb
end
end
This does allow you to use Ruby code, as the JS is rendered server-side, and passed to the browser. I'm not sure as to the specifics, but I know you can do the following with it:
#app/views/nodes/update.js.erb
<% if #node.something %>
$("js")....
<% else %>
...
<% end %>

Related

How to prevent directly sending the post request in the backend in rails?

I am currently have a form that search for a registrant and has a validation on the front-end to verify that all required fields are filled out, which it throws an error all the fields are left blank after submitting, BUT when anyone send a direct POST request(without using the ui) they can bypass all the required field. I have been told that I should specify a validation so that it can prevent someone sending direct POST request through other means such as postman.
Here is my current controller:
class RegistrantsController < ApplicationController
before_action :set_search, only: %i[search_by_name]
def search_by_name
authorize Registrant, :search?
session.delete(:search_for_patient)
if search_params[:first_name].present? && search_params[:last_name].present? && search_params[:date_of_birth].present?
#registrants = Registrant.search_by_name(search_params.transform_values(&:strip))
end
respond_to do |format|
format.html { render 'search'}
format.js { render partial: 'patient_search_result'}
end
end
private
def search_params
params.permit(:first_name, :last_name, :date_of_birth, :identification_type, :identification_number)
end
def set_search
#q = Registrant.ransack(params[:q]&.transform_values(&:strip))
#registrants = params[:q] ? #q.result : nil
#patient = Registrant.find(session[:patient_id_to_add_caregiver]) if session[:patient_id_to_add_caregiver]
hospice_id = session[:hospice_id_to_add_caregiver] || session[:hospice_id_to_add_patient]
#hospice = Hospice.find(hospice_id) if hospice_id
end
end
Im not sure if that's enough information but let me know if more information need to be clarified. Thank you
UPDATE:
Here is the route:
match 'registrants/search_by_name',
to: 'registrants#search_by_name',
as: 'name_search_registrants',
via: [:get, :post]
One of the solutions would be to change the code in your controller to something like this:
def search_by_name
# some code omitted
if search_params[:first_name].present? && search_params[:last_name].present? && search_params[:date_of_birth].present?
#registrants = Registrant.search_by_name(search_params.transform_values(&:strip))
respond_to do |format|
format.html { render 'search' }
format.js { render 'patient_search_result'}
end
else
flash[:alert] = "Please provide all the data required for a search"
respond_to do |format|
format.html { render 'search_error' }
format.js { render 'search_error' }
end
end
end
Then, you would have to create two new views to display the error to a user. The simplest way would be something like this:
app/views/registrants/search_error.html.erb
<div><%= flash[:alert] %></div>
app/views/registrants/search_error.js.erb
alert('<%= flash[:alert] %>');
Of course in production it should be a bit more user friendly but my code can be extended a bit to achieve that.

Rails form_tag remote example

I have this incredible simple form:
<%= form_tag("/portal/search", method: 'get', remote: true ) do %>
<%= label_tag(:query, 'Search for:') %>
<%= text_field_tag(:query) %>
<%= submit_tag("Find") %>
<% end %>
<div id="results"></div>
I know from the documentation, I can use AJAX through the remote option:
<%= form_tag("/portal/search", method: 'get', remote: true ) do %>
Then I get stuck. I know how to generate results in a view/partial, but how do I get that partial inside the div element? I know a bit of JQuery, so I can select the element. I found a lot of results, but they seem to miss that lightbulb moment for me. I am using Rails 4.1.6
My results are just simple records from a model, like Book (title, author)
Thank you for helping!
EDIT
I've won the cup for missing the point by a long shot. I had to add the search.js.erb inside the view folder instead of assets.
Let's say you get #results out of your search method.
In the controller, search method:
respond_to do |format|
format.html {render or redirect_to wherever you need it}
format.js
end
Then all you need is a file named search.js.erb to run the required js at the end of your request. It might look something like:
$('#results_div').html("<%= j #results.pluck(:title).join('<br/>') %>")
When you add remote: true jquery-ujs will provide you the ajax request (by default this javascript lib is required in app/assets/javascripts/application.js).
The ajax call will request a 'text/javascript' response. for that reason your server code should reply with:
# action
def search_query
respond_to do |format|
format.js {
# additional code
}
end
end
If in your view (search_query.js.erb) you provide javascript, it will be executed. That is why everyone is replying you with a $('#my_div_id').html('my html text') suggestion, which when executed will replace your div content with the new HTML.
If for some reason you want to return a json data structure, then you should provide a different data-type:
form_tag("/jquery_ujs/search_query", remote: true, 'data-type' => :json) do
# ....
end
And you should reply with:
# action
def search_query
respond_to do |format|
format.json { render json: #my_object }
end
end
And handle the success event:
<script>
$(document).ready(function(){
$("form#my_form").on('ajax:success', function(event, data, status, xhr) {
console.log("Great!");
// use data to access to your json data structure
});
$("form#my_form").on('ajax:error', function(event, xhr, status, error) {
console.log("sorry mate!");
});
// ....
})
</script>
You can also request a html response (in case you want to return a table, for instance), with :'data-type' => :html and format.html { render layout: false }
Ajax
Firstly, the Rails UJS (unobtrusive javascript) driver just gives you a "canned" way to send an ajax request to your browser. To avoid confusion, it's best to appreciate that you will be sending Ajax requests regardless of whether you use Rails UJS or the standard Ajax method
This means that the process of capturing the response from your Ajax is still the same - you need to make sure you have to catch the response from the system
Either :-
#app/assets/javascripts/application.js
$(document).on("ajax:success", ".element", function(status, data, xhr) {
// do something here
});
or
#app/controllers/portal_controller.rb
class PortalController < ApplicationController
def search
respond_to do |format|
format.js #-> app/views/portal/search.js.erb
format.html
end
end
end
#app/views/portal/search.js.erb
//something here
Fix
how do I get that partial inside the div element
You'll be able to use JS:
#app/controllers/portal_controller.rb
class PortalController < ApplicationController
def search
#results = ...
respond_to do |format|
format.js
format.html
end
end
end
#app/views/portal/search.js.erb
$(".div").html("<%=j render #results %>");

Rails AJAX - don't redirect or render

Using rails and .js.erb to make an AJAX request (and append values to a div), how do you prevent rendering a new layout? In other words, stay on the same page without going anywhere and just append the fresh data from the server in a div. No reloading the same page, no redirecting.
At the moment my controller looks like this
def update_shipping
#order = Order.find(params[:id])
#order.shipping_option_id = params[:shipping_options]
#order.save!
respond_to do |format|
format.js
format.html
end
end
and my form like zisss:
<%= form_tag update_shipping_order_path(#order), method: :put, remote: true do %>
<%= select_tag 'shipping_options', #options_for_select, onchange: 'this.form.submit()' %>
<% end %>
and my routes look like a so:
resources :orders do
member do
put :update_shipping
end
end
But I get a 'Template is Missing' error
Please help!!
You need to add a update_shipping.js.erb file under app/views/your_controller/ directory. Note the name of the javascript file should be same as the action. Since you have a remote:true in your form so rails will try to render a javascript template in your case update_shipping.js.erb.
Now in your update_shipping.js.erb file write some basic javascript to update the page elements like
#update_shipping.js.erb
$('.some-div').html(<%=j #model.some_value' %>)
Try this:-
respond_to do |format|
format.js { render :nothing => true }
format.html
end
If you don't want to render a layout, you can use !request.xhr? like so:
respond_to do |format|
format.html { layout: !request.xhr? }
format.js
end
If you're looking to get your ajax-powered JS to fire, you just need to call your .js.erb file the same as your view:
#app/views/controller/update_shipping.js.erb
alert("This JS is returned & fired after the Ajax request");
You'll be best doing this in your routes.rb too:
resources :orders do
put :update_shipping
end
A little late, I came across this searching for the same issue. It must of slipped out of my mind at some point while working with action cable, but what is needed is a http response code of no_content. Http response codes tell the browser how to act when a request is returned. Here is a link to a list of them, and their symbols in rails. More on 204 no content
Here is how it would look:
def update_shipping
#order = Order.find(params[:id])
#order.shipping_option_id = params[:shipping_options]
#order.save!
head :no_content #or head 204
end
edit: what solved the solution for me was a link provided by William Denniss in this stack overflow question

Change redirect_to format to :js in Rails

I have a controller action that should only be accessed by users that are logged in. If the user is not logged in, I want to redirect them to the login form, which an ajax-powered lightbox. Although the initial request is in html format, I need to change is to js. Right now, I'm trying it like this:
def new
if user_signed_in?
#company = Company.new
else
redirect_to new_user_session_path, format: 'js'
end
end
Unfortunately, it's still treating the redirect as though it's in the html format. Is there someway to get the redirect to be treated as js?
I was try this in rails 3.2.13 n it's works, i hope it's can solve
if user_signed_in?
.....
else
if request.xhr?
flash[:notice] = "Please login."
flash.keep(:notice)
render :js => "window.location = #{new_user_session_path}"
else
.....
end
end
I think you might be looking for a respond_to block along with that before filter mentioned earlier:
class CompaniesController < ApplicationController
before_filter :login
def new
#company = Company.new
render json: #company, layout: false # layout false if you do not want rails to render a page of json
end
private
def login
if !user_signed_in?
#your login code here
end
end
end
Don't forget to set your dataType ajax option to json:
$.ajax({
url: "company/new" //your rails url, what is there is just my best guess
data: query, //whatever paramaters you have
dataType: "json", // or html, whichever you desire
type: "GET",
success: function (data) {
//your code here
}
});
For anyone coming to this and using Rails 5 I did this and this works inside the youractionname.js.erb file if one sets a #redirect variable inside the controller youractionname function when one wants to redirect.
# ruby control flow
<% if #redirect != nil %>
// javascript call to use rails variable as redirect destination.
location.replace("<%=#redirect%>");
<% end %>

How can I restrict the number of records (reviews) added by a user?

How can I go about restricting the number of reviews a user can write for a venue to just one?
I would also like a flash message prompt if they try to review a venue twice.
I'm not too sure what code I should to include in my question but heres the create review method:
Review controller
def create
#review = current_user.reviews.create!(params[:review])
#review.venue = #venue
if #review.save
flash[:notice] = 'Thank you for reviewing this venue!'
respond_to do |format|
format.html { redirect_to venue_path(#venue) }
format.js
end
else
render :action => :new
end
end
Thanks for any help its much appreciated!
edit
I've added this helper method into the venues controller:
def reviewed?
if current_user.reviews.for_venue(#venue).empty?
true
else
false
end
end
and wrapped my form in:
<% if reviewed? %>
form
<% end %>
but this just returns undefined method `reviews' for VenuesController
It would be better if you could prevent the user from reviewing the venue in the first place. In your view(or create a helper) do a check if the venue was already reviewed by the user(many ways to do this). If it was, don't show the review form. Easy. If you're pretty OC, that's when you check in the controller.
Maybe you should use a before_validation callback, so that you can check if the venue has been already reviewed by the user. You can include the venue in the create line:
current_user.reviews.create!({:venue_id => #venue.id}.merge(params[:review]))
And for the validation, use something like this:
before_validation :check_if_already_reviewed
def check_if_already_reviewed
if (check if already reviewed)
return false
else
return true
end
end

Resources