Passing parameter value from react component - ruby-on-rails

In my app, people can comment on pets' images. I am using the react example from here, although I changed quite a few stuff.
Right now, it is successful in displaying the existing comments. Now, when a user is creating a comment, I have to pass the comment body, user id, and pet id. I was trying to do the following:
var CommentForm = React.createClass({
handleSubmit:function()
{
var user=this.refs.user_id.getDOMNode().value.trim();
var comment=this.refs.body.getDOMNode().value.trim();
var pet_id=this.refs.pet_id.getDOMNode().value.trim();
this.props.onCommentSubmit({comment:comment, user:user, pet:pet_id});
if(!user||!comment||!pet_id)
return false;
var formData = $( this.refs.form.getDOMNode() ).serialize();
this.props.onCommentSubmit( formData, this.props.form.action );
// reset form
this.refs.body.getDOMNode().value = "";
},
render: function () {
return (
<form ref="form" className="comment-form" action={ this.props.form.action } accept-charset="UTF-8" method="post" onSubmit={ this.handleSubmit }>
<p><input type="hidden" name={ this.props.form.csrf_param } value={ this.props.form.csrf_token } /></p>
<p><input type="hidden" ref="user" value={ this.props.user_id } /></p>
<p><input type="hidden" ref="pet_id" value={ this.props.pet_id } /></p>
<p><textarea ref="body" name="comment[text]" placeholder="Say something..." /></p>
<p><button type="submit">Post comment</button></p>
</form>
)
}
});
And apparently, it doesn't look like it is passing the pet_id correctly, because I am getting the error message
ActiveRecord::RecordNotFound in CommentsController#create
Couldn't find Pet with 'id'=
My CommentsController looks like
def create
#pet = Pet.find(params[:pet_id])
#comment = #pet.comments.new(comment_params)
#comment.user = current_user
For further clarification, I have three models, Pets, Users and Comments, and when users make comments, the comment gets the user_id, and pet_id as its parameters.
edit:
My react component looks like
<%= react_component('CommentBox',
{:presenter => #presenter.to_json},
{:prerender => true}) %>
and my PetController looks like
def show
#comments = #pet.comments
#user = current_user
#presenter = {
:comments => #comments,
:user => current_user,
:pet_id => #pet,
:form => {
:action => comments_path,
:csrf_param => request_forgery_protection_token,
:csrf_token => form_authenticity_token
}

So there are a few issues I can see. Firstly your using ref where you should be name.
<input type="hidden" ref="pet_id" value={ this.props.pet_id } />
should be
<input type="hidden" name="pet_id" value={ this.props.pet_id } />
Your setting both an action and an onSubmit. Is there a reason to do this? Why not just read it from the props when you perform the ajax request? This is most likely causing your form to be submitted and the browser to load another page. The form submitting has nothing to do with what is on the server. The issue is in your client side code.
I would also consider putting your model values in to their own array. This is generally what rails expects back from the server. In your case it should be params[:pet][:id] not params[:pet_id]. Many of the rails active record methods such as update attributes can then be directly called giving you less verbose code.
#pet = Pet.find(params[:pet][:id])
#pet.update_attributes(prams[:pet])
#pet.save

Related

Ruby/Rails button and search filtering

I've added the Active and Archived buttons to this page for extra filtering.
The existing search box functionality uses the following js.coffee which calls the controller index to pull the data from the db.
triggerSearch: (e) ->
if searchCompanyTimer
window.clearTimeout searchCompanyTimer
searchCompanyTimer = window.setTimeout(( ->
searchCompanyTimer = null
query_text = $(e.currentTarget).val()
el_id = $(e.currentTarget)[0].id
$.get( "companies", "q[name_cont]": query_text )
), 500, e)
I have added 2 similar js.coffee methods which set an active flag to true or false depending on which button was pressed.
Here is one of those methods.
triggerShowActive: (e) ->
if searchCompanyTimer
window.clearTimeout searchCompanyTimer
searchCompanyTimer = window.setTimeout(( ->
searchCompanyTimer = null
$.get( '/companies', {active: true} )
), 500, e)
here is part of my controller.
class CompaniesController < ApplicationController
respond_to :js, :json, :html
$active_inactive_flag = true
def index
puts "params are: #{params}"
puts "active param is: #{params[:active]}"
puts "#active_inactive_flag pre any conditions is: #{$active_inactive_flag}"
$active_inactive_flag = params[:active] ? params[:active] : $active_inactive_flag
puts "#active_inactive_flag after check on params[:active] is: #{$active_inactive_flag}"
if $active_inactive_flag.try :nonzero?
$active_inactive_flag = true
end
puts "#active_inactive_flag after check if it has a value, else true - is: #{$active_inactive_flag}"
#companies =
Company.search(params[:q])
.result
.order('created_at DESC')
.page(params[:page])
.per(Settings.paginate_limit)
.where(is_active: $active_inactive_flag)
end
here is my index.html.erb file where the buttons and search code is.
<div class="row">
<div class="col-md-3">
<label>Filter By:</label>
<button class="show_active btn btn-primary" style="border-radius:10px">Active</button>
<button class="show_inactive btn btn-primary" style="border-radius:10px">Archived </button>
</div>
<div class="col-md-9">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">Filter by name</span>
<input class="form-control" id="q_name_cont" name="q[name_cont]" type="text">
</div>
</div>
</div>
I am using a global variable (tried instance and class variables also) in the controller but it's not working as expected. Sometimes the value in $active_inactive_flag switches from false to true or vice versa incorrectly. I don't want to use a global variable but I am at a loss as to how to combine both filter and search correctly. When the page loads we want Active button to be on by default and return active companies. The problem I am having is knowing what button was pressed when the search box is used.
Edit:
Here is an example of what is happening.
Any direction would be grateful.
Thanks Dave,
in js.coffee added the following:
returnActiveInctiveFlag = true
and then to the triggerSearch updated to:
$.get( "companies", { "q[name_cont]": query_text, active: returnActiveInctiveFlag } )
In my new methods updated returnActiveInctiveFlag accordingly.
Was really overcomplicating it. Thanks again for getting me thinking in the correct way.

typeahead rails 3 autocomplete with user per request

The app is using typeahead and bootstrap 3 for, autocomplete function.
but now we have so many records that , it is getting really slower.
var substringMatcher = function(strs) {
return function findMatches(q, cb) {
var matches, substrRegex;
// an array that will be populated with substring matches
matches = [];
// regex used to determine if a string contains the substring `q`
substrRegex = new RegExp(q, 'i');
// iterate through the pool of strings and for any string that
// contains the substring `q`, add it to the `matches` array
$.each(strs, function(i, str) {
if (substrRegex.test(str)) {
// the typeahead jQuery plugin expects suggestions to a
// JavaScript object, refer to typeahead docs for more info
matches.push({ value: str });
}
});
cb(matches);
};
};
$('.typeahead').each(function(elem) {
$(elem).typeahead({
hint: false,
highlight: true,
minLength: 3
}, {
displayKey: 'value',
source: substringMatcher($(elem).data('source'))
});
});
these are javasxript parts of typeahead fuction used by the app
on the view side of the form
<div class="form-group">
<label class="col-sm-2 control-label" for="typeahead_book">Title</label>
<div class="col-sm-3">
<input value="<%=#option.book.try(:title)%>" name="option[book_title]" type="text" class="form-control typeahead typeahead-remote-book" id="typeahead_book" data-provide="typeahead" data-items="10" data-source='<%=#book_titles%>'>
<p class="help-block"><small><%= $autocomplete_message %></small></p>
</div>
i use a callback as
before_filter :get_autocomplete_lists, only: [:new, :edit]
def get_autocomplete_lists
#book_titles = Rails.cache.fetch("booktitles", :force => true, expires_in: 10.minutes) do
Book.scoped.map(&:title)
end
#publisher_names = Rails.cache.fetch("publishernames", :force => true, expires_in: 10.minutes) do
Publisher.all.map(&:name)
end
#users_by_email_name = Rails.cache.fetch("users", :force => true, expires_in: 7.days) do
User.scoped.map{|user| "#{user.email} - #{user.name}"}
end
#country_names = Rails.cache.fetch("countrynames", :force => true, expires_in: 7.days) do
Country.all.map(&:first).sort
end
# #languages = LanguageList::COMMON_LANGUAGES.map(&:name)
# #country_names = Anatolialit::Country::List.sort
#
#languages = Rails.cache.fetch("languages", :force => true, expires_in: 7.days) do
Anatolialit::Language::EnList.sort
end
end
on controller that generates source of the book , as #book_titles ,
THE APP IS WORKING NOW but i have to refactor the code for performance issues.Because after 10k record #book_titles is very big.
THAN WHAT I DID
i created a new controller action on application controller
def book_titles
searchterm = (params[:title])
books =Book.any_of({ :title => /.*#{searchterm}.*/ })
respond_to do |format|
format.json { render json: books.map(&:title) }
end
end
and i defined a route to use as application controller as an api service
get 'booktitles/:title' => 'application#book_titles', format: :json
now when i use a localhost:3000/booktitles/the
it brings me as a json data all the books that indicates 'the' in the title.
I think everything Until here is ok. about refactoring.
but when i use this code below for userside per request for sourcing typeahead
$(document).ready(function() {
$('.typeahead-remote-book').typeahead({
source: function (query, process) {
return $.get('/booktitles', { query: query }, function (data) {
return typeahead.process(data);
});
}
});
});
and i changed the view part of the form to
<div class="form-group">
<label class="col-sm-2 control-label" for="typeahead_book">Title</label>
<div class="col-sm-3">
<input value="<%=#option.book.try(:title)%>" name="option[book_title]" type="text" class="form-control typeahead typeahead-remote-book" id="typeahead_book" data-provide="typeahead" data-items="10" >
<p class="help-block"><small><%= $autocomplete_message %></small></p>
</div>
</div>
*I do not know what is wrong with the situation , but it does not work *
could u please help me ? about solving the problem.
Thanks for your kind help..
best regards.
The problem is you're trying to do realime searching in the database and that is not very efficient. At least make sure you're indexing books on title. You should have a migration like:
class AddIndexOnBookTitles < ActiveRecord::Migration
def change
add_index :books, :title, unique: false #or omit false if dupes not allowed.
end
end
Then you don't want book objects, but just titles, which you should probably cache somewhere. We can talk about that later.
Don't use .map on an ActiveRecordRelation object but instead .pluck to just pluck the fields you need out of the DB.
def book_titles
books = Book.any_of({ :title => /.*#{params[:title]}.*/ }).pluck(:title)
respond_to do |format|
format.json { render json: books }
end
end
That still might be slow if you have a lot of books. But give that a try first and see if it's any better. If not, we'll need to do some caching or maybe use Elasticsearch which is designed for this type of thing.

Rails - Stripe::InvalidRequestError in Bookings#new Must provide source or customer

I'm getting the above error in my Rails app. I have the following Stripe code in my Booking model -
booking.rb
class Booking < ActiveRecord::Base
belongs_to :event
belongs_to :user
validates :quantity, presence: true, numericality: { greater_than: 0 }
validates :event, presence: true, numericality: {greater_than_or_equal_to: 0 }
before_save :set_price_to_zero_if_free
def set_price_to_zero_if_free
self.event.price >= 1 unless self.event.is_free
end
def reserve
# Don't process this booking if it isn't valid
#return unless valid?
# We can always set this, even for free events because their price will be 0.
#self.total_amount = booking.quantity * event.price
# Free events don't need to do anything special
if event.is_free?
save!
# Paid events should charge the customer's card
else
begin
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: 'token',
description: "Booking created for amount #{total_amount}")
self.stripe_charge_id = charge.id
save!
rescue Stripe::CardError => e
errors.add(:base, e.message)
false
end
end
end
end
bookings_controller.rb
class BookingsController < ApplicationController
before_action :authenticate_user!
def new
# booking form
# I need to find the event that we're making a booking on
#event = Event.find(params[:event_id])
# and because the event "has_many :bookings"
#booking = #event.bookings.new(quantity: params[:quantity])
# which person is booking the event?
#booking.user = current_user
##booking.quantity = #booking.quantity
##total_amount = #booking.quantity.to_f * #event.price.to_f
end
def create
# actually process the booking
#event = Event.find(params[:event_id])
#booking = #event.bookings.new(booking_params)
#booking.user = current_user
if
#booking.reserve
flash[:success] = "Your place on our event has been booked"
redirect_to event_path(#event)
else
flash[:error] = "Booking unsuccessful"
render "new"
end
end
private
def booking_params
params.require(:booking).permit(:stripe_token, :quantity, :event_id, :stripe_charge_id)
end
end
booking.new.html.erb
<div class="col-md-6 col-md-offset-3" id="eventshow">
<div class="row">
<div class="panel panel-default">
<div class="panel-heading">
<h2>Confirm Your Booking</h2>
</div>
<div class="calculate-total">
<p>
Confirm number of spaces you wish to book here:
<input type="number" placeholder="1" min="1" value="1" class="num-spaces">
</p>
<p>
Total Amount
£<span class="total" data-unit-cost="<%= #event.price %>">0</span>
</p>
</div>
<%= simple_form_for [#event, #booking], id: "new_booking" do |form| %>
<span class="payment-errors"></span>
<div class="form-row">
<label>
<span>Card Number</span>
<input type="text" size="20" data-stripe="number"/>
</label>
</div>
<div class="form-row">
<label>
<span>CVC</span>
<input type="text" size="4" data-stripe="cvc"/>
</label>
</div>
<div class="form-row">
<label>
<span>Expiration (MM/YYYY)</span>
<input type="text" size="2" data-stripe="exp-month"/>
</label>
<span> / </span>
<input type="text" size="4" data-stripe="exp-year"/>
</div>
</div>
<div class="panel-footer">
<%= form.button :submit %>
</div>
<% end %>
<% end %>
</div>
</div>
</div>
<script type="text/javascript">
$('.calculate-total input').on('keyup change', calculateBookingPrice);
function calculateBookingPrice() {
var unitCost = parseFloat($('.calculate-total .total').data('unit-cost')),
numSpaces = parseInt($('.calculate-total .num-spaces').val()),
total = (numSpaces * unitCost).toFixed(2);
if (isNaN(total)) {
total = 0;
}
$('.calculate-total span.total').text(total);
}
$(document).ready(calculateBookingPrice)
</script>
<script type="text/javascript" src="https://js.stripe.com/v2/"></script>
<script type="text/javascript">
Stripe.setPublishableKey('<%= STRIPE_PUBLIC_KEY %>');
var stripeResponseHandler = function(status, response) {
var $form = $('#new_booking');
if (response.error) {
// Show the errors on the form
$form.find('.payment-errors').text(response.error.message);
$form.find('input[type=submit]').prop('disabled', false);
} else {
// token contains id, last4, and card type
var token = response.id;
// Insert the token into the form so it gets submitted to the server
$form.append($('<input type="hidden" name="booking[stripe_token]" />').val(token));
// and submit
$form.get(0).submit();
}
};
// jQuery(function($) { - changed to the line below
$(document).on("ready page:load", function () {
$('#new_booking').submit(function(event) {
var $form = $(this);
// Disable the submit button to prevent repeated clicks
$form.find('input[type=submit]').prop('disabled', true);
Stripe.card.createToken($form, stripeResponseHandler);
// Prevent the form from submitting with the default action
return false;
});
});
</script>
I don't understand why I'm getting the error when I'm clearly including source within my stripe code. I've expressed it above as self.stripe_token but I've tried just stripe_token but still get the same error. My understanding is I need to provide either source or customer, not both, and that it needs to be source rather than customer.
What am I missing?
If you get "Must provide source or customer" as the error message, that means that self.stripe_token in your code must be either nil or an empty string.
You need to make sure that the source parameter's value is a valid token ID. The token should be created client-side, with Checkout or Stripe.js. Once the token has been created, you need to send it to your server (typically as a POST parameter) so you can use it in your charge creation request.
You can also check your account's logs in your dashboard to see the requests that are sent by your integration, which is very helpful when debugging issues: https://dashboard.stripe.com/test/logs/overview.
My wild guess that stripe_token isn't an attribute in the booking model. That's why when you try to retrieve it's value with self.stripe_token it will give you nil.
Try passing the token from controller params to model instance method:
controller:
if #booking.reserve(booking_params['stripe_token'])
# everything is good
else
# ops
end
model:
def reserve(stripe_token)
if event.is_free?
save!
else
begin
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: stripe_token,
....
)
# rest of method
end
Update:
currently your code has the part where total_amount is set commented out!
usually you will want to make something like:
total_amount = self.booking.event.price * 100 #not really sure how should calculate this?
charge = Stripe::Charge.create(
amount: total_amount,
currency: "gbp",
source: 'token',
description: "Booking created for amount #{total_amount}"
)

Grails validation with composite key

I'm running into a complex scenario where we are using a legacy database with composite keys and the client wants to be able alter two of the databases pks, "expenseDate" and "adjustmentNumber". In order to be able to change the pks, I've have to use a HQL query. Now of course this has caused another slew of issues with validation. My work around was to populate a domain so that I could validate against it.
So far everything works fine until we have a validation error that I'd like to return to the UI.
I have the following URL which uses the recoveryDetail controller and the edit action to render the page.
http://localhost:8080/pisr/recoveryDetail/edit?division=ALBANY&peid=PI0003&orgkey=14046701&expenseDate=07-22-2015&adjustmentNumber=1
Edit action
def edit() {
//Parse clean url expense date
params.expenseDate = new SimpleDateFormat('MM-dd-yyyy').parse(params.expenseDate)
def recoveryDetailInstance = RecoveryDetail.get(new RecoveryDetail(params))
if(recoveryDetailInstance == null) {
redirect(uri:'/')
return
}
[recoveryDetailInstance: recoveryDetailInstance, disabled: isdisabled(recoveryDetailInstance.batchOverride)]
}
And the following update action.
Update action
#Transactional
def update() {
params.pk_expenseDate = getDateParser(params.pk_expenseDate)
params.expenseDate = getDateParser(params.expenseDate)
params.adjustmentNumber = getAdjustementNumber(params)
RecoveryDetail recoveryDetailInstance = new RecoveryDetail(params);
recoveryDetailInstance.division = params.pk_division
recoveryDetailInstance.peid = params.pk_peid
recoveryDetailInstance.orgkey = params.pk_orgkey
recoveryDetailInstance .validate()
if(recoveryDetailInstance .hasErrors()) {
flash.message = "test"
respond view: "edit", model:[recoveryDetailInstance:recoveryDetailInstance]
return
} else {
def sqlParams = [
pk_division:params.pk_division,
pk_peid:params.pk_peid,
pk_orgkey:params.pk_orgkey,
pk_expenseDate:params.pk_expenseDate,
pk_adjustmentNumber:params.int('pk_adjustmentNumber'),
approved:YesNoTypes.valueOf(params.approved),
batchOverride:YesNoTypes.valueOf(params.batchOverride),
adjustmentFlag:params.adjustmentFlag,
adjustmentNumber:params.adjustmentNumber,
projectHours:new BigDecimal(params.projectHours),
percentEffort:new BigDecimal(params.percentEffort),
totalHours:new BigDecimal(params.totalHours),
expenseDate:params.expenseDate
]
RecoveryDetail.executeUpdate(recoveryDetailQuery, sqlParams)
}
Edit gsp
<g:form class="form-horizontal" url="[resource:recoveryDetailInstance, action:'update']" method="PUT">
<!-- hidden fields contain params from url (composite key)-->
<g:hiddenField name="pk_division" value="${recoveryDetailInstance?.division}"/>
<g:hiddenField name="pk_peid" value="${recoveryDetailInstance?.peid}"/>
<g:hiddenField name="pk_orgkey" value="${recoveryDetailInstance?.orgkey}"/>
<g:hiddenField name="pk_expenseDate" value="${formatDate(format:'MM/dd/yyyy',date:recoveryDetailInstance?.expenseDate)}" />
<g:hiddenField name="pk_adjustmentNumber" value="${recoveryDetailInstance?.adjustmentNumber}"/>
<div class="row">
<div class="col-md-6">
<g:render template="form" model="[recoveryDetailInstance: recoveryDetailInstance, 'mode':'edit']"/>
</div>
</div>
<div class="form-group">
<div class="col-sm-offset-2 col-sm-10">
<g:actionSubmit class="btn btn-primary" action="update" disabled="${disabled}" value="${message(code: 'default.button.update.label', default: 'Update')}"/>
</div>
</div>
</g:form>
The problem
When a user causes a validation error that triggers a server side response, I run into the following problems with the different return types.
redirect - this is returning the flash message, but redirects the gsp to the edit action which in turn fires the initializer query and replaces all the form data with the original data.
Example -
redirect (controller:"recoveryDetail", action:"edit", params:["division":params.pk_division, "peid":params.pk_peid, "orgkey": params.pk_orgkey, "expenseDate":params.expenseDate.format("MM-dd-yyyy"), "adjustmentNumber":params.adjustmentNumber])
respond - So I assumed I needed to just use respond, well it resulted in the following.
URL was changed to http://localhost:8080/pisr/recoveryDetail/update dropping all parameters and a 404 page was returned.
Example
flash.message = "test"
respond view: "edit", model:[recoverDetailInstance:recoverDetailInstance]
return
So my question
How do I throw a server side validation error and return it to the page with the data entered by the user?
You may add the parameters back into the redirect or render call.
redirect (..., params: params)
https://grails.github.io/grails-doc/latest/ref/Controllers/redirect.html
Also, I'd suggest using a service instead of the controller method. Services are already transactional. I would also throw an exception if the hasErrors is true. The exception message or object can be your payload back to the user.
Solution was to use render without the use of params like so.
if(recoveryDetailInstance.hasErrors()) {
render view: "edit", model:[recoveryDetailInstance:recoveryDetailInstance]
return
}

AJAX Response Rails 4 Does Not Return In Text Field

I've just started coding in Rails 4, so I may need very specific instruction. I'm using AJAX to take user input from a text field, modify it (for simplicity's sake I'm just adding " yay!" on the end of it), and return the response as a string to the text field.
I believe I am sending my AJAX request correctly, but something in my controller or view isn't modifying or displaying the result.
Here is my code:
View (solve.html.erb):
<head>
<script>
function myFunction() {
var xmlhttp = new XMLHttpRequest();
var url = "solve";
var params = "field1=" + document.getElementById("field1").value;
xmlhttp.onreadystatechange=function() {
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
document.getElementById("field1").innerHTML=xmlhttp.responseText;
}
}
xmlhttp.open("GET", url + "?" + params, true);
xmlhttp.send();
}
</script>
</head>
<h1>Solve!</h1>
<p>Type it in, type it in.</p>
<div class="col-lg-6">
<div class="input-group">
<input id="field1" type="text" class="form-control" placeholder="1+1">
<span class="input-group-btn">
<button class="btn btn-default" type="button" onclick="myFunction()">Solve</button>
</span>
</div>
</div>
Routes (routes.rb):
resources :equations
#rest of my routes code
get 'solve' => 'equations#solve'
Controller (equations_controller.rb):
class EquationsController < ApplicationController
def solve
if request.post?
new_var = params[:field1]
other_var = "#{new_var} yay!"
return other_var
end
end
end
If you have any questions, I'll get right back to you. Thank you so much for your help! Anything is greatly appreciated.
You're making a GET request but checking if the request is POST in your EquationsController#solve method. Update controller code as follows:
class EquationsController < ApplicationController
def solve
if request.get? # Change to `request.get?`
new_var = params[:field1]
other_var = "#{new_var} yay!"
return other_var
end
end
end
Then the second problem is, you should be writing the output instead of returning it from the controller. As you are expecting text/plain mime type, you should render text instead:
class EquationsController < ApplicationController
def solve
if request.get?
new_var = params[:field1]
other_var = "#{new_var} yay!"
render plain: other_var # Change to `render plain: other_var`
end
end
end
This however, is not the recommended way to handle AJAX requests/responses in Rails. Please refer to: Working with JavaScript in Rails. Also, please see The response object for details on the same.
Try this in myFunction()
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
document.getElementById("field1").value = xmlhttp.responseText;
}
I think this works
ok, I think the controller's action is rendering the default 'solve.html.erb' view. so you should return just the value you wish like
class EquationsController < ApplicationController
def solve
if request.post?
new_var = params[:field1]
other_var = "#{new_var} yay!"
render html: other_var
end
end
end
and in js
if (xmlhttp.readyState==4 && xmlhttp.status==200) {
document.getElementById("field1").value = xmlhttp.responseText;
}

Resources