I'm having some difficulty with Elastic Search and Tire not returning any results. I'm using Ruby 1.9.3 and Rails 3.2.11.
In my controller I'm calling:
#location_id = 1
#listings = Listing.search(params.merge!(location_id: #location_id))
In my listing model I have
mapping do
indexes :id, type: 'integer'
...
indexes :author do
indexes :location_id, :type => 'integer', :index => :not_analyzed
...
end
def self.search(params={})
tire.search(load: true, page: params[:page], per_page: 20) do |search|
search.query { string params[:query], :default_operator => "AND" } if params[:query].present?
search.filter :range, posted_at: {lte: DateTime.now}
search.filter :term, "author.location_id" => params[:location_id]
end
I have 300 results which all have the location_id of 1 in the database so I can't seem to figure out why it's returning a nil set? If I comment out the author.location_id search filter line it returns all other results as expected?
There are several things which needs to be adressed in a situation like yours. Let's start with a fully working code:
require 'active_record'
require 'tire'
require 'logger'
# Tire.configure { logger STDERR }
# ActiveRecord::Base.logger = Logger.new(STDERR)
Tire.index('articles').delete
ActiveRecord::Base.establish_connection( adapter: 'sqlite3', database: ":memory:" )
ActiveRecord::Schema.define(version: 1) do
create_table :articles do |t|
t.string :title
t.integer :author_id
t.date :posted_at
t.timestamps
end
create_table :authors do |t|
t.string :name
t.integer :number, :location_id
t.timestamps
end
add_index(:articles, :author_id)
add_index(:authors, :location_id)
end
class Article < ActiveRecord::Base
belongs_to :author, touch: true
self.include_root_in_json = false
include Tire::Model::Search
include Tire::Model::Callbacks
mapping do
indexes :title
indexes :author do
indexes :location_id, type: 'integer'
end
end
def self.search(params={})
tire.search load: {include: 'author'} do |search|
search.query do |query|
query.filtered do |f|
f.query { params[:query].present? ? match([:title], params[:query], operator: 'and') : match_all }
f.filter :range, 'posted_at' => { lte: DateTime.now }
f.filter :term, 'author.location_id' => params[:location_id]
end
end
end
end
def to_indexed_json
to_json( only: ['title', 'posted_at'], include: { author: { only: [:location_id] } } )
end
end
class Author < ActiveRecord::Base
has_many :articles
after_touch do
articles.each { |a| a.tire.update_index }
end
end
# -----
Author.create id: 1, name: 'John', location_id: 1
Author.create id: 2, name: 'Mary', location_id: 1
Author.create id: 3, name: 'Abby', location_id: 2
Article.create title: 'Test A', author: Author.find(1), posted_at: 2.days.ago
Article.create title: 'Test B', author: Author.find(2), posted_at: 1.day.ago
Article.create title: 'Test C', author: Author.find(3), posted_at: 1.day.ago
Article.create title: 'Test D', author: Author.find(3), posted_at: 1.day.from_now
Article.index.refresh
# -----
articles = Article.search query: 'test', location_id: 1
puts "", "Documents with location:1", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
articles = Article.search query: 'test', location_id: 2
puts "", "Documents with location:2", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
puts "(NOTE: 'D' is missing, because is not yet posted)"
articles = Article.search query: 'test b', location_id: 1
puts "", "Documents with query:B and location:1", '-'*80
articles.results.each { |a| puts "* TITLE: #{a.title}, LOCATION: #{a.author.location_id}, DATE: #{a.posted_at}" }
First, it's usually a good idea to create an isolated, extracted case like this.
In your example code, I assume you have a relationship Listing belongs_to :author. You need to properly define the mapping and serialization, which I again assume you did.
As for the query itself:
Unless you're using faceted navigation, use the filtered query, not top level filters, as in my example code.
Do not use the string query, unless you really want to expose all the power (and fragility!) of the Lucene query string query to your users.
Use the match query, as your "generic purpose" query -- Tire sprinkles some sugar on top of it, allowing to easily create multi_match queries, etc
The filter syntax in your example is correct. When the filter method is called multiple times in Tire, it creates and and filter.
Uncomment the Tire logging configuration (and possibly also the ActiveRecord logging), to see what the code is doing.
Related
So I'm implementing Stripe and users are able to purchase successfully, however, I would like to get the charge information, last 4 card numbers, card type etc so I can generate receipts using https://github.com/excid3/receipts.
Here is what I have so far:
PaymentsController
class PaymentsController < ApplicationController
before_action :authenticate_user!
def create
token = params[:stripeToken]
#course = Course.find(params[:course_id])
#user = current_user
begin
charge = Stripe::Charge.create(
amount: (#course.price*100).to_i,
currency: "gbp",
source: token,
description: params[:stripeEmail],
receipt_email: params[:stripeEmail]
)
if charge.paid
Order.create(
course_id: #course.id,
user_id: #user.id,
Amount: #course.price
)
end
flash[:success] = "Your payment was processed successfully"
rescue Stripe::CardError => e
body = e.json_body
err = body[:error]
flash[:error] = "Unfortunately, there was an error processing your payment: #{err[:message]}"
end
redirect_to course_path(#course)
end
end
OrdersController
class OrdersController < ApplicationController
layout proc { user_signed_in? ? "dashboard" : "application" }
before_action :authenticate_user!
def index
#orders = Order.includes(:course).all
end
def show
#order = Order.find(params[:id])
respond_to do |format|
format.pdf {
send_data #order.receipt.render,
filename: "#{#order.created_at.strftime("%Y-%m-%d")}-aurameir-courses-receipt.pdf",
type: "application/pdf",
disposition: :inline
}
end
end
def create
end
def destroy
end
end
Order.rb
class Order < ApplicationRecord
belongs_to :course
belongs_to :user
validates :stripe_id, uniqueness: true
def receipt
Receipts::Receipt.new(
id: id,
subheading: "RECEIPT FOR CHARGE #%{id}",
product: "####",
company: {
name: "####",
address: "####",
email: "####",
logo: "####"
},
line_items: [
["Date", created_at.to_s],
["Account Billed", "#{user.full_name} (#{user.email})"],
["Product", "####"],
["Amount", "£#{amount / 100}.00"],
["Charged to", "#{card_type} (**** **** **** #{card_last4})"],
["Transaction ID", uuid]
],
font: {
normal: Rails.root.join('app/assets/fonts-converted/font-files/AvenirBook.ttf')
}
)
end
end
schema.rb
create_table "orders", force: :cascade do |t|
t.integer "user_id"
t.integer "course_id"
t.integer "stripe_id"
t.integer "amount"
t.string "card_last4"
t.string "card_type"
t.integer "card_exp_month"
t.integer "card_exp_year"
t.string "uuid"
t.index ["course_id"], name: "index_orders_on_course_id"
t.index ["user_id"], name: "index_orders_on_user_id"
end
How would I go about getting the charge information?
All the information you need is in the charge object you created.
if charge.paid
Order.create(
course_id: #course.id,
user_id: #user.id,
amount: #course.price,
card_last4: charge.source.last4,
card_type: charge.source.brand,
card_exp_month: charge.source.exp_month,
card_exp_year: charge.source.exp_year
)
end
See https://stripe.com/docs/api/ruby#charge_object for all the information available to you about a charge.
I guess you are not storing the Stripe Charge ID anywhere on successful payment.
My suggestion, store the charge ID in your Order record
if charge.paid
Order.create(
course_id: #course.id,
user_id: #user.id,
amount: #course.price,
stripe_charge_id: charge.id
)
end
In your receipt method you can fetch the stripe charge as follows:
def receipt
stripe_charge = Stripe::Charge.retrieve(stripe_charge_id)
...
end
The stripe_charge objects contains all the information and you can use whatever data you need.
Hope this helped.
Im trying to make a search function for an app. I have a service object called searches and all search logic is in this object.
When the search form is submitted with an about query #results in Search Controller is the following array.
=> [
[0] [],
[1] [
[0] About {
:id => 2,
:title => "About",
:slug => "about",
:meta_description => "",
:meta_keywords => "",
:content => "<p>Lorem ipsum about</p>",
:position => 1,
:published => true,
:on_menu => true,
:created_at => Wed, 13 Jan 2016 00:44:08 UTC +00:00,
:updated_at => Fri, 15 Jan 2016 04:05:52 UTC +00:00
}
],
[2] [],
[3] []
]
Here is my Search object. See how the User attributes are different then the Pages and NewsArticle? Good. Now go look at the view.
class SearchService
attr_accessor :query
def initialize(query)
#query = query
end
# searchable_fields_by_model
CONDITIONS_BY_MODEL = {
Page => [:title, :content],
NewsArticle => [:title, :content]
User => [:first_name, :last_name]
}
def raw_results
ActiveRecord::Base.transaction do
CONDITIONS_BY_MODEL.flat_map do |model, fields|
Array(fields).map do |field|
model.where(["#{field} LIKE ?", "%#{query}%"])
end
end
end
end
end
Below is my view. Currently the view will only display content and title. But my User has first_name and last_name therefore it won't display. I need a way to display the results of all Models and it needs to be clean so if i add another model to search with entirely different attributes it will still display. How would you do this?
.page-header
%h1 Search#index
- #results.each do |results|
- results.each do |r|
%p= r.title
%p= r.content
Search controller
class SearchController < ApplicationController
def index
#results = SearchService.new(params[:q]).results
end
end
How to fix this.
class SearchController < ApplicationController
def index
#results = SearchService.new(params[:q]).results
#page_results = []
#news_article_results = []
#category_results = []
#results.each do |resource|
if resource.class == Page
#page_results << resource
elsif resource.class == NewsArticle
#news_article_results << resource
elsif resource.class == Category
#category_results << resource
else
end
end
end
end
class SearchService
MIN_QUERY_LENGTH = 3
attr_accessor :query
def initialize(query)
raise ArgumentError if query.length < MIN_QUERY_LENGTH
#query = query
end
# searchable_fields_by_model
CONDITIONS_BY_MODEL = {
Page => [:title, :content],
NewsArticle => [:title, :content],
Category => :name,
}
def results
#results ||= ActiveRecord::Base.transaction do
CONDITIONS_BY_MODEL.flat_map do |model, fields|
Array(fields).flat_map do |field|
model.where(["#{field} LIKE ?", "%#{query}%"])
end
end
end
end
end
Here is my view
.page-header
%h1 Search Results
%h2= "For #{params[:q]}"
%br
%h2 Pages
- #page_results.each do |resource|
%p= link_to "#{resource.title}", page_path(resource.slug)
= resource.content.html_safe
%h2 News Article
- #news_article_results.each do |resource|
%p= link_to "#{resource.title}", news_article_path(resource.slug)
= resource.content.html_safe
%h2 Category
- #category_results.each do |resource|
%p= link_to "#{resource.name}", category_path(resource.slug)
require "active_record"
ActiveRecord::Base.establish_connection(
:adapter => 'mysql2',
:database => '<db_name>',
:username => '<username>',
:password => '<password>',
:host => 'localhost')
ActiveRecord::Base.pluralize_table_names = false
class Location < ActiveRecord::Base
has_many :location_channels
has_many :channels, :through => :location_channels
end
class Channel < ActiveRecord::Base
has_many :location_channels
has_many :locations, :through => :location_channels
end
class LocationChannel < ActiveRecord::Base
belongs_to :location
belongs_to :channel
end
locations = Location.all
hash = {} # hash initialization
locations.each do |location|
hash["location"] = location[:name]
puts "#{location[:name]} has #{location.channels.size} channels:"
location.channels.each do |channel|
puts "--> #{channel[:name]}"
end
puts
end
puts hash
Final goal is to create one JSON file.
So I decided it'll be easier to create JSON from Hash object.
As in the code above, I'm able to access nested documents via JOIN table called LocationChannel class and I'm trying to figure out how to create a Hash object what will look like:
{
["location" => "A", "channels" => {"1","2","3"}],
["location" => "B", "channels" => {"1","2"}],
["location" => "C", "channels" => {"4","5","6"}]
}
where "A", "B" and "C" - locations name and "1", "2", etc. - represents channels name.
And the current code prints out only the last record like:
{"location"=>"A"}
Please correct me how should Hash look like if the sample above is wrong.
UPDATE 1
Thanks to #jonsnow for point out the hash format.
Hash format should be:
{ :locations =>
[
{ name: a, channels: [1,2,3]},
{ name: b, channels: [1,2]},
{ name: c, channels: [4,5,6]}
]
}
Solution for your updated hash,
hash = { locations: [] } # hash initialization
locations.each do |location|
hash[:locations] << { name: location.name,
channels: location.channels.pluck(:name) }
end
I'm quite new to Rails, so be gentle :)
I have the following models set-up:
class User
has_many :it_certificates, :class_name => 'UserCertificate'
class UserCertificate
belongs_to :skill
Given the following input (in JSON)
{
"certificates":[
{ // update
"id":1,
"name":"Agile Web Dev 2",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2012
},
"skill": {
"id":57
}
},
{ // create
"name":"Agile Web Dev 1",
"entity":"Agile Masters!",
"non_it":false,
"date_items":{
"month":10,
"year":2011
},
"skill": {
"id":58
}
}
]
}
How's the easiest way to update the information for the relation it_certificates?
I've been looking to update_all but it doesn't match my needs (it only updates given fields with the same value).
So I've been struggling around with the approach of iterating over each of these records and then update them one-by-one.
I mean struggling because it looks to me there are lots of things I have to care of when the idea of Rails is the opposite.
Thanks in advance!
So, here's my solution for now:
def self.update_from_hash(data, user_id)
self.transaction do
data.each do |certificate|
if certificate[:id] == nil
# create
if !self.create(
:name => certificate[:name],
:entity => certificate[:entity],
:user_id => user_id,
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
)
raise ActiveRecord::Rollback
end
else
# update
if !self.update(certificate[:id], {
:name => certificate[:name],
:entity => certificate[:entity],
:non_it => certificate[:non_it],
:skill_id => certificate[:skill][:id],
:date => self.build_date_from_items(certificate[:date_items][:month], certificate[:date_items][:year])
})
raise ActiveRecord::Rollback
end
end
end
end
return true
end
It works, but I'm still expecting a more elegant solution :)
I'm trying to add an attribute to a model object. Direct access works but when I print the entire object or encode it into JSON, that attribute is left out. Can someone tell me what I'm doing wrong?
Here is my rails console output:
irb(main):010:0> b=ChatMessage.new(:user_id=>4,:room_id=>1,:message=>"Hello World")
=> #<ChatMessage id: nil, room_id: 1, user_id: 4, message: "Hello World", created_at: nil, updated_at: nil>
irb(main):011:0> b.sender_nickname="bbb"
=> "bbb"
irb(main):012:0> b.sender_nickname
=> "bbb"
irb(main):013:0> b
=> #<ChatMessage id: nil, room_id: 1, user_id: 4, message: "Hello World", created_at: nil, updated_at: nil>
Here is my model code:
class ChatMessage < ActiveRecord::Base
attr_accessor :sender_nickname
def self.get_last_message_id
last_message=ChatMessage.all.last
last_message.nil? ? 0 : last_message.id
end
def self.get_all_messages_after(room_id,message_id)
ChatMessage.where("room_id = ? AND id > ?",room_id,message_id)
end
end
edit:
Here is the migration file for chat_messages table.
I'm not really looking to save sender_nickname. So it's more like a virtual attribute (but is still in db through association). And I might need to add other attributes later that aren't in the db. Is it possible to do it without using association?
def self.up
create_table :chat_messages do |t|
t.integer :room_id
t.integer :user_id
t.string :message
t.timestamps
end
end
as far as I know to_json will only take the attributes in the model and serialize (as in chat_message.attributes, not attr_accessor).
You properbly got a sender, or user model, or anything like that.
What I would do is to make a relation to the sender, user or what its called, with a belong_to, and then use this code to convert it to json:
chat_message.to_json(:include => { :sender => { :only => :nickname } })
It may also work with you code, and then just:
chat_message.to_json(:include => { :sender_nickname })
There also some documentation here: http://api.rubyonrails.org/classes/ActiveModel/Serializers/JSON.html
Hope it helps :)