How do I remove sphinx_deleted from a Sphinx query? - ruby

I am new to Ruby and ThinkingSphinx.
I have the following Sphinx Query - SELECT * FROM user_core, user_delta WHERE sphinx_deleted = 0.
I do not want to see the condition "WHERE 'sphinx_deleted' = 0. How do I remove this? I have removed the sql_attr_uint = sphinx_deleted from my sphinx.conf file, yet I see the sphinx_deleted being passed in the query.
Here is the index file definition:
ThinkingSphinx::Index.define :user, :with => :active_record, :delta => true do
indexes [first_name,last_name,display_name], :as=>:name, :sortable=>true
indexes first_name, :sortable => true
indexes last_name, :sortable => true
indexes display_name, :sortable => true
indexes email, :sortable => true
indexes phone, :sortable => true
indexes title, :sortable => true
has id, :as => :user_id
has roles(:id), :as => :role_ids
has jurisdictions(:id), :as => :jurisdiction_ids
set_property :delta => true
end
I do not have a sphinx_scope or default_sphinx_scope defined.
We are using thinking-sphinx-3.1.0 and ruby-2.1.0

The sphinx_deleted attribute is created by Thinking Sphinx, and is used in the following cases (using your scenario of a User model with core and delta indices in the examples):
When a User is deleted, sphinx_deleted is set to 1 for that record in both the core and delta indices - there's no point returning Sphinx records if the underlying ActiveRecord object no longer exists.
When a User is updated, the delta index is processed with the latest field and attribute details, and the core index's document has sphinx_deleted set to 1, so only the latest (accurate) information will match. e.g. if a user has their name changed from Fred to Georgina, a search for 'Fred' will not return Georgina, because the core index document (which does match) is filtered out.
That is why the attribute exists. You cannot tell Thinking Sphinx to not add it, nor can you remove that filter, short of mucking around in the internals of Thinking Sphinx.
If there is a specific reason for wanting to remove the attribute and filter, feel free to comment here, or you can open an issue on the GitHub repo, or post to the TS Google Group.
Update
Okay, further to this, there are three ways around it.
Option One:
The first way is to make the query to Sphinx yourself, using a Thinking Sphinx connection:
results = ThinkingSphinx::Connection.take do |connection|
connection.execute "SELECT * FROM user_core, user_delta"
end
Keep in mind that this returns raw Sphinx values, not ActiveRecord instances.
Option Two:
A more complicated alternative, though, is to have your own search middleware stack. First, you'll want to create a custom subclass of ThinkingSphinx::Middlewares::SphinxQL that removes the :sphinx_deleted filter:
class SphinxQLWithoutFilter < ThinkingSphinx::Middlewares::SphinxQL
def call(contexts)
contexts.each do |context|
Inner.new(context).call
end
app.call contexts
end
private
class Inner < ThinkingSphinx::Middlewares::SphinxQL::Inner
def inclusive_filters
super.except :sphinx_deleted
end
end
end
Then, create a new middleware stack which uses this new SphinxQL query middleware:
WithoutFilterMiddleware = ::Middleware::Builder.new do
use ThinkingSphinx::Middlewares::StaleIdFilter
use SphinxQLWithoutFilter
use ThinkingSphinx::Middlewares::Geographer
use ThinkingSphinx::Middlewares::Inquirer
use ThinkingSphinx::Middlewares::ActiveRecordTranslator
use ThinkingSphinx::Middlewares::StaleIdChecker
use ThinkingSphinx::Middlewares::Glazier
end
And then you can use that middleware stack in specific search queries:
User.search 'foo', :middleware => WithoutFilterMiddleware
It's worth noting the two middleware present in that stack for stale ids. They work together to catch any Sphinx results that do not have a matching ActiveRecord object, and re-run the Sphinx query up to three times filtering out those unmatched records. They're probably useful, but if you don't want to use them, you can remove them from your custom stack. However, without them, any Sphinx records that don't have matching ActiveRecord objects will be transformed into nils.
Option Three:
This is the more hackish version of the previous solution, but will apply to all searches, so probably isn't worthwhile: re-open the class that adds the filter with class_eval and change the method definition:
ThinkingSphinx::Middlewares::SphinxQL::Inner.class_eval do
def inclusive_filters
# normally:
# (options[:with] || {}).merge({:sphinx_deleted => false})
# but without the sphinx_deleted filter:
options[:with] || {}
end
end
Now, all that said: I presume you're not actually deleting users, but somehow the deletion callbacks are being fired anyway? Hence, users do exist but are currently being filtered out by Sphinx? If so, I highly recommend not using ActiveRecord's destroy method, and instead having a custom method to mark users as inactive. This avoids the callbacks, and thus avoids the need for any of the above 'solutions'.

Related

How to query multiple fields with Chewy

Let's say I have an index with multiple objects in it:
class ThingsIndex < Chewy::Index
define_type User do
field :full_name
end
define_type Post do
field :title
end
end
How do I search both users' full_name and posts' titles.
The docs only talk about querying one attribute like this:
ThingsIndex.query(term: {full_name: 'Foo'})
There are a couple ways you could do this. Chaining is probably the easiest:
ThingsIndex.query(term: {full_name: 'Foo'}).query(term: {title: 'Foo'})
If you need to do several queries, you might consider merging them:
query = ThingsIndex.query(term: {full_name: 'Foo'})
query = query.merge(ThingsIndex.query(term: {title: 'Foo'}))
Read more about merging here: Chewy #merge docs
Make sure to set your limit or else it only shows 10 results:
query.limit(50)

Mongoid push with upsert

I've got model User:
class User
field :username, type: String
embeds_many :products
end
class Product
field :name, type: String
embedded_in :user
end
I would like to have single operation that would:
insert the user
update the user in case the user exists already (this i can easily do with upsert)
push the products
This works for upserting:
User.new(username: 'Hello').upsert
The problem is that this will delete the embedded products (the products attribute is not specified).
Can I ask mongoid to skip setting array to empty?
Can I ask mongoid to push new products at the end of products array?
Something like this:
User.new(username: 'Hello').push(products: [Product.new(name: 'Screen')]).upsert
Finally I ended up by manually writing the following query:
User.mongo_client[:users].update_one({username: 'Hello'},
{"$set" => {first_name: 'Jim', last_name: 'Jones'},
"$pushAll" => [products: [{name: 'Screen'}, {name: 'Keyboard'}]
},
upsert: true)
Where:
$set - are the params that we want to set for a given document
$pushAll - when you use $push you can specify only one element, $pushAll allows you to append multiple elements (when you specify only one it will behave like $push)
upsert - will do the insert/update magic in the mongodb
In the second hash you can also specify $inc, $dec, $pop, $set etc... which is quite useful.

Mongo Ruby Driver #find() on Specified field Values

Ruby: ruby 1.9.3p194 (2012-04-20 revision 35410) [x86_64-linux]
RubyGem: mongo (2.0.4)
I need help querying a MongoDB database with their gem and updating the appropriate fields.
EDIT: I'm trying to loop over documents of a Mongo database, pull down the values of specific fields in those documents, and update them later in the script.
Objectives
Query the database for documents where the field partner_id is "partner" and where the field state is "provisioned", and return only the values under the _id and config fields.
After this point, I'll be iterating over each document, generating a password, and updating another database.
Update the database with the newly generated password to each documents config field.
I'm at my wit's end as I've seen about half a dozen different way to write the syntax, and the documentation is little help unless I already knew how to do these things. Any assistance would be greatly appreciated!
#!/usr/bin/env ruby
require 'json'
require 'net/http'
require 'mongo'
# Fetch the addons database URI and connect.
db_uri = ENV['DATABASE_URI']
client = Mongo::Client.new(db_uri)
# Connect to the needed collection and pull down each document to be looped over individually.
# **Having trouble getting this to work. The result is just '= []' - don't know what I'm doing wrong.
client[:collection].find("partner_id" => "partner", "state" => "provisioned", :fields => ["_id", "config"]).each {
# Need something here to pull down the values from each document's '_id' and 'config' fields and assign them to variables.
user_id =
user_config =
user_config = JSON.parse(user_config)
# ...generating password and updating other database...
# Convert the Hash of the user's new configuration into JSON, and update the original database with it.
# Not sure if any of this is correct. When querying to check, the database doesn't seem to be updated.
user_config = user_config.to_json
client[:collection].update(
{"_id" => user_id},
{'$set' => {
"config" => user_config
}
}
)
}
end
return
You're not finding anything because this:
:fields => ["_id", "config"]
argument to find isn't specifying the fields you want, find just sees that as a third document field to look for. Your documents probably don't have a field called field whose value is an array of those strings so the query silently finds nothing at all.
If you want to limit your query, you need to use projection:
client[:collection].find("partner_id" => "partner", "state" => "provisioned")
.projection('_id' => 1, 'config' => 1)
.each { |doc| ... }
Then inside the each block the doc will be a Hash so you can say:
user_id = doc['user_id']
user_config = doc['user_config']
If I'm reading your code right, the user_config should be a Hash already so you probably won't need to parse it yourself.

Sinatra: User.first method

I'm reading a book that's making a Twitter clone with Sinatra in order to improve my knowledge of Ruby. I'm puzzled by the author's use of
User.first(:nickname => params[:recipient])
which he uses in several locations throughout the code, as in the following example.
post '/message/send' do
recipient = User.first(:nickname => params[:recipient])
Status.create(:text => params[:message], :user => User.
get(session[:userid]), :recipient => recipient)
redirect '/messages/sent'
end
What exactly is 'first' adding to this method. For example, is it searching for the first user with the nickname passed in as the parameter :recipient? In other words, is it equivalent to 'find'?
I should add that it puzzles me also because the nicknames are supposed to be unique, so there's no reason why it would need to search for the 'first' if that's indeed what it's doing.
Update
The author is using DataMapper for the ORM
Ok, 'first' is a datamapper method that 'finds'. From the docs
zoo = Zoo.first(:name => 'Metro') # first matching record with the name 'Metro'

ThinkingSphinx Order via URL Params

I am using ThinkingSphinx in an application and right now I am not doing any type of order on my results. However, I would like to make this an option via a link someone can click on the page and it just passes it through the URL to 'refresh' the page with the results now ordered.
In the .search parameters I tried doing :order => params[:o] then in the URL passing o=columnname but that does not seem to work.
Just to note, when I hard-code the ordering it works fine, I'm not having trouble with indexing/making a DB column sortable. I would just like to make it so via a URL argument it the results can be displayed ordered.
According to the Sphinx documentation, the fields you want to use for sorting must be flagged as sortable. Attributes defined with has do not have to be flagged, because all attributes are sortable:
class Article
..
define_index do
indexes title, :sortable => true
indexes author(:name), :as => :author, :sortable => true
..
end
Then one can use the :order and :sort_mode parameter to define the sort order:
sort_order = params[:o]
Article.search "pancakes", :order => sort_order, :sort_mode => :desc

Resources