Indexing using two tables in sunspot solr + rails 4 - ruby-on-rails

I'm using sunspot-solr in ROR and I need help in creating a searchable block using two tables.(join of two tables)
The query I want to be executed when the indexes are formed is :
SELECT a.id,a.title
FROM table_one a,table_two b
WHERE a.status=1
AND a.id=b.id
AND b.status=1
I want the "title" field to be searchable(text), only if the id exists in both tables and both have status 1.And I want them to be stored fields(no db hits).
class TableOne
has_many :table_twos
searchable do
text :title, :stored => true
string :status, :stored => true
string :id, :multiple => true, :stored => true do
table_twos.map(&:id)
end
end
When I searched a word, I got 5 results.
But when I delete an entry of one of the results from table_two and searched the same word again.. I still got 5 results when I should get only the other 4.
Any help ?

If you delete an associated record that was stored as a solr/sunspot record, you will have no choice but to reindex that record.

So to solve the issue I did somthing like without(:id,nil) in my controller and I got the results as I wanted them.
I'm not sure its the right way to go about it though.

Related

Need to find all "Clubs" where column "userlist" contains "current_user.id" [duplicate]

This question already has answers here:
Searching serialized data, using active record
(10 answers)
Closed 9 years ago.
I need to find all "Clubs" where column "userlist" (which contians an array of user ids) contains "current_user.id".
Here is the line of code in the controller that I am trying to use to accomplish this:
#userclubs = Club.where('userlist in (?)', current_user.id).all
I end up with an empty array: []
This is the code I am using in the view:
<%= f.association :clubs, :collection => #userclubs, :label_method => :name, :value_method => :name, :as => :check_boxes, :item_wrapper_class => 'inline' %>
If I change the controller code to #userclubs = Club.all I get all of the clubs as check boxes, but I only want the ones that the user belongs to to show.
I am probably going about this all wrong, but I am new to RoR and can't seem to find the proper way to do it.
The where method is thin layer over SQL. Your attribute is only an array in the eyes of Rails. From the database perspective, userlist is a text field and you can only search it as a text field.
While searching serialized structures is generally not practical, as discussed in Searching serialized data, using active record, in the case of flat array, you could do a regex search of the column looking for user_id in question, taking care to insure that the user_id is surrounded by the appropriate separate characters, something like:
userlist rlike ...
where ... is the constructed regex including ? for the value of current_user.id surrounded by the appropriate separate characters.
Since you're using Postgres, you could change the column datatype to be a native array, but that would only be a marginal improvement, as described in http://www.postgresql.org/docs/9.1/static/arrays.html under "Searching Arrays".
Unless you have an extraordinary situation, it's best to store relationships like this use the Rails association mechanism.

Select menu for type field and map integers to text in rails

[I'm new to rails, and I hope it's not a silly question, seen a similar question but it's for PHP and doesn't help in my case]
To explain my problem, I'm using a analogy to users here. Lets say I have users table in my app, I have added a field called user_type to users table. Now I want to specify which type of user is.
lets say I have 5 types of users eg. moderator, administrator, consumer etc.
I don't want to make user_type field to be string type to store user type. Instead I want to make user_type to store integer and then map these integer values to respective string values.
Advantage to this approach is that I can change what a user type is called. Suppose that I no longer wish to call consumer a consumer and instead wish to call it something else.
I believe storing integer in db is better and gives some flexibility.
I know I can create select menu using formtastic(I'm using active_admin as admin panel, formtastic is used for forms)
<%= f.input :user_type, :as => :select, :collection => {
0 => "Admin",
1 => "Moderator",
2 => "Consumer",
} %>
and then store values in db, and then select these users from db.
I want to know Is there a better way or approach to do it in rails or there is some gem available to do this or some other approach you prefer and why you recommend it.
I'm using postgresql as database.
Thanks!!
I personally like the active_enum gem combined to simple_form because it's really simple to implement and they work fine together.
In your case, you would have to define an enum class like this :
class Type < ActiveEnum::Base
value 1 => 'Admin'
value 2 => 'Moderator'
value 3 => 'Consumer'
end
Then in your User model, you simply add this :
enumerate :user_type, :with => Type
And what is really great with simple_form is that you simply have to call :
<%= f.input :user_type =>
to get a select with all your values.
Try this
# user.rb
USER_TYPES = { moderator: 1, superuser: 2, admin: 3, client: 4 }
# views
select :user, :user_type, User::USER_TYPES
This saves the integer values to the database. If you want to get the the string equivalent, use User::USER_TYPES.key(#user.user_type)
EDIT: forgot to add scopes
scope :moderators, where(user_type: USER_TYPES[:moderator])
scope :superusers, where(user_type: USER_TYPES[:superuser])
...
or
USER_TYPES.each do |user_type, value|
scope :"#{user_type}s", where(user_type: USER_TYPES[user_type])
end

Indexing fields + custom text in with Thinking Sphinx

I've got indexes on a few different models, and sometimes the user might search for a value which exists in multiple models. Now, if the user is really only interested in data from one of the models I'd like the user to be able to pre/postfix the query with something to limit the scope.
For instance, if I only want to find a match in my Municipality model, I've set up an index in that model so that the user now can query "xyz municipality" (in quotes):
define_index do
indexes :name, :sortable => true
indexes "name || ' municipality' name", :as => :extended_name, :type => :string
end
This works just fine. Now I also have a Person model, with a relation to Municipality. I'd like, when searching only on the Person model, to have the same functionality available, so that I can say Person.search("xyz municipality") and get all people connected to that municipality. This is my current definition in the Person model:
has_many :municipalities, :through => :people_municipalities
define_index do
indexes [lastname, firstname], :as => :name, :sortable => true
indexes municipalities.name, :as => :municipality_name, :sortable => true
end
But is there any way I can create an index on this model, referencing municipalities, like the one I have on the Municipality model itself?
If you look at the generated SQL in the sql_query setting of config/development.sphinx.conf for source person_core_0, you'll see how municipalities.name is being concatenated together (I'd post an example, but it depends on your database - MySQL and PostgreSQL handle this completely differently).
I would recommend duplicating the field, and insert something like this (SQL is pseudo-code):
indexes "GROUP_CONCAT(' municipality ' + municipalities.name)",
:as => :extended_municipality_names
Also: there's not much point adding :sortable true to either this nor the original field from the association - are you going to sort by all of the municipality names concat'd together? I'm guessing not :)

How to enforce unique embedded document in mongoid

I have the following model
class Person
include Mongoid::Document
embeds_many :tasks
end
class Task
include Mongoid::Document
embedded_in :commit, :inverse_of => :tasks
field :name
end
How can I ensure the following?
person.tasks.create :name => "create facebook killer"
person.tasks.create :name => "create facebook killer"
person.tasks.count == 1
different_person.tasks.create :name => "create facebook killer"
person.tasks.count == 1
different_person.tasks.count == 1
i.e. task names are unique within a particular person
Having checked out the docs on indexes I thought the following might work:
class Person
include Mongoid::Document
embeds_many :tasks
index [
["tasks.name", Mongo::ASCENDING],
["_id", Mongo::ASCENDING]
], :unique => true
end
but
person.tasks.create :name => "create facebook killer"
person.tasks.create :name => "create facebook killer"
still produces a duplicate.
The index config shown above in Person would translate into for mongodb
db.things.ensureIndex({firstname : 1, 'tasks.name' : 1}, {unique : true})
Can't you just put a validator on the Task?
validates :name, :uniqueness => true
That should ensure uniqueness within parent document.
Indexes are not unique by default. If you look at the Mongo Docs on this, uniqueness is an extra flag.
I don't know the exact Mongoid translation, but you're looking for something like this:
db.things.ensureIndex({firstname : 1}, {unique : true, dropDups : true})
I don't believe this is possible with embedded documents. I ran into the same issue as you and the only workaround I found was to use a referenced document, instead of an embedded document and then create a compound index on the referenced document.
Obviously, a uniqueness validation isn't enough as it doesn't guard against race conditions. Another problem I faced with unique indexes was that mongoid's default behavior is to not raise any errors if validation passes and the database refuses to accept the document. I had to change the following configuration option in mongoid.yml:
persist_in_safe_mode: true
This is documented at http://mongoid.org/docs/installation/configuration.html
Finally, after making this change, the save/create methods will start throwing an error if the database refuses to store the document. So, you'll need something like this to be able to tell users about what happened:
alias_method :explosive_save, :save
def save
begin
explosive_save
rescue Exception => e
logger.warn("Unable to save record: #{self.to_yaml}. Error: #{e}")
errors[:base] << "Please correct the errors in your form"
false
end
end
Even this isn't really a great option because you're left guessing as to which fields really caused the error (and why). A better solution would be to look inside MongoidError and create a proper error message accordingly. The above suited my application, so I didn't go that far.
Add a validation check, comparing the count of array of embedded tasks' IDs, with the count of another array with unique IDs from the same.
validates_each :tasks do |record, attr, tasks|
ids = tasks.map { |t| t._id }
record.errors.add :tasks, "Cannot have the same task more than once." unless ids.count == ids.uniq.count
end
Worked for me.
You can define a validates_uniqueness_of on your Task model to ensure this, according to the Mongoid documentation at http://mongoid.org/docs/validation.html this validation applies to the scope of the parent document and should do what you want.
Your index technique should work too, but you have to generate the indexes before they brought into effect. With Rails you can do this with a rake task (in the current version of Mongoid its called db:mongoid:create_indexes). Note that you won't get errors when saving something that violates the index constraint because Mongoid (see http://mongoid.org/docs/persistence/safe_mode.html for more information).
You can also specify the index in your model class:
index({ 'firstname' => 1, 'tasks.name' => 1}, {unique : true, drop_dups: true })
and use the rake task
rake db:mongoid:create_indexes
you have to run :
db.things.ensureIndex({firstname : 1, 'tasks.name' : 1}, {unique : true})
directly on the database
You appear to including a "create index command" inside of your "active record"(i.e. class Person)

Searching multiple columns with Sphinx

I have model Products with columns:
name, number, description
Index defined for this model looks like this:
define_index do
indexes :name, :sortable => true
indexes :number
indexes :description
where "amount > 0"
has :price
end
Since in description can be lots of random words I want to exclude it from searching sometimes (when user clicks chceckbox 'don't search in descriptions').
I went to the sphinx page and found following:
#(name, number) *pencil* *123*
And it seems like I don't understand how sphinx works. When I execute search
*pencil* *123*
word 'pencil' is found in name and '123' is found in number and I get 1 result. But when I execute
#(name, number) *pencil* *123*
no results are found.
Is searching by columns somehow different?
You can only search on fields when using the :extended match mode - Thinking Sphinx sets this automatically if you use :conditions - but you're constructing a multi-field query yourself, hence why this isn't happening. Try this:
Product.search "#(name, number) *pencil* *123*", :match_mode => :extended
Hope this helps.
It was all about spaces :/
This works:
#(name,number) *pencil* *123*

Resources