Sunspot Rails to order search results by model id? - sunspot

Assume that I have the following model and I have made it searchable with sunspot_rails.
class Case < ActiveRecord::Base
searchable do
end
end
Standard schema.xml of Sunspot in Rails declare id as an indexed field. When I use the web interface to access solr and test queries a query like:
http://localhost:8982/solr/select/?q=id%3A%22Case+15%22&version=2.2&start=0&rows=10&indent=on
which searches for Cases with id equal to Case 15 works fine and returns results.
The problem is when I carry out the search with Sunspot Rails in the rails console:
s = Case.search do
keywords('id:"Case 15"')
end
I get:
=> <Sunspot::Search:{:fl=>"* score", :rows=>10, :start=>0, :q="id:\"Case 15\"", :defType=>"dismax", :fq=>["type:Case"]}>
which show that it correctly puts in :q the correct query value, but the hits are 0:
s.hits
returns
=> []
If we assume that keywords is not equivalent and only searches the text field (full-text search) and not the field defined before the colon :, then I can try the following:
s = Case.search do
with(:id, "Case 15")
end
but this fails with a Sunspot exception:
Sunspot::UnrecognizedFieldError: No field configured for Case with name 'id'
How can I search using the indexed standard solr/sunspot id field of my model?
And to make the question more useful, how can I order by the id. The following does not work:
s = Case.search do
keywords("xxxx")
order_by :id, :desc
end
does not work. Sunspot::UnrecognizedFieldError: No field configured for Case with name 'id'

The id that you are talking about is a Sunspot internal field and it should not be used directly.
Why not add your own id field (change variable name, to avoid name collision):
class Case < ActiveRecord::Base
searchable do
integer :model_id {|content| content.id }
end
end
and then
s = Case.search do
keywords("xxxx")
order_by :model_id, :desc
end
Other (messy) option would be to hack directly solr params:
s = Case.search do
keywords("xxxx")
adjust_solr_params(:sort, 'id desc')
end

Related

AR Query for jsonb attribute column

I'm using Postgres for my db, and I have a column in my Auction model as a jsonb column. I want to create a query that searches the Auction's json column to see whether a user's name OR email exists in any Auction instance's json column.
Right now I have #auctions = Auction.where('invitees #> ?', {current_user.name.downcase => current_user.email.downcase}.to_json), but that only brings back exact key => value matches I want to know whether the name OR the email exists.
You're using the #> operator. The description for it is this:
“Does the left JSON value contain the right JSON path/value entries
at the top level?”
You probably want the ? operator if you want to return a row when the key (name in your case) matches.
There's not a good way to search for values only in a JSON column (see this answer for more details), but you could check if the key exists alone or the key and value match exists.
The same ActiveRecord methods and chaining apply as when using non-JSON columns, namely where and where(…).or(where(…)):
class Auction
def self.by_invitee(user)
name = user.name.downcase
json = { name => user.email } # note: you should be downcasing emails anyways
where('invitee ? :name', name: name).or(
where('invitee #> :json', json: json)
)
end
end
This is just a temporary patch until I add an Invite model, but casting the invitee column to text I can search the columns. Like so
SELECT * FROM auctions
WHERE data::text LIKE "%#{string I'm searching for}%"
So, AR:
Auction.where('data::text LIKE ?', "%#{string I'm searching for}%")

Using Ecto for Postgres fulltext search on GIN indexes

I have a simple model:
schema "torrents" do
field :name, :string
field :magnet, :string
field :leechers, :integer
field :seeders, :integer
field :source, :string
field :filesize, :string
timestamps()
end
And I want to search based on the name. I added the relevant extensions and indexes to my database and table.
def change do
create table(:torrents) do
add :name, :string
add :magnet, :text
add :leechers, :integer
add :seeders, :integer
add :source, :string
add :filesize, :string
timestamps()
end
execute "CREATE EXTENSION pg_trgm;"
execute "CREATE INDEX torrents_name_trgm_index ON torrents USING gin (name gin_trgm_ops);"
create index(:torrents, [:magnet], unique: true)
end
I'm trying to search using the search term, but I always get zero results.
def search(query, search_term) do
from(u in query,
where: fragment("? % ?", u.name, ^search_term),
order_by: fragment("similarity(?, ?) DESC", u.name, ^search_term))
end
SELECT t0."id", t0."name", t0."magnet", t0."leechers", t0."seeders", t0."source",
t0."filesize", t0."inserted_at", t0."updated_at" FROM "torrents"
AS t0 WHERE (t0."name" % $1) ORDER BY similarity(t0."name", $2) DESC ["a", "a"]
Is something wrong with my search function?
My initial guess is that because you're using the % operator, the minimum limit to match is too high for your queries. This limit defaults to 0.3 (meaning that the strings' trigrams are 30% similar). If this threshold isn't met, no results will be returned.
If that is the issue, this threshold is configurable in a couple of ways. You can either use set_limit (docs here), or set the limit on a per query basis.
The set_limit option can be a bit of a hassle, as it needs to be set per connection every time. Ecto (through db_connection) has an option to set a callback function for after_connect (docs here).
To change the limit per query, you can use the similarity function in the where clause, like this:
def search(query, search_term, limit = 0.3) do
from(u in query,
where: fragment("similarity(?, ?) > ?", u.name, ^search_term, ^limit),
order_by: fragment("similarity(?, ?) DESC", u.name, ^search_term))
end
To start, I would try that with a limit of zero to see if you get any results.

doctrine2 select specific columns

In mysql, we can return specific result like as:
select name,lastName from users where id=1;
how use the same request in doctrine2?
I khnow that we can use
$query=$this->_em->createQuery('select a.name, a.lastName from ...:Users a'); but i search some methods to user it without a.name
for examlpe
$query=$this->_em->createQuery('select name, lastName from ...:Users '); is correct?
it's possible to return just the name and the lastname from a table without the prefix a?
If you want to select some specific fields from the database you need to use the partial keyword.
Taken from the doctrine partial object documentation your query should look like:
$query=$this->_em->createQuery('SELECT partial u.{name, lastName} from Users u');
This will return an array of partially loaded User objects.

How to select the latest entry from database with Ecto/Phoenix

I'm just starting with databases and queries (coming from front-end) and I would like to select the latest entry from the fields that contain a specific title, say Page1. I know I have two fields that can help me:
1) created_at - I can order it an select only the latest one;
2) id - I know that every entry generates a new id so I can get the biggest id;
I want to apply it to the "standard" edit controller action:
def edit(conn, %{"id" => id}) do
user = Repo.get(User, id)
changeset = User.update_changeset(user)
render(conn, "edit.html", user: user, changeset: changeset)
end
So I would like to know which method is better and to see an example of the query.
Using the id field depends on your primary key being an integer. This may be the case currently, but will it always be the case? You can be sure that inserted_at will always be a time field - so I would use that. Regardless of which you use, be sure to order it in descending order so that the most recent item is first.
title = "Page 1"
query = Ecto.Query.from(e in Entry,
where: e.title == ^title,
order_by: [desc: e.inserted_at],
limit: 1)
most_recent = Repo.one(query)
It is worth noting that in Ecto - by default the insertion time is captured as inserted_at and not created_at - if you have a created_field then you can replace inserted_at in my example.
It's also possible to accomplish this with Ecto expressions syntax and Ecto.Query.last/2 like so:
alias App.Entry
import Ecto.Query
Entry |> where(title: "Page 1") |> last(:inserted_at) |> Repo.one
See docs for last/2 here!

Race condition, url shortener algorithm/Rails/MongoDB/MongoMapper

I created a url shortener algorithm with Ruby + MongoMapper
It's a simple url shortener algorithm with max 3 digits
http://pablocantero.com/###
Where each # can be [a-z] or [A-Z] or [0-9]
For this algorithm, I need to persist four attributes on MongoDB (through
MongoMapper)
class ShortenerData
include MongoMapper::Document
VALUES = ('a'..'z').to_a + ('A'..'Z').to_a + (0..9).to_a
key :col_a, Integer
key :col_b, Integer
key :col_c, Integer
key :index, Integer
end
I created another class to manage ShortenerData and to generate the unique
identifier
class Shortener
include Singleton
def get_unique
unique = nil
#shortener_data.reload
# some operations that can increment the attributes col_a, col_b, col_c and index
# ...
#shortener_data.save
unique
end
end
The Shortener usage
Shortener.instance.get_unique
My doubt is how can I make get_unique synchronized, my app will be
deployed on heroku, concurrent requests can call
Shortener.instance.get_unique
I changed the behaviour to get the base62 id. I created an auto increment gem to MongoMapper
With the auto incremented id I encode to base62
The gem is available on GitHub https://github.com/phstc/mongomapper_id2
# app/models/movie.rb
class Movie
include MongoMapper::Document
key :title, String
# Here is the mongomapper_id2
auto_increment!
end
Usage
movie = Movie.create(:title => 'Tropa de Elite')
movie.id # BSON::ObjectId('4d1d150d30f2246bc6000001')
movie.id2 # 3
movie.to_base62 # d
Short url
# app/helpers/application_helper.rb
def get_short_url model
"http://pablocantero.com/#{model.class.name.downcase}/#{model.to_base62}"
end
I solved the race condition with MongoDB find_and_modify http://www.mongodb.org/display/DOCS/findAndModify+Command
model = MongoMapper.database.collection(:incrementor).
find_and_modify(
:query => {'model_name' => 'movies'},
:update => {'$inc' => {:id2 => 1}}, :new => true)
model[:id2] # returns the auto incremented_id
If this new behaviour I solved the race condition problem!
If you liked this gem, please, help to improve it. You’re welcome to make your contributions and send them as a pull request or just send me a message http://pablocantero.com/blog/contato