I am trying to create an instant search engine in rails.
My searchable model is tournament
I added the gem 'pg_search'to my gemfile
In my tournament model I have :
class Tournament < ActiveRecord::Base
include PgSearch
pg_search_scope :search,
:against => [:name, :address, :city, :club_organisateur, :starts_on, :ends_on, :postcode],
:using => {:tsearch => {:prefix => true} }
The search takes place on my Tournament#index view, so in my tournaments controller I have :
def index
#tournaments = policy_scope(Tournament)
if #tournaments.blank? && current_user.judge?
render 'pages/partials/_no_tournaments_judge'
elsif #tournaments.blank?
render 'pages/partials/_no_tournaments'
elsif params[:content].blank?
elsif params[:content]
#tournaments = #tournaments.search(params[:content])
respond_to do |format|
format.js {}
format.html{ render :index }
end
end
end
In my view (slim) tournament/index I have :
div class="container"
div class="row"
div class="col-xs-12"
h1 class="emperor" Recherche de tournois
= form_tag tournaments_path, method: "get", :remote => true do ||
div class="form-group"
= label_tag :content, "recherche"
= text_field_tag :content, nil, class: "form-control"
= render "tournaments"
Here is the tournaments' partial :
div class="row" id="tournament_row"
- #tournaments.each do |tournament|
div class="col-md-4"
div class="panel panel-default"
div class="panel-heading"
h2
= tournament.name
br
small = tournament.city.upcase
div class="panel-body"
p
'Du
strong> = tournament.starts_on.strftime("%d/%m/%Y")
'au
strong = tournament.ends_on.strftime("%d/%m/%Y")
ul
- tournament.competitions.each do |competition|
li
= "#{competition.category} #{competition.genre.text}"
div class="panel-footer"
= link_to 'En savoir plus', tournament_path(tournament), class: 'btn btn-primary'
and Here is my index.js.erb file :
$('#tournament_row').html("<%= render 'tournaments' %>");
When I check my logs everytime I submit the form, params[:content] is sent to my tournament#index action and I can see that
#tournaments = #tournaments.search(params[:content]) is working.
My problem is that my tournament/index view is never updating to display the tournaments contained in #tournaments... It always stay the same. What is the problem ?
Try to add escape_javascript
$('#tournament_row').html("<%= escape_javascript(render 'tournaments') %>");
or
$('#tournament_row').html('<%=j render 'tournaments' %>')
Related
In Rails 5 app with devise, I need to use a new.js.erb file to update select tag in my registrations view and controller. I cant seem to figure out why my new.js.erb file isn't working.
I've tried to use respond_to in controller as below,
registrations-controller.rb
def new
super
#cities = CS.get(:us,params[:state])
respond_to do |format|
format.js { render '/new.js.erb' }# layout: false }
format.html
end
end
new.html.erb
<%= form_for(resource, as: resource_name, url: registration_path(resource_name), :remote => true) do |f| %>
<div class="signup-input-container">
<div class="field">
<%= f.text_field :firstname, autofocus: true, autocomplete: "firstname", placeholder: "First name", class: "signup-input-container--input" %>
</div>
<div class="field">
<%= f.select :state, options_for_select(CS.states(:us).map { |code, name| [name, code] }),{:prompt => "State"}, {:class => "signup-input-container--input", :id => "state-picker"} %>
</div>
<div class="field">
<%= f.select :city, options_for_select([]),{}, {:class => "signup-input-container--input", :id => "city-picker"} %>
</div>
</div>
<% end %>
new.js.erb
var city = document.getElementById("city-picker");
while (city.firstChild) city.removeChild(city.firstChild);
var placeholder = document.createElement("option");
placeholder.text = "Choose a city";
placeholder.value = "";
city.appendChild(placeholder);
<% #cities.each do |c| %>
city.options[city.options.length] = new Option('<%= c %>');
<% end %>
main.js
var state = document.getElementById("state-picker");
state.addEventListener("change", function() {
$.ajax({
url: "/states?state=" + state.value,
type: "GET"
})
})
I'm expecting this to create select tag options with my array of cities in my controller. Does anyone know how to get this to work?
To solve this you should just setup a separate controller where you can fetch the data from asynchronously and alternatively there are also several free API's which can be used for geographical lookup such as Googles Geocoding API and Geonames.
To setup a separate controller you can do it by:
# /config/routes.rb
get '/states/:state_id/cities', to: 'cities#index'
# /app/controllers/cities_controller.rb
class CitiesController < ApplicationController
# GET
def index
#cities = CS.get(:us, params[:state_id])
respond_to do |f|
f.json { render json: #cities }
end
end
end
I would skip using a .js.erb template altogether and just return JSON data which you can use directly in your JS or with one of the many existing autocomplete solutions. .js.erb only makes sense for extensive HTML templating (like for example rendering an entire form) where you want to reuse your server side templates - it greatly increases the complexity and generally makes a mess of your javascript which is not worth it just to output a list of option tags.
// If you are using jQuery you might as well setup a delegated
// handler that works with turbolinks,
$(document).on('change', '#state-picker', function(){
$.getJSON("/states/" + $(this).value() + "/cities", function(data){
// using a fragment avoids updating the DOM for every iteration.
var $frag = $('<select>');
$.each(data, function(city){
$frag.append$('<option>' + data + '</option>');
});
$('#city-picker').empty()
.append($('frag').children('option'));
});
});
Think of the below as a bike rental. Someone fills out a form and gets a bike assigned to them which they can rent and borrow for a certain amount of time.
The problem I am having is I am trying to show the person who wants to rent the bikes what bikes are available before they submit the form. Below is my attempt using ajax. I have no errors but also my select is not updating.
request controller methods below
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
create method below (with a temporary workaround, that reloads the form with a warning about availability.)
def create
#request = Request.new(request_params)
available_bikes = #request.new_request(current_user.id)
if (available_bikes >= #request.number_of_bikes_wanted) && #request.save
redirect_to root_path
else
flash[:warning] = "You have requested more bikes than available. There are only #{available_bikes} bikes available"
redirect_to new_request_url
end
end
params in request controller
def request_params
params.require(:request).permit(:Borrow_time, :Borrow_date,
:Return_date, :Return_time,
:number_of_bikes_wanted, bike_ids: [])
end
new.html.erb view
<div class="form" align = "center">
<%= render 'form.js.erb' %>
</div>
_form.js.erb below
<script type="text/javascript">
$(document).ready(function() {
$('.my-date').on('change', function() {
var data = {}
$('.my-date').each(function() {
if($(this).val()) {
data[$(this).attr("id")] = $(this).val();
}
});
if(Object.keys(data).length > 1) {
$.ajax({
type: "POST",
url: <%= new_request_path %>,
data: data
});
}
});
});
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
</script>
<div class="block-it" align=center>
<br>
<%= form_for #new_request do |request| %>
<%= request.label :Borrow_date, 'Borrow on' %>
<%= request.date_field :Borrow_date, id: 'Borrow_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Borrow_time, 'Borrow at' %>
<%= request.time_field :Borrow_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<%= request.label :Return_date, 'Return On' %>
<%= request.date_field :Return_date, id: 'Return_date', class: 'my-date', min: Date.today, :required => true %>
<%= request.label :Return_time, 'Return at' %>
<%= request.time_field :Return_time, value: '10:00', min: '9:00 AM', max: '4:30 PM', default: '10:00 AM', :ignore_date => true, :required => true %>
<br><br>
<br><br>
<%= request.label :NumberOfBikesWanted, 'Number of bikes' %>
<%= request.select :number_of_bikes_wanted, %w(select_bike), :required => true %>
<br>
<%= request.submit 'Submit' %>
<%= request.submit 'Reset', :type => 'reset' %>
<% end %>
<br>
</div>
There are a two main problems with your code:
Controller
Use a different action to set the endpoint that you will call with ajax, so instead of this:
def new
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
#new_request = Request.new
end
Try this:
def bikes
#bikes = Bike.available_based_on_request_date(params[:Borrow_date], params[:Return_date])
def new
#new_request = Request.new
end
If you want to keep REST routes, then create a new controller and use the index action within that controller.
Form
This code:
var options = "";
<% #bikes.each do |bike| %>
options += "<option value='<%= bike.id %>'><%= bike.name %></option>"
<% end %>
$('#request_number_of_bikes_wanted').html(options);
doesn't belong here, it must be deleted from your file and instead put it on a new file called bikes.js.erb; also rename your form to _form.html.erb.
And update your ajax call to use your new route:
$.ajax({
type: "POST",
url: <%= bikes_path %>,
data: data
});
What you want to setup is a new endpoint but instead of returning html, it will return a js. But you must treat it as an independent action, just as any other action in rails. The only difference is how you call that action (ajax) and how you respond to it (js).
Been struggling with this query for a few days. I have 3 models Books, Children and Hires. I have created a view for hires which allows a user to select 2 books and a single child and what i'm looking to do is insert two rows to reflect this into the 'hires' table. I have some JS that populates the hidden fields with the values that they require. Now, I don't think nested attributes is the way to go, because i'm trying to insert directly into the joining table.
So, what i'm trying now is the following:
hires/_form.html.erb
<%= form_for(#hire) do |f| %>
<% 2.times do %>
<%= f.hidden_field :child_id %>
<%= f.hidden_field :book_id %>
<% end %>
<%= f.submit 'Take me home' %>
<% end %>
and then what I want to do is to run through the 'create' function twice in my controller and thus create two rows in the 'hires' model. Something like this:
hires_controller.rb
def create
hire_params.each do |hire_params|
#hire = Hire.new(hire_params)
end
end
Is this completely the wrong way to go? I'm looking for advice on the right way to do this? If this will work, what's the best way to format the create statement?
** Edit **
I have 3 models. Each Child can have 2 books. These are my associations:
class Child < ActiveRecord::Base
has_many :hires
has_many :books, through: :hires
end
class Hire < ActiveRecord::Base
belongs_to :book
belongs_to :child
accepts_nested_attributes_for :book
accepts_nested_attributes_for :child
end
class Book < ActiveRecord::Base
has_many :hires
has_many :children, through: :hires
belongs_to :genres
end
hires/new.html.erb
<div class="form-inline">
<div class="form-group">
<h1><label for="genre_genre_id">Pick Book 1:
<%=collection_select(:genre1, :genre_id, #genres.all, :id, :Name, {prompt: true}, {:class => "form-control dropdown"})%></label></h1>
</div>
</div>
<div id="book1-carousel" class="owl-carousel owl-theme">
<% #books.each do |book| %>
<div data-id = "<%= book.id %>" class="tapbook1 tiles <% #genres.each do |g|%> <% if g.id == book.Genre_id %> book1genre<%=g.id %> <% end end%> <%= %>"><a class="item link"><% if book.bookimg.exists? %><%= image_tag book.bookimg.url(:small), :class => "lazyOwl", :data => { :src => book.bookimg.url(:small)}%> <%end%></br><p class="tile_title" ><%= book.Title %></p></a></div>
<% end %>
</div>
<div class="form-inline">
<div class="form-group">
<h1><label for="genre_genre_id">Pick Book 2:
<%=collection_select(:genre2, :genre_id, #genres.all, :Name, :Name, {prompt: true}, {:class => "form-control dropdown"})%></label></h1>
</div>
</div>
<div id="book2-carousel" class="owl-carousel owl-theme">
<% #books.each do |book| %>
<div data-id = "<%= book.id %>" id="<%= book.id %>" class="tapbook2 tiles <% #genres.each do |g|%> <% if g.id == book.Genre_id %> book2genre<%=g.id %> <% end end%> <%= %>"><a class="item link"><% if book.bookimg.exists? %><%= image_tag book.bookimg.url(:small) , :class => "lazyOwl", :data => { :src => book.bookimg.url(:small)}%> <%end%></br> <p class="tile_title"><%= book.Title %></p></a></div>
<% end %>
</div>
<h1 class="child_heading1" >Now choose your name:</h1>
<div id="children-carousel" class="owl-carousel owl-theme">
<% #children.each do |child| %>
<div data-id = "<%= child.id %>" class="tapchild tiles"><a class="item link"><% if child.childimg.exists? %><%= image_tag child.childimg.url(:small), :class => "lazyOwl", :data => { :src => child.childimg.url(:small)} %> <%end%></br> <p class="tile_title"><%= child.nickname %></p></a></div>
<% end %>
</div>
<%= render 'form' %>
and the coffeescript:
hires.coffee
$(document).on 'ready page:load', ->
book1carousel = $("#book1-carousel")
book2carousel = $('#book2-carousel')
book1carousel.owlCarousel items: 5, lazyLoad : true
book2carousel .owlCarousel items: 5, lazyLoad : true
$('#children-carousel').owlCarousel items: 5, lazyLoad : true
book1clickcounter = 0
book2clickcounter = 0
childclickcounter = 0
book1selection = 0
book2selection = 0
$('.tapbook1').on 'click', (event) ->
$this = $(this)
book1id = $this.data('id')
book1selection = book1id
if $this.hasClass('bookclicked')
$this.removeAttr('style').removeClass 'bookclicked'
book1clickcounter = 0
$('#hire_book_id').val("");
book1selection = 0
else if book1clickcounter == 1
alert 'Choose one book from this row'
else if book1selection == book2selection
alert "You've already picked this book"
else
$('#hire_book_id').val(book1id);
$this.css('border-color', 'blue').addClass 'bookclicked'
book1clickcounter = 1
return
$('.tapbook2').on 'click', (event) ->
$this = $(this)
book2id = $this.data('id')
book2selection = book2id
if $this.hasClass('book2clicked')
$this.removeAttr('style').removeClass 'book2clicked'
book2clickcounter = 0
book1selection = 0
else if book2clickcounter == 1
alert 'Choose one book from this row'
else if book1selection == book2selection
alert "You've already picked this book"
else
$this.css('border-color', 'blue').addClass 'book2clicked'
book2clickcounter = 1
return
$('.tapchild').on 'click', (event) ->
$this = $(this)
childid = $this.data('id')
if $this.hasClass('childclicked')
$this.removeAttr('style').removeClass 'childclicked'
childclickcounter = 0
$('#hire_child_id').val("");
else if childclickcounter == 1
alert 'Choose one child from this row'
else
$this.css('border-color', 'blue').addClass 'childclicked'
childclickcounter = 1
$('#hire_child_id').val(childid);
return
jQuery ($) ->
$('td[data-link]').click ->
window.location = #dataset.link
return
return
return
My approach to this would be what's called a form object, a class that acts like a model but exists only to handle the creation of multiple objects. It provides granular control, but at the expense of duplicating validations. In my opinion (and that of many others), it's a much better option than nested attributes in most cases.
Here's an example. Note that I don't have any idea what your application does, and I didn't look at your associations close enough to make them accurate in this example. Hopefully you'll get the general idea:
class HireWithBookAndChild
include ActiveModel::Model
attr_accessor :child_1_id, :child_2_id, :book_id
validates :child_1_id, presence: true
validates :child_2_id, presence: true
validates :book_id, presence: true
def save
if valid?
#hire = Hire.new(hire_params)
#child_1 = #hire.child.create(id: params[:child_1_id])
#child_2 = #hire.child.create(id: params[:child_2_id])
#book = #hire.book.create(id: params[:book_id])
end
end
end
By including AR::Model, you get validations and an object you can create a form with. You can even go into your i18n file and configure the validation errors messages for this object. Like an ActiveRecord model, the save method is automatically wrapped in a transaction so you won't have orphaned objects if one of them fails to persist.
Your controller will look like this:
class HireWithBookAndChildController < ApplicationController
def new
#hire = HireWithBookAndChild.new
end
def create
#hire = HireWithBookAndChild.new(form_params)
if #hire.save
flash['success'] = "Yay"
redirect_to somewhere
else
render 'new'
end
end
private
def form_params
params.require(:hire_with_book_and_child).permit(:child_1_id, :child_2_id, :book_id)
end
end
Your form will look like this:
form_for #hire do |f|
f.hidden_field :child_1_id
...
f.submit
end
You'll notice right away that everything is flat, and you aren't having to mess with fields_for and nested nested parameters like this:
params[:child][:id]
You'll find that form objects make your code much easier to understand. If you have different combinations of children, books and hires that you need to create, just make a custom form object for each one.
Update
A solution that might be more simple in this case is to extract a service object:
class TwoHiresWithChildAndBook < Struct.new(:hire_params)
def generate
2.times do
Hire.create!(hire_params)
end
end
end
And from your controller:
class HiresController
def create
generator = HireWitHChildAndBook.new(hire_params)
if generator.generate
*some behavior*
else
render :new
end
end
end
This encapulates the knowledge of how to create a hire in one place. There's more detail in my answer here: Rails 4 Create Associated Object on Save
How can I use simple_form to filter a field, based on a previous fields value?
For instance, I have an Opportunities form, with two fields, Company and Contact.
Company Field:
<div class="form-group">
<%= f.association :company, collection: Company.all.order(:account), prompt: "", :label_method => :account, :value_method => :id %>
</div>
Contact Field:
<div class="form-group">
<%= f.association :contact, collection: Contact.all.order(:first_name), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
Here is what I want to do: If I select a company called "Deviant" from the Company field above, I want the Contact field to only display those contacts associated with the company called "Deviant".
I am trying something like this, but can't get it to work:
<div class="form-group">
<%= f.association :contact, collection: Contact.where("company_id = ?", params[:id]), prompt: "", :label_method => lambda { |contact| "#{contact.first_name} #{contact.last_name}" }, :value_method => :id %>
</div>
I don't know how to reference the value in the Company field.
How can I do this?
Thanks.
Update
Anyone? Surely this must be possible. This is a key functionality in any form. I would hope I don't need jQuery or something.
I think the best approach is to use ajax requests to update your contacts collection dinamically whenever the company's selected value is changed.
First you'll need an action in your contacts controller:
app/controllers/contacts_controller.rb
class ContactsController < ApplicationController
def contacts_list
if params[:company_id]
#contacts = Contact.where(company_id: params[:company_id])
else
#contacts = Contact.all
end
respond_with(#contacts) do |format|
format.json { render :json => #contacts.to_json(:only => [:id, :first_name, :last_name]) }
end
end
end
Add this to your routes:
config/routes.rb
post 'contacts_list' => "contacts#contacts_list", as: :contacts_list
Then use the coffeescript code bellow to populate your contacts' collection:
app/assets/javasctipts/companies.js.coffee
$(document).ready ->
if $("#opportunity_company_id")
populate_contacts()
$("#opportunity_company_id").change ->
populate_contacts()
populate_contacts = ->
$contacts_select = $("select#opportunity_contact_id")
$contacts_select.attr "disabled", "disabled"
company_id = $("select#opportunity_company_id").val()
if company_id is ""
$contacts_select.html "<option value=\"\">(select the company first)</option>"
else
$contacts_select.html "<option value=\"\">(loading contacts...)</option>"
data = {company_id: company_id}
data[window._auth_token_name] = window._auth_token
$.ajax "/contacts_list",
type: "post"
dataType: "json"
data: data
success: (contacts) ->
_html = '<option value="">Select the contact:</option>'
_html += '<option value="'+contact.id+'">'+contact.first_name + ' ' + contact.last_name + '</option>' for contact in contacts
$contacts_select.html _html
$contacts_select.removeAttr "disabled"
error: ->
alert 'Error trying to load contacts.'
Finally, inside your html's head tag:
<% if protect_against_forgery? %>
<script>
window._auth_token_name = "<%= request_forgery_protection_token %>";
window._auth_token = "<%= form_authenticity_token %>";
</script>
<% end %>
Hope it helps...
update:
Add the following line to your ApplicationController (app/controllers/application_controller.rb):
respond_to :html, :xml, :json, :js
I have a view which contain multiple links:
<% a.each do |q| %>
<%= link_to "stock it",
{ :action => "stock",
:qid => q.question_id,
:qur => q.question_answers_url,
:qti => q.title } ,
:remote => true %>
<div id="<%= "stock" + q.question_id.to_s %>"></div>
<% end %>
Each link generate AJAX-request. Here is a controller:
def stock
if(!Later.where(:question_id => params[:qid]).exists?)
later = Later.new(:question_id => params[:qid], :name => params[:qti], :url => params[:qur])
later.save
end
respond_to do |format|
format.js { render :layout=>false }
end
end
Now return to the view. Each link has a 'div' with unique id='stock'. When user press the link I need to add text to specific div with corresponding id.
I have a stock.js.erb file:
$("#stock<number>").html("some text");
How can I pass div-id to stock.js.erb and how can I use it ?
Common use is to add object.id to your DOM id. That what you exactly did:
<div id="<%= "stock_#{q.question_id}" %>"></div>
Then in your controller you shoud define your question_id or your exact question:
def stock
if(!Later.where(:question_id => params[:qid]).exists?)
later = Later.new(:question_id => params[:qid], :name => params[:qti], :url => params[:qur])
later.save
end
#question_id = params[:qid]
end
Now it will be shared with your stock.js.erb file:
$("#stock_<%= #question_id %>").html("some text");