output formated json with rails 3 - ruby-on-rails

I use rails 3.0.3
A javascript auto complete needs data like this
{
query:'Li',
suggestions:['Liberia','Libyan Arab Jamahiriya','Liechtenstein','Lithuania'],
data:['LR','LY','LI','LT']
}
My action is
def autocomplete
#query = params[:query]
#customers = Customer.where('firstname like ?', "%#{#query}%")
render :partial => "customers/autocomplete.json"
end
My view is
{
query:'<%= #query %>',
suggestions: <%= raw #customers.map{|c| "#{c.firstname} #{c.lastname}" } %>,
data: <%= raw #customers.to_json %>
}
it returns
{
query:'e',
suggestions: ["customer 1", "customer 2"],
data: [1, 3]
}
it's not working because the data for suggestions/data should be between simple quote...
I cannot use the to_json method, because it'll returns all the content of my object.
Any suggestion?
cheers

Note: this is way out of date, Jbuilder is by far a better option.
There are two ways you can approach this. If you simply need a subset of the fields in an object, you can use :only or :except to exclude what you don't want.
#customer.to_json(:only => [:id, :name])
in your example it looks like you need to return json in a specific format, so simply serializing an array of results won't work. The easiest way to create a custom json response is with the Hash object:
render :json => {
:query => 'e',
:suggestions => #customers.collect(&:name),
:data => #customers.collect(&:id)
}
I've tried using partials to build json responses, but that doesn't work nearly as well as simply using Hash to do it.
Formatting the first and last names as a single string is something you are likely to do a lot in your views, I would recommend moving that to a function:
class Customer < ActiveRecord::Base
...
def name
"#{first_name} #{last_name}"
end
def name=(n)
first_name, last_name = n.split(' ', 2)
end
end
Just some convenience functions that makes your life a little easier, and your controllers/views cleaner.

If Adam's response won't work for you, this may do it (admittedly not the most elegant solution):
{
query:'<%= #query %>',
suggestions: [<%= raw #customers.map{|c| "'#{c.firstname} #{c.lastname}'" }.join(", ") %>],
data: [<%= raw #customers.map{|c| "'#{c.id}'" }.join(", ") %>]
}

I've seen something like this in a .erb:
<%= raw
{
:query => #query,
:suggestions => #customers.map{|c| "#{c.firstname} #{c.lastname}" },
:data => #customers
}.to_json
%>
If thinking of preparing data to be consumed by other programs as presentation logic, this might make sense to you.
FWIW I like it.

Related

How to convert a string to an array using Ruby on Rails

I have a text field that takes in a string value, like
"games,fun,sports"
My main goal here is to take the string and turn it into a Array like this:
[games, fun, sports]
in the filters attribute for the integrations object I have. Right now I have the beginning of a method that doesn't seem to work.
Here is my code:
View:
<%= form_for #integrations, url: url_for(:controller => :integrations, :action => :update, :id => #integrations.id) do |f| %>
<%= f.label :filters %>
<%= f.text_field :filters, class: "filter-autocomplete" %>
<%= f.submit "Save" %>
<% end %>
That is the text field that takes in the string.
Model:
def filters=(filters)
end
This is the place that I'd like to make the switch from string to array.
Controller:
def update
#integrations = current_account.integrations.find(params[:id])
if #integrations.update_attributes(update_params)
flash[:success] = "Filters added"
redirect_to account_integrations_path
else
render :filters
end
end
def filters
#integrations = current_account.integrations.find(params[:id])
end
private
def update_params
[:integration_webhook, :integration_pager_duty, :integration_slack].each do |model|
return params.require(model).permit(:filters) if params.has_key?(model)
end
end
So, recap: I have a integrations model that takes in a string of filters. I want a method that will break up the string into an element of filter attributes.
Here is the object that I'm trying to add the filters to:
Object:
id: "5729de33-befa-4f05-8033-b0acd5c4ee4b",
user_id: nil,
type: "Integration::Webhook",
settings: {"hook_url"=>"https://hooks.zapier.com/hooks/catch/1062282/4b0h0daa/"},
created_at: Mon, 29 Aug 2016 03:30:29 UTC +00:00,
owner_id: "59d4357f-3210-4ddc-9cb9-3c758fc1ef3a",
filters: "[\"Hey\", \"ohh\"]">
As you can see the filters is what I'm trying to modify. Instead of this in the object:
"[\"Hey\", \"ohh\"]"
I would like this:
[Hey, ohh]
It's not clear what you're after, but generally, when you have a string like:
"games,fun,sports"
you can use split(',') to break it on the commas and turn it into an array of strings:
"games,fun,sports".split(',') # => ["games", "fun", "sports"]
If you're receiving a JSON encoded array of strings, it'll look like:
'["games", "fun", "sports"]'
AKA:
'["games", "fun", "sports"]' # => "[\"games\", \"fun\", \"sports\"]"
which can be returned to a Ruby array of strings easily enough:
require 'json'
JSON['["games", "fun", "sports"]'] # => ["games", "fun", "sports"]
One option is to use JSON.
require 'json'
filters = "[\"Hey\", \"ohh\"]"
JSON.parse(filters)
returns:
["Hey","ohh"]
You need to remove extra characters and then split the string into array using the split pattern like this:
"[\"Hey\", \"ohh\"]".gsub(/(\[\"|\"\])/, '').split('", "')
Which returns:
["Hey", "ohh"]

render model to json and use encode64 on one of the model's columns

I am trying to send a model as json. The model has binary data in one of it's columns. For another model I have used
format.json {self.encode64(#resource_type.data).to_json}
with success, but in that case I only wanted the data column, and not the title etc. What can I do when I want contents from several columns, where only one column's content should be encoded with encode64?
In the following code, I don't know where to put the self.encode64 method.
format.json { render :json => #resource.to_json(:only => [:id, :title, :data])}
How can I do this?
You have a few options here.
You could add a data_base64 method to your model that returned the data in base-64 format and then use the :methods option to to_json in your controller:
#resource.to_json(:only => [ :id, :title ], :methods => :data_base64)
That would give you a data_base64 key in the JSON instead of data but that might not be a problem.
You could also use as_json to get a hash and fix the encoding in the controller:
json = #resource.as_json(:only => [ :id, :title, :data ])
json['resource']['data'] = self.encode64(json['resource']['data'])
render :json => json
You can use as_json in model to override this behaviour like this
def as_json(options={})
{ :name_of_resource => { :created_at => created_at, binary => encode64(self.data) } }
end
You need to specify how he should serialize whole model into json.
Cheers!

How to use jquery-Tokeninput and Acts-as-taggable-on

This is how you use autocomplete with jQuery Tokeninput and ActsAsTaggableOn.
In my situation i am using a nested form but it shouldnt matter. Everything below is code that works.
Code
Product Model:
attr_accessible :tag_list # i am using the regular :tag_list
acts_as_taggable_on :tags # Tagging products
Products Controller:
#1. Define the tags path
#2. Searches ActsAsTaggable::Tag Model look for :name in the created table.
#3. it finds the tags.json path and whats on my form.
#4. it is detecting the attribute which is :name for your tags.
def tags
#tags = ActsAsTaggableOn::Tag.where("tags.name LIKE ?", "%#{params[:q]}%")
respond_to do |format|
format.json { render :json => #tags.map{|t| {:id => t.name, :name => t.name }}}
end
end
Routes:
# It has to find the tags.json or in my case /products/tags.json
get "products/tags" => "products#tags", :as => :tags
Application.js:
$(function() {
$("#product_tags").tokenInput("/products/tags.json", {
prePopulate: $("#product_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
});
Form:
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tags.map(&:attributes).to_json %>
Issue 1(SOLVED)
Must have the line:
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name }}}
Note - You can use #tags.map here as well and you dont have to change the form either.
Below are the 2 issues on why you needed to do this:
I have the following Tag: {"id":1,"name":"Food"}. When I save a Product, tagged "Food", it should save as ID: 1 when it searches and finds the name "Food". Currently, it saves a new Tag with a new ID that references the "Food" ID, i.e. {"id":19,"name":"1"}. Instead, it should be finding the ID, showing the name, and doing a find_or_create_by so it doesn't create a new Tag.
Issue 2(SOLVED)
When I go to products/show to see the tags by doing <%= #product.tag_list %>. The name appears as "Tags: 1", when it really should be "Tags: Food".
How can I fix these issues?
You should define a route in your routes.rb which should handle products/tags path. You can define it like:
get "products/tags" => "products#tags", :as => :tags
Thus should give you a tags_path helper which should evaluate to /products/tags. This should get rid of the errors you mentioned in the question. Be sure to add this route before defining resources :product in your routes.rb
Now onto acts-as-taggable-on, I haven't used this gem, but you should look at method all_tag_counts documentation. Your ProductsController#tags method will need some changes on the following lines. I am not sure if its exactly what would be required, as I use Mongoid and can't test it out.
def tags
#tags = Product.all_tag_counts.(:conditions => ["#{ActsAsTaggableOn::Tag.table_name}.name LIKE ?", "%#{params[:q]}%"])
respond_to do |format|
format.json { render :json => #tags.collect{|t| {:id => t.name, :name => t.name } }
end
end
little add-on:
If you want to create the tags on the fly, you could do this in your controller:
def tags
query = params[:q]
if query[-1,1] == " "
query = query.gsub(" ", "")
Tag.find_or_create_by_name(query)
end
#Do the search in memory for better performance
#tags = ActsAsTaggableOn::Tag.all
#tags = #tags.select { |v| v.name =~ /#{query}/i }
respond_to do |format|
format.json{ render :json => #tags.map(&:attributes) }
end
end
This will create the tag, whenever the space bar is hit.
You could then add this search setting in the jquery script:
noResultsText: 'No result, hit space to create a new tag',
It's a little dirty but it works for me.
There is a bug in Application.js code. There is an extra ) after "/products/tags.json". Remove the extra ). The code should be:
$("#product_tags").tokenInput("/products/tags.json", {
prePopulate: $("#product_tags").data("pre"),
preventDuplicates: true,
noResultsText: "No results, needs to be created.",
animateDropdown: false
});
I don't know if this is the entirety of your error, but you are not hitting the proper URL with the tokenInput plugin.
This
$("#product_tag_list").tokenInput("/products/tags.json"), {
should be
$("#product_tag_list").tokenInput("/products.json"), {
As I said, I don't know if this is the only problem you are having, but if you change this, does it work?
EDIT:
I have never used ActsAsTaggableOn. Does it create a Tag model for you to use?
From the looks of it on github, if you wanted to query all tags, you might have to use its namespace as opposed to just Tag, meaning ActsAsTaggableOn::Tag. For example, you can see how they access Tags directly in some of the specs.
I had problems with editing the tags if for example the model failed to validate,
I changed
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tags.map(&:attributes).to_json %>
to
<%= p.text_field :tag_list,
:id => "product_tags",
"data-pre" => #product.tag_list.map {|tag| {:id => tag, :name => tag } }.to_json %>
If the form failed to validate on first submission, it was creating tags as the ID's of the tags it had created on subsequent submissions.
Two notes: if you're getting the tags changed by numbers on the POST request, use:
tokenValue: "name"
And if you're trying to add non-existent tags, use (undocumented):
allowFreeTagging: true

Rails/ajax - whitespace and request.raw_post

I am starting to learn Ajax with rails.
I have a catalog index page with a text_field_tag querying db if it finds similar "section" results.
Index.html.erb
<h1>Catalogs</h1>
<label>Search by Section:</label>
<%=text_field_tag :section %>
<%= observe_field(:section,
:frequency=> 0.1,
:update=> "article_list",
:url=>{ :action => :get_article_list }) %>
<div id="article_list"></div>
Catalogs_controller.rb
def index
end
def get_article_list
#section = request.raw_post.split(/&/).first
#catalogList = "<ol>"
Catalog.find(:all, :conditions => ["section = ?", #section]).each do |catalog|
#catalogList += "<li>" + catalog.title + "</li>"
end
#catalogList += "</ol>"
render :text => #catalogList
end
Question:
request.raw_post renders something like:
xml&authenticity_token=tgtxV3knlPvrJqT9qazs4BIcKYeFy2hGDIrQxVUTvFM%3D
so I use
request.raw_post.split(/&/).first
to get the section query ("xml"). It works, however how can I do if the query have a whitespace. (like "Open Source") In fact, I have Open Source sections in my db, but request.raw_post.split(/&/).first renders Open%20Source. How can I manage this? Did I have to use a full text search engine to achieve it or there is another way?
Thanks a lot for your explanation!
Look over your logs, in them you will see the post and the params being passed. You should not need to do your own query-string splitting. You should be able to use params[:section] to get the post data.
As your comment implies, there's something missing. Your observe_field needs to tell the Rails helper what to do. Check out: http://apidock.com/rails/ActionView/Helpers/PrototypeHelper/observe_field. Anyhow, you'll want to do something like:
observe_field(... # lots of parameters
:with => 'section'
)
And that should give you params[:section].

Searching in Ruby on Rails - How do I search on each word entered and not the exact string?

I have built a blog application w/ ruby on rails and I am trying to implement a search feature. The blog application allows for users to tag posts. The tags are created in their own table and belong_to :post. When a tag is created, so is a record in the tag table where the name of the tag is tag_name and associated by post_id. Tags are strings.
I am trying to allow a user to search for any word tag_name in any order. Here is what I mean. Lets say a particular post has a tag that is 'ruby code controller'. In my current search feature, that tag will be found if the user searches for 'ruby', 'ruby code', or 'ruby code controller'. It will not be found if the user types in 'ruby controller'.
Essentially what I am saying is that I would like each word entered in the search to be searched for, not necessarily the 'string' that is entered into the search.
I have been experimenting with providing multiple textfields to allow the user to type in multiple words, and also have been playing around with the code below, but can't seem to accomplish the above. I am new to ruby and rails so sorry if this is an obvious question and prior to installing a gem or plugin I thought I would check to see if there was a simple fix. Here is my code:
View: /views/tags/index.html.erb
<% form_tag tags_path, :method => 'get' do %>
<p>
<%= text_field_tag :search, params[:search], :class => "textfield-search" %>
<%= submit_tag "Search", :name => nil, :class => "search-button" %>
</p>
<% end %>
TagsController
def index
#tags = Tag.search(params[:search]).paginate :page => params[:page], :per_page => 5
#tagsearch = Tag.search(params[:search])
#tag_counts = Tag.count(:group => :tag_name,
:order => 'count_all DESC', :limit => 100)
respond_to do |format|
format.html # index.html.erb
format.xml { render :xml => #tags }
end
end
Tag Model
class Tag < ActiveRecord::Base
belongs_to :post
validates_length_of :tag_name, :maximum=>42
validates_presence_of :tag_name
def self.search(search)
if search
find(:all, :order => "created_at DESC", :conditions => ['tag_name LIKE ?', "%#{search}%"])
else
find(:all, :order => "created_at DESC")
end
end
end
If I read your problem correctly, you want to return a row if the tag names for the row matches one of the words passed in the query string.
You can rewrite your search method as follows:
def self.search(search)
all :conditions => (search ? { :tag_name => search.split} : [])
end
If you need partial matching then do the following:
def self.search(str)
return [] if str.blank?
cond_text = str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values = str.split.map{|w| "%#{w}%"}
all(:conditions => (str ? [cond_text, *cond_values] : []))
end
Edit 1
If you want pass multiple search strings then:
def self.search(*args)
return [] if args.blank?
cond_text, cond_values = [], []
args.each do |str|
next if str.blank?
cond_text << "( %s )" % str.split.map{|w| "tag_name LIKE ? "}.join(" OR ")
cond_values.concat(str.split.map{|w| "%#{w}%"})
end
all :conditions => [cond_text.join(" AND "), *cond_values]
end
Now you can make calls such as:
Tag.search("Ruby On Rails")
Tag.search("Ruby On Rails", "Houston")
Tag.search("Ruby On Rails", "Houston", "TX")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah")
Tag.search("Ruby On Rails", "Houston", "TX", "Blah", ....) # n parameters
Caveat:
The wild card LIKE searches are not very efficient(as they don't use the index). You should consider using Sphinx (via ThinkingSphinx) OR Solr(via SunSpot) if you have lot of data.
You can try to set up ferret, or if you are really bend on just using rails, try this:
# Break the search string into words
words = params[:search].blank? ? [] : params[:search].split(' ')
conditions = [[]] # Why this way? You'll know soon
words.each do |word|
conditions[0] << ["tag_name LIKE ?"]
conditions << "%#{word}%"
end
conditions[0] = conditions.first.join(" OR ") # Converts condition string to include " OR " easily ;-)
# Proceed to find using `:conditions => conditions` in your find
hope this helps =)
Sounds like you need a full text search. The best search integration right now is with Sphinx and the Thinking_Sphinx plugin. I have used it on several projects and it's super easy to setup.
You do need to install sphinx on your host so if you are using a shared host that could present some issues.
You could also use full text search in a MyISAM MySQL database, but performance on that is pretty poor.
Once you have your sphinx installed you just put what you want to index in your model and call model.search. The results will be a list of model objects. It supports will_paginate as well.
I'd suggest looking at Searchlogic if you don't want to use a separate fulltext search engine (Ferret, Sphinx, etc). It makes simple searches extremely easy, although you may not want to use it in a public facing area without lots of testing.
Also check out the Railscast on it: http://railscasts.com/episodes/176-searchlogic
1.You can do some coding in your controller post as such:-
<pre>
def show
#post = Post.find(params[:id])
#tag_counts = Tag.count(:group => :name, :order => 'updated_at DESC', :limit => 10)
respond_to do |format|
format.html # show.html.erb
format.json { render json: #post }
end
end
</pre>
2.Now make some changes in your view file:-
<pre>
<b>Tags:</b>
<%= join_tags(#post) %>
<%unless #tag_counts.nil?%>
<% #tag_counts.each do |tag_name, tag_count| %>
<tr><td><%= link_to(tag_name, posts_path(:name => tag_name)) %></td>
<td>(<%=tag_count%>)</td>
</tr><% end %>
<%end%>
</pre>
3. And one important thing is that there should be many to many relationship between tags and post.

Resources