Load model association dynamically in Rails form before submit - ruby-on-rails

So I'm not sure how to achieve this or if it's even possible and having a tough time searching for similar examples/answers. But is there a way to dynamically load a model association within a form before you submit it, like without knowing what the instance variable would be ahead of time?
I have models as follows:
Property
has_many :owners, inverse_of: :property
has_many :orders
Owners
belongs_to :property
Orders
belongs_to :property
has_many :owners, through: :property
In my orders.new form I have select boxes to choose a property to create the association for the new order (using Select2 plugin):
<%= bootstrap_form_for(#orders, ...) do |f| %>
...
<%= f.select :property_id, options_from_collection_for_select(Property.all, "id", "full_address"),
{ label: "Property:", include_blank: true }, { id: "orderPropSelect", data: { placeholder: "Select a Property"} } %>
...
<% end %>
So when someone SELECTS a property for the new order, is there a way I can load what owners that property already has associated to it before the form is submitted? Even just being able to see what owners are already there is okay (being able to edit them would be even better but I realize thats probably more complex)
The following code of course doesn't work but looking for something along the lines of:
<%= f.static_control label: "Property Owners" do %>
<% :property_id.owners.each do |owner| %>
<%= owner.name %>
<% end %>
<% end %>
I've tried variations of fields_for but I don't know how to tell the nested fields to be based off what is chosen in the above select (of course loading different owners based on what property is chosen. The errors that I get are undefined method owners for nil:NilClass which are appropriate because I know I'm not telling rails where to look correctly.
So is this possible and if so, how would I achieve this?
(I use bootstrap forms gem incase anyone is wondering about form syntax. I also have cocoon loaded for other forms so if there's a way to use that then I'm not opposed.)
Update Working code, slightly modified for extraneous conditions.
$("#orderPropSelect").off().on('change', function() {
var id = $(this).val();
console.log(id);
if (id !== '') {
$.ajax({
url: '/properties/' + id + '/owners',
dataType: "json",
success: function (data) {
owners_html = '';
$.each(data['owners'], function () {
owners_html += '<p>' + this + '</p>';
});
if (owners_html === '') {
$("#propOwnersShow").html('<p>No owner added yet.</p>');
} else {
$("#propOwnersShow").html($(owners_html));
}
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
} else {
$('#propOwnersShow').html('Please select a Property below to view its associated owners.')
}
});

You need to make sure it matches your underlying routes & stuff, and probably handle the case where there aren't owners, so to hide the owners div. If you want to do more complex stuff you could instead of .pluck build a better array/hash with id's that you can then also use to build elements that you can interact with (e.g. remove them from the list)
# your property controller
before_action :allowed, only [:owners]
def owners
owners = Property.find(params[:id]).owners.pluck(:name)
respond_to |format|
format.json { render json: { owners: owners, success: true } }
end
end
def allowed
# logic to define if this user is allowed to request this data, if you have devise, it could be
user_signed_in? && request.xhr?
end
# your routes.rb
get "properties/:id/owners", to: "properties#owners", as: "property_owners"
# or if you have resources
resources :properties do
member do
get :owners
end
end
# js file
$("#property_id").off().on('change', function() {
var id = $(this).val();
$.ajax({
url: '/properties/'+id+'/owners',
dataType: "json",
success: function(data) {
owners_html = '';
$.each(data['owners'], function() {
owners_html += '<p>'+this+'</p>';
});
$("selector_where_you_want_to_show_owners").html($(owners_html));
},
error: function(XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
}
UPDATE:
You can prevent the issue with no Owners by using find_by instead and making sure that you always return an [], this way simplifying the logic on the front-side as well.
# on your controller instead use:
def owners
# .find_by will return "nil" if no properties are found, which
# then we can leverage with .try. .try will "try" calling those
# chained methods without raising an error. It usually returns
# "nil" if the parent is "nil" but .try with .pluck though will
# always return an [] no matter what the ancestors returned.
owners = Property.find_by(id: params[:id]).try(:owners).try(:pluck, :name]
# actually looking at your relationships it seems you could instead
# .where, since that will return an empty array, like so:
# owners = Owner.where(property_id: params[:id]).pluck(:name)
# this only does one database query where the other one does 2
# here we're simply making sure we add something to the array so
# that then on the front-end you can always deal with an array
# without worrying with the "no properties found". .blank? will
# return "true" if the value is nil, empty string, empty hash or
# empty array which works fine for this. So if there's no value in
# the array we add the "no owners" string.
owners << 'No owner added yet.' if owners.blank?
respond_to |format|
format.json { render json: { owners: owners, success: true } }
end
end
# And then since you'll always be returning an array you no longer
# have to worry about an empty array and your ajax function will
# look like this:
$.ajax({
url: '/properties/' + id + '/owners',
dataType: "json",
success: function (data) {
owners_html = '';
$.each(data['owners'], function () {
owners_html += '<p>' + this + '</p>';
});
$("#propOwnersShow").html($(owners_html));
},
error: function (XMLHttpRequest, textStatus, errorThrown) {
console.log(errorThrown);
}
});
Hope this helps

Related

Rails, Select2 and Ransack return emprty array

I have a City Model:
class Geo::City < Smth
self.table_name = 'cities_table'
has_many :streets, class_name: "Geo::Street", primary_key: :geo_code, foreign_key: :geo_code
The aim is, to search all streets in the city and print them as a search result. In my controller I have a filter method as follows:
def filter
#city_list = city_list
params[:q] ||= {}
params[:q][:ags8_id_eq] = #city_list[0][1]
if params[:q].present? && params[:q][:geo_code_eq].present?
#city = Geo::City.find_by(geo_code: params[:q][:geo_code_eq])
#collection = [["#{#city}", "#{params[:q][:geo_code_eq]}"]]
end
#streets = #city.streets.limit(15)
#q = #streets.ransack(params[:q])
end
I also have a search_city method as follows:
def search_city
render json: city_list.select {|city| city.first.include?(params[:q][:geo_code_eq])}
end
And a private method city_list which I am calling in methods above:
def city_list
all_cities = Geo::City.order('city_name').reduce([]) {|cities, c| cities << [c.name, c.geo_code]}
I had to change all the method names,cause it's for work, so the_city list is kind of an example and it also works fine, I assume, this is not the problem. I also have a js-file for select 2.
(function() {
$(function() {
return $('.city-select2').select2({
ajax: {
url: "/filter/search_city",
dataType: 'json',
delay: 250,
data: function (params) {
return {
q: {
geo_code_eq: params.term
}
};
},
processResults: function(data) {
return {
results: $.map(data, function(obj) {
return {
id: obj[1],
text: obj[0]
};
})
};
},
cache: true
},
minimumInputLength: 2,
placeholder: "City",
tags: false,
tokenSeparators: [","],
selectOnClose: true,
allowClear: true
});
});
}).call(this);
It seems to be working, cause when I type city name it finds the city with the letters I typed. What happens after I submit select form is that I just get an empty array, so basically like this:
The url looks the following:
http://localhost:3000/filter/search_city?q%5Bgeo_code_eq%5D=16071003&commit=Search
My view does this:
= search_form_for #q, url: search_city_path do |f|
.columns.is-centered{style: 'padding-top: 100px;'}
.column.is-half
= f.select(:geo_code_eq, options_for_select(collection: #collection.present? ? #collection : [], input_html: { class: "city-select2 select-input" })
= f.submit "Search"
What is wrong? How do I get streets in the cities printed? There might be some spelling mistakes in the code, as I had to replace names, this won't be the reason for my empty array. Also, in the url there is only B selected, maybe that the reason? But it also finds the geo_code, so it should be capable of giving the streets back linked to geo_code, as geo_code is also the part of streets_table..
Main reason for the array was the path in the view. Should have been filter_path ans not the search_city_path, as it was the same path my select2 was getting data from, redirection there on submit made no sence.

How can I check if a username is taken, while typing?

Many popular websites have a username field that, usually, turns red or blue as a user types in a new character, based on whether or not the characters entered correspond to an already-existing user.
Say I have the following username field:
<%= f.text_field :username, id:"username" %>
How could that functionality be added to this field?
Instead of checking the username and making request on every key, you can use the blur method to check the user name once the user leaves the username field, or else you need it on every key you can use keyup itself,
Your Javascript,
$( "#username" ).keyup(function() { # you can also try, $( "#username" ).blur(function() {
$.ajax({
url: '<%= check_duplicate_username_path %>', # your own route
type: "GET",
data: { username: $('#username').val() }, // This goes to Controller in params hash, i.e. params[:username]
complete: function() {},
success: function(data, textStatus, xhr) {
// do something with your returned data //
if (data.available == false)
{
$('#username').addClass("error"); // style the class with your required css
}
},
error: function() {
alert("Your Ajax error! message")
}
});
});
The route can be taken as,
get '/check_duplicate_username' => 'users#check_duplicate_username', as: :check_duplicate_username
The controller action can be something like,
def check_duplicate_username
#user = User.where('username = ?',params[:username]).first
if #user.present?
render json: {:success => 0, :message => 'User exists', :user_available => true}
else
render json: {:success => 1, :message => 'User Does not exist', :user_available => false}
end
end
You have to fire ajax request on textbox event.
write ajax function and add new function to you user_controller with GET http method and return suitable response for check availabilty of your username.
Trigger an ajax request while writing on the text box. Like:
$( "#username" ).keyup(function() {
$.ajax({
type: "GET",
url: '<%= username_availability_path %>', # replace by your route
data: {name: $('#username').prop('value')}
});
});
Create a new route on your routes.rb file with type GET. In the method access the typed name using params[:name] and then check if exists or not. Then do whatever you want.

Should Backbone collection 'create' add new model to collection with id from server included?

In my Backbone view here, I want to be able to immediately add my model to my collection with an 'id' attribute included in the newly created model. In other words, I need to have access to this 'id' right away. With the id available, my removeCategory method can work. To "solve" that problem, I tried to add the line this.collection.fetch() directly below this.collection.add(model);, but it gave me very bad UI (the new <li> wouldn't even render, etc), plus it seems like very bad coding practice anyway.
I'm relatively new with Backbone, so I'm not sure if create (save/add) should automatically add the model to the collection with an id or not. If it should, then there must be something wrong with the JSON data I'm receiving from my Rails RESTful API. If create does NOT do that, then how can I instantly get access to this specific newly created model, with an id (other than by refreshing the browser -- ugh!)? I need the id for <li data-id={{id}}></li> in my Handlebars template as soon as I invoke the "addCategory" method.
Also (& this might be a related problem), when I do model.fetch();, I get back the model's entire collection, plus that newly-created server-side model without an id. Any help on this would be much appreciated.
window.CategoriesView = Backbone.View.extend({
index_template: HandlebarsTemplates['categories/index'],
events: {
'click .delete': 'removeCategory',
'click .category_add': 'addCategory'
},
initialize: function() {
_.bindAll(this, 'render');
this.collection.fetch();
this.listenTo(this.collection, 'all', this.render);
},
show: function() {
$("#categorymodal").modal();
},
addCategory: function(e) {
e.preventDefault();
// I wrote the next three lines here explicitly rather than using 'create'
var model = new Category({ name: $(this.el).find('.category_name').val() });
model.save();
this.collection.add(model);
this.show();
},
removeCategory: function(e) {
e.preventDefault();
var id = $(e.target).parents('li').data('id');
var model = this.collection.where({ id: id })[0];
this.collection.remove(model);
if (model) { model.destroy(); }
this.render();
},
render: function() {
$(this.el).html(this.index_template());
this.collection.models.forEach(function(cat) {
var li_template = HandlebarsTemplates['categories/show']
$(this.el).find('.categories').append(li_template({
id: cat.toJSON().id,
name: cat.toJSON().name
}));
}, this);
return this;
}
});
Here is my #create action in Rails....
def create
#category = Category.create(category_params)
render :nothing => true
end
I am using jbuilder to provide the JSON from the Rails API.
Here is my Backbone model and collection...
window.Category = Backbone.Model.extend({
urlRoot: "categories"
});
window.Categories = Backbone.Collection.extend({
model: Category,
url : function() {
return 'categories';
}
});
I figured out my problem. It was in my Rails controller, not with Backbone create. I changed my #create action from this...
def create
#category = Category.create(category_params)
render :nothing => true
end
...to this....
def create
#category = Category.create(category_params)
redirect_to category_path(#category)
end
...with my show action being this...
def show
#category = Category.find(params[:id])
end
...that provided me with http://localhost:3000/categories/752.json. I've gotta make sure my backend is providing what I need JSON-wise!

Handling a JS POST

I am working on a ruby on rails website that also has javascript.
I have this javascript code, that I made, that makes a POST to the website and completes the action that I want it to.
function create_group_item(group_id, items_id){
$.ajax({
type: "POST",
url: '/group_items/create/',
data: { "group_id": group_id, "items_id": items_id },
success: function(data, textStatus, jqXHR){
},
error: function (jqXHR, textStatus, errorThrown){
console.log(errorThrown);
}
});
}
This works and is fine.
The issue I have is calling that same POST function from another rails controller instead of with javascript. I want to create a group_item from inside the item controller.
I have a variable in my item controller: group_item_info = {"group_id" => group_id.to_i, "item_id" => item_id}
Which, when I print it reads out: {"group_id"=>15, "item_id"=>754} in some instances. So, I have no issue getting the values of the group and item.
QUESTION
How do I call the group_items create function from the items controller.
Attempts so far
code such as:
#group.items << #item
and
def create_group_item
redirect_to url_for(:controller => :group_items_controller, :action => :create)
end
then: create_group_item({group_id: group_id, items_id: item_id})
have only given me errors.
Found a solution:
group_item_info = {"group_id" => group_id.to_i, "items_id" => item_id}
#group_item = GroupItem.new group_item_info
if #stand_item.save
flash[:notice] = "#{#item.name} has been added to your group."
else
flash[:alert] = "Something went wrong adding this item to your group, try again later."
end
One main thing which I had wrong in the other code included items_id vs item_id

Rails mongoid solr grouping remote ajax data in Select2: Optgroup appearing multiple times when I search for a child

I'm using Select2 with Rails and Mongoid and I have Sunspot Solr on the server side to do the searching. What I'm trying is to filter the data from mongo by solr, get that data by ajax request and group the results according to parent-child hierarchy. What I can't figure out is when I search for a child and if there are multiple child results, the parent category is occuring multiple times. Please read below for more info:
Here is my data in Mongo DB
{ "_id" : ObjectId("5209eb465a721ae827c661de"), "title" : "Bina Kanalizasyon Tesisatı", "parent" : "Su Tesisatçılığı", "path" : "sutesitati/kanalizasyon" }
{ "_id" : ObjectId("5209eb465a721ae827c661df"), "title" : "Daire Temiz Su Tesisatı", "parent" : "Su Tesisatçılığı", "path" : "sutesisati/temizsu" }
{ "_id" : ObjectId("5209eb465a721ae827c661e0"), "title" : "Musluk Tamiri", "parent" : "Su Tesisatçılığı", "path" : "sutesitati/musluktamiri" }
Here is my Rails Model
class Category
include Mongoid::Document
include Sunspot::Mongoid2
searchable do
text :title
end
field :title, :as => :title_textp
field :parent
field :path
end
Here is my Rails Controller
class CategoriesController < ApplicationController
respond_to :html, :json
def list_styles
search = Category.search do
fulltext params[:q]
end
search = Category.search { keywords params[:q]; paginate :page => params[:page], :per_page => params[:page_limit] }
#results = search.results
#total_lines = search.total
#categories = #results
respond_with #categories
end
end
Here is my Haml View
= f.label :category
= f.hidden_field :category, class: 'input-block', placeholder: "Lütfen almak istediğiniz hizmet türünü seçiniz"
And Here is my Javascript that I'm trying to figure out
$('#itinerary_category').select2({
minimumInputLength: 3,
ajax: {
url: "/categories/list_styles",
dataType: 'json',
quietMillis: 100,
data: function (term, page) {
return {
q: term,
page_limit: 10,
page: page,
};
},
results: function (data) {
var results = [];
$.each(data, function(index, item){
results.push({
text: item.parent,
children: [{id: item._id, text:item.title}]
});
});
return {
results: results
};
}
}
});
When I run the server and make a search its working ok for group with one item
However When I make a search that displays multiple items the group name("Su Tesisatı") is displayed twice too.
I don't want the group name to be displayed twice and I can't figure out how to make the change. I want a list like
Su Tesisatçılığı
Daire Temiz Su Tesisatı
Bina Kanalizasyon Tesisatı
I'm not good at js and could not figure out how to solve it. Could anyone please help me? I think the example I've provided also adds up to discussion about Grouping remote data in select2 and clarly states how to use select2 with rails+mongoid+solr+ajax. If anyone could clear this then this will make select2 more powerful because there is almost no clear example on the web for making it work in grouping with ajax remote data.
Any help would be greatly appreciated.
in this code you are creating a parent item per child:
$.each(data, function(index, item){
results.push({
text: item.parent,
children: [{id: item._id, text:item.title}]
});
});
instead, you should group by parent:
var hashtable={};
var results=[];
$.each(data, function(index, item){
if (hashtable[item.parent]===undefined) {
hashtable[item.parent]={text:item.parent, children:[]};
results.push(hashtable[item.parent]);
}
hashtable[item.parent].children.push({id:item._id,text:item.title});
});

Resources