Dynamic Select with ancestry - ruby-on-rails

My application using ancestry gem.
class Location < ActiveRecord::Base
has_ancestry :cache_depth => true
has_many :posts
end
class User < ActiveRecord::Base
belongs_to :location
end
I created some random Location,
Alaska
California
Los Angeles
Fresno
Cincotta (Fresno)
Hammond (Fresno)
Melvin (Fresno)
My question if user sign up form if User select California, display child Los Angles and Fresno, after select Fresno then display it's child.
I got javascript tutorial for Dropdown list http://www.plus2net.com/javascript_tutorial/dropdown-list-demo.php
How is possible work with ancestry gem?

Nested
Firstly, if you wanted to keep them all in a single dropdown, we created the following helper which achieves it for you:
#app/helpers/application_helper.rb
def nested_dropdown(items)
result = []
items.map do |item, sub_items|
result << [('- ' * item.depth) + item.name, item.id]
result += nested_dropdown(sub_items) unless sub_items.blank?
end
result
end
This will allow you to call:
<%= f.select(:category_ids, nested_dropdown(Category.all.arrange), prompt: "Category", selected: #category ) %>
This will give you the ability to call a single dropdown, which has been nested according to your ancestry associations
--
Ajax
If you want to have double dropdown boxes, you'll probably have to implement an ajax function to pull the required data each time the initial dropdown changes:
#config/routes.rb
resources :categories do
get :select_item
end
#app/assets/javascripts/application.js
$("#first_dropdown").on("change", function(){
$.ajax({
url: "categories/"+ $(this).val() + "/select_item",
dataType: "json",
success: function(data) {
//populate second dropdown
}
})
});
#app/controllers/categories_controller.rb
Class CategoriesController < ApplicationController
respond_to :json, only: :select_item
def select_item
category = #category.find params[:category_id]
respond_with category.children
end
end

I couldn't understand your question, but try to follow this tutorial:
http://railscasts.com/episodes/262-trees-with-ancestry
I watched it once when needed to work with ancestry gem.
Hope it can help you.

Related

Rails 5 ActiveRecord, how to include fields from associated tables?

With Rails 5, Given the models:
chef_positions
* id
* name
skills
* id
* name
chef_position_skills
* id
* chef_position_id
* skill_id
I have a controller method to return the chef_position_skills by chef_position_id:
def index
chef_position = ChefPosition.find(params[:chef_position_id])
render json: chef_position.chef_position_skills
end
This returns:
[{"id":1,"chef_position_id":2,"skill_id":1,"created_at":"2017-06-05T15:44:06.821Z","updated_at":"2017-06-05T15:44:06.821Z"},{"id":2,"chef_position_id":2,"skill_id":2,"created_at":"2017-06-05T15:44:06.821Z","updated_at":"2017-06-05T15:44:06.821Z"}]
How can I get the controller to do the following:
include skill.name for each record
Do not include the timestamps
you need to associate the two first if you haven't already, in your model, chef_positions.rb
has_many :skills, through: :chef_position_skills
Then in your controller,
ChefPosition.where(id: params[:chef_position_id]).joins(:skills).select('chef_positions.id, skills.name')
def index
chef_position = ChefPosition.find(params[:chef_position_id])
render json: chef_position.chef_position_skills.map(&:json_data)
end
# ChefPositionSkill#json_data
def json_data
to_json(
include: { skill: { only: [:name] } },
only: [:id, :chef_position_id, :skill_id]
)
end
Define a method json_data (just for convenience), and use .map to call it for each chef_position_skill.
The include and only are standard json serializer methods, which assist rails in what needs to be included.
The only drawback (as far as I see), is that now you will have another attribute "skill": { "name": "skill_name" } in your final json.
Use
render json: chef_position.chef_position_skills.
map {|s| s.slice(:id, :chef_position_id, :skill_id).merge s.skill.slice(:name) }
I don't have the same models but here is a similar example:
irb(main):026:0> u.slice(:id, :email).merge u.funds.slice(:min)
=> {"id"=>1, "email"=>"test#example.com", "min"=>1000000}
But I think you'll really like JBuilder which is included in Rails 5.
https://github.com/rails/jbuilder

hide a row in activeadmin

I'm using activeadmin with Ruby 2.2.1 and Rails 4.2.0 for a trouble ticketing system. I need to hide/archive the closed tickets, but I don't know how...
index do
selectable_column
column :ticket
column :in_carico
column :inoltrato
column :categoria
column :oggetto do |n|
truncate(n.oggetto, omision: "...", length: 50)
end
column :note do |n|
truncate(n.note, omision: "...", length: 30)
end
column :created_at
column :stato
actions defaults: true do |a|
link_to 'Infoweb', "http://XXX/main/ticket_dettagli.asp?TT="+a.ticket , :target => "_blank"
end
end
In :stato I can choose 3 voices: working, suspended and closed.
Example using Post model.
You can register a model in AA for archived posts (/admin/archived_posts.rb):
ActiveAdmin.register Post, as: "Archived Posts" do
end
Then in Post model define a scope, returning only post, where, for example, status attribute is archived:
scope :archived, -> { where(status: 'archived') }
Then in already registered model in AA you use this scope in scoped_collection method:
ActiveAdmin.register Post, as: "Archived Posts" do
# ...
controller do
def scoped_collection
Post.archived
end
end
# ...
end
This is it, you have all the archived posts in this new tab of AA.
Of course, now, to not have posts, where status is archived in regular Post tab in AA (/admin/posts.rb), add new scope to Post model (/models/post.rb):
scope :not_archived, -> { where.not(status: 'archived') } # or something like this
and use it in scoped_collection method in /admin/posts.rb

Ruby on Rails search with multiple parameters

For example in my Car model i have such fields:
color, price, year
and in form partial i generate form with all this fields. But how to code such logic:
user could enter color and year and i must find with this conditions, user could enter just year or all fields in same time...
And how to write where condition? I could write something like:
if params[:color].present?
car = Car.where(color: params[:color])
end
if params[:color].present? && params[:year].present?
car = Car.where(color: params[:color], year: params[:year])
end
and so over....
But this is very ugly solution, i'm new to rails, and want to know: how is better to solve my problem?
Check out the has_scope gem: https://github.com/plataformatec/has_scope
It really simplifies a lot of this:
class Graduation < ActiveRecord::Base
scope :featured, -> { where(:featured => true) }
scope :by_degree, -> degree { where(:degree => degree) }
scope :by_period, -> started_at, ended_at { where("started_at = ? AND ended_at = ?", started_at, ended_at) }
end
class GraduationsController < ApplicationController
has_scope :featured, :type => :boolean
has_scope :by_degree
has_scope :by_period, :using => [:started_at, :ended_at], :type => :hash
def index
#graduations = apply_scopes(Graduation).all
end
end
Thats it from the controller side
I would turn those into scopes on your Car model:
scope :by_color, lambda { |color| where(:color => color)}
scope :by_year, lambda { |year| where(:year => year)}
and in your controller you would just conditionally chain them like this:
def index
#cars = Car.all
#cars = #cars.by_color(params[:color]) if params[:color].present?
#cars = #cars.by_year(params[:year]) if params[:year].present?
end
user_params = [:color, :year, :price]
cars = self
user_params.each do |p|
cars = cars.where(p: params[p]) if params[p].present?
end
The typical (naive, but simple) way I would do this is with a generic search method in my model, eg.
class Car < ActiveRecord::Base
# Just pass params directly in
def self.search(params)
# By default we return all cars
cars = all
if params[:color].present?
cars = cars.where(color: params[:color])
end
if params[:price1].present? && params[:price2].present?
cars = cars.where('price between ? and ?', params[:price1], params[:price2])
end
# insert more fields here
cars
end
end
You can easily keep chaining wheres onto the query like this, and Rails will just AND them all together in the SQL. Then you can just call it with Car.search(params).
I think you could use params.permit
my_where_params = params.permit(:color, :price, :year).select {|k,v| v.present?}
car = Car.where(my_where_params)
EDIT: I think this only works in rails 4, not sure what version you're using.
EDIT #2 excerpt from site I linked to:
Using permit won't mind if the permitted attribute is missing
params = ActionController::Parameters.new(username: "john", password: "secret")
params.permit(:username, :password, :foobar)
# => { "username"=>"john", "password"=>"secret"}
as you can see, foobar isn't inside the new hash.
EDIT #3 added select block to where_params as it was pointed out in the comments that empty form fields would trigger an empty element to be created in the params hash.

Rails Select2 Case-insensitive AJAX Autocomplete

I'm working on implementing autocomplete utilizing Select2 to get an AJAX loaded list of Users from JSON in order to fill a multi-value select box.
So far I've been able to implement most of the desired functionality by referencing the following sources:
http://gistflow.com/posts/428-autocomplete-with-rails-and-select2
http://luksurious.me/?p=46
My problem is that, the autocomplete query is case sensitive. I need it to be case-insensitive. Through a bit of research, I came across a GitHub issue where the Select2 creator explains that "Ajax matching should be done on server side."
https://github.com/ivaynberg/select2/issues/884
After much trial and error and some extensive research, I've come up with no solutions to solve the case-sensitivity issue. Unfortunately, "matching on the server side" is a bit over my head, and I was wondering if somebody might be able to suggest a solution to this problem?
What follows is my work so far:
Haml
= hidden_field :recipient_id, "", data: { source: users_path }, class: "select2-autocomplete"
CoffeeScript
$ ->
$('.select2-autocomplete').each (i, e) ->
select = $(e)
options = {
multiple: true
}
options.ajax =
url: select.data('source')
dataType: 'json'
data: (term, page) ->
q: term
page: page
per: 5
results: (data, page) ->
results: data
options.dropdownCssClass = 'bigdrop'
select.select2 options
Users Controller
class UsersController < ApplicationController
def index
#users = User.order('name').finder(params[:q]).page(params[:page]).per(params[:per])
respond_to do |format|
format.html
format.json { render json: #users }
end
end
end
User Model
class User < ActiveRecord::Base
scope :finder, lambda { |q| where("name like :q", q: "%#{q}%") }
def as_json(options)
{ id: id, text: name }
end
end
Figured it out!
The answer lies in the custom scope and the LIKE clause in the query. LIKE is a case-sensitive clause. Since, I'm using PostgreSQL, I was able to change the LIKE clause to ILIKE which is case-insensitive.
So in order to get case-insensitive matching, the User Model should look like the following:
User Model
class User < ActiveRecord::Base
scope :finder, lambda { |q| where("name ILIKE :q", q: "%#{q}%") }
def as_json(options)
{ id: id, text: name }
end
end

Getting rails3-autocomplete-jquery gem to work nicely with Simple_Form with multiple inputs

So I am trying to implement multiple autocomplete using this gem and simple_form and am getting an error.
I tried this:
<%= f.input_field :neighborhood_id, collection: Neighborhood.order(:name), :url => autocomplete_neighborhood_name_searches_path, :as => :autocomplete, 'data-delimiter' => ',', :multiple => true, :class => "span8" %>
This is the error I get:
undefined method `to_i' for ["Alley Park, Madison"]:Array
In my params, it is sending this in neighborhood_id:
"search"=>{"neighborhood_id"=>["Alley Park, Madison"],
So it isn't even using the IDs for those values.
Does anyone have any ideas?
Edit 1:
In response to #jvnill's question, I am not explicitly doing anything with params[:search] in the controller. A search creates a new record, and is searching listings.
In my Searches Controller, create action, I am simply doing this:
#search = Search.create!(params[:search])
Then my search.rb (i.e. search model) has this:
def listings
#listings ||= find_listings
end
private
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline)
listings = listings.includes(:neighborhood).where("listings.headline like ? or neighborhoods.name like ?", key, key) if keywords.present?
listings = listings.where(neighborhood_id: neighborhood_id) if neighborhood_id.present?
#truncated for brevity
listings
end
First of all, this would be easier if the form is returning the ids instead of the name of the neighborhood. I haven't used the gem yet so I'm not familiar how it works. Reading on the readme says that it will return ids but i don't know why you're only getting names. I'm sure once you figure out how to return the ids, you'll be able to change the code below to suit that.
You need to create a join table between a neighborhood and a search. Let's call that search_neighborhoods.
rails g model search_neighborhood neighborhood_id:integer search_id:integer
# dont forget to add indexes in the migration
After that, you'd want to setup your models.
# search.rb
has_many :search_neighborhoods
has_many :neighborhoods, through: :search_neighborhoods
# search_neighborhood.rb
belongs_to :search
belongs_to :neighborhood
# neighborhood.rb
has_many :search_neighborhoods
has_many :searches, through: :search_neighborhoods
Now that we've setup the associations, we need to setup the setters and the attributes
# search.rb
attr_accessible :neighborhood_names
# this will return a list of neighborhood names which is usefull with prepopulating
def neighborhood_names
neighborhoods.map(&:name).join(',')
end
# we will use this to find the ids of the neighborhoods given their names
# this will be called when you call create!
def neighborhood_names=(names)
names.split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
# view
# you need to change your autocomplete to use the getter method
<%= f.input :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, input_html: { data: { delimiter: ',', multiple: true, class: "span8" } %>
last but not the least is to update find_listings
def find_listings
key = "%#{keywords}%"
listings = Listing.order(:headline).includes(:neighborhood)
if keywords.present?
listings = listings.where("listings.headline LIKE :key OR neighborhoods.name LIKE :key", { key: "#{keywords}")
end
if neighborhoods.exists?
listings = listings.where(neighborhood_id: neighborhood_ids)
end
listings
end
And that's it :)
UPDATE: using f.input_field
# view
<%= f.input_field :neighborhood_names, url: autocomplete_neighborhood_name_searches_path, as: :autocomplete, data: { delimiter: ',' }, multiple: true, class: "span8" %>
# model
# we need to put [0] because it returns an array with a single element containing
# the string of comma separated neighborhoods
def neighborhood_names=(names)
names[0].split(',').each do |name|
next if name.blank?
if neighborhood = Neighborhood.find_by_name(name)
search_neighborhoods.build neighborhood_id: neighborhood.id
end
end
end
Your problem is how you're collecting values from the neighborhood Model
Neighborhood.order(:name)
will return an array of names, you need to also collect the id, but just display the names
use collect and pass a block, I beleive this might owrk for you
Neighborhood.collect {|n| [n.name, n.id]}
Declare a scope on the Neighborhood class to order it by name if you like to get theat functionality back, as that behavior also belongs in the model anyhow.
edit>
To add a scope/class method to neighborhood model, you'd typically do soemthing like this
scope :desc, where("name DESC")
Than you can write something like:
Neighborhood.desc.all
which will return an array, thus allowing the .collect but there are other way to get those name and id attributes recognized by the select option.

Resources