Rails 3 Sunspot Limit Objects to be indexed - ruby-on-rails-3

I am using Rails 3 sunspot solr.
I want to restrict Objects to be indexed.
e.g. Index Posts where ids ranges from 1..1000. After that it should not be index.

Finally I found my answer
searchable :if => :id_less_than_1000? do
...
end
def id_less_than_1000?
self.id < Post.maximum(:id)
end

Related

How can I query Rails ActiveRecord data stored in arrays

I have a rails model call MentorData and it has an attribute called os_usage. The oses are stored in an array like so ['apple', 'linux'].
To recap:
$ MentorData.first.os_usage
=> ['apple', 'linux']
I am looking to be able to query the data for all MentorData that includes the os_usage of apple, but when I search MentorData.where(os_usage: 'apple') I only get the mentors who can only use apple and not apple and linux. I need to search in some way that checks if apple is included in the array.
I have also tried the following.
MentorData.where('os_usage like ?', 'apple’)
MentorData.where('os_usage contains ?', 'apple’)
MentorData.where('os_usage contains #>ARRAY[?]', 'apple')
Is it possible to query data in ActiveRecord by attributes that have an array or items?
The database is on Postgres if that helps in providing a more raw search query.
Here are the examples given in the current Rails Edge Guides for PostgreSQL:
# db/migrate/20140207133952_create_books.rb
create_table :books do |t|
t.string 'title'
t.string 'tags', array: true
t.integer 'ratings', array: true
end
add_index :books, :tags, using: 'gin'
add_index :books, :ratings, using: 'gin'
# app/models/book.rb
class Book < ActiveRecord::Base
end
# Usage
Book.create title: "Brave New World",
tags: ["fantasy", "fiction"],
ratings: [4, 5]
## Books for a single tag
Book.where("'fantasy' = ANY (tags)")
## Books for multiple tags
Book.where("tags #> ARRAY[?]::varchar[]", ["fantasy", "fiction"])
## Books with 3 or more ratings
Book.where("array_length(ratings, 1) >= 3")
Have you tried MentorData.where("'apple' = ANY (os_usage)")?
Maybe you should detach the os_usage array from your model and make it a separate table.
In ActiveRecord world you will get something like the following code:
class MentorData < ActiveRecord::Base
..
has_and_belongs_to_many :os_usage
..
end
class OsUsage < ActiveRecord::Base
..
has_and_belongs_to_many :mentors_data
..
end
Creating a many_to_many relationship between this two models, allows you to query easily and avoid duplications. This technique is called normalization.
Using this new design you have your collection of os_usage made by objects instead of strings
MentorData.first.os_usage
# => [#<OsUsage:....>, #<OsUsage:...>]
Which you can convert easy into the old array of strings
MentorData.first.os_usage.map(&:name)
# => ['apple', 'linux']
In addition, you can query the data for all MentorData that includes the os_usage of apple:
MentorData.joins(:os_usages).where('os_usages.name' => 'apple')
And also query all the MentorData records for an OsUsage:
OsUsage.where(name: 'apple').mentors_data
I hope you find it useful :)
For like queries, you need %% to indicate that text can appear on the left or right of your search.
So, try
MentorData.where('os_usage LIKE "%apple%"')
and see if that works.
It is a wild card search, but omitting the % operates like =
See this question: SQL LIKE with no wildcards the same as '='?
This assumes os_usage is a serialized array, where the column backing that data is a string, and rails deserializes when instantiating your MentorData
Edit: I'd find out how your db is storing the array, so maybe you could do
"%'apple'%"
to make sure that it doesn't select oses with apple just contained in the name.

Limit the amount of results in associated queries with Rails 3.2

I have the following query:
#books = Books.includes(:author, :pages)
.find(:all,
:order => 'created_at DESC')
Let's assume my "Pages" table has fields "words, pictures". For blank pages, field "words" is NULL. There are many "Pages" records per book.
The problem with the above query, is that it retrieves ALL the pages for each book. I would like to retrieve only 1 page record for example with the condition "NOT NULL" on the "words" field. However, I don't want to exclude from the query results the Books that do not match the pages query (I have 10 books in my table and I want 10 books to be retrieved. The book.page association should be "nil" for the books where the condition does not match.)
I hope this makes sense.
Check this SO question:
Rails 3 - Eager loading with conditions
It looks like what you want
class Category
has_many :children, :class_name => "Category",
:foreign_key => "parent_id"
has_many :published_pages, :class_name => "Page",
:conditions => { :is_published => true }
end
If you only want a single blank page to be returned then you could add an association:
has_one :first_blank_page, -> {merge(Page.blanks).limit(1)}, :class_name => "Page"
... where in page.rb ...
def blanks
where(:words => nil)
end
Then you can:
#books = Books.includes(:author, :first_blank_page).order('created_at desc')
... and subsequently reading first_blank_page would be very efficient.
The limit will not be used if you eager load, though, as the SQL syntax for that sort of this would be very complex to execute as one query, so you'd want to consider whether you want to eager load all of the pages per book and then just use one per book. It's a tricky trade-off.

Rails: complex search on 3 models, return only newest - how to do this?

I'm trying to add an advanced search option to my app in which the user can search for certain links based on attributes from 3 different models.
My app is set up so that a User has_many :websites, Website has_many :links, and Link has_many :stats
I know how create SQL queries with joins or includes etc in Rails but I'm getting stuck since I only want to retrieve the latest stat for each link and not all of them - and I don't know the most efficient way to do this.
So for example, let's say a user has 2 websites, each with 10 links, and each link has 100 stats, that's 2,022 objects total, but I only want to search through 42 objects (only 1 stat per link).
Once I get only those 42 objects in a database query I can add .where("attribute like ?", user_input) and return the correct links.
Update
I've tried adding the following to my Link model:
has_many :stats, dependent: :destroy
has_many :one_stat, class_name: "Stat", order: "id ASC", limit: 1
But this doesn't seem to work, for example if I do:
#links = Link.includes(:one_stat).all
#links.each do |l|
puts l.one_stat.size
end
Instead of getting 1, 1, 1... I get the number of all the stats: 125, 40, 76....
Can I use the limit option to get the results I want or does it not work that way?
2nd Update
I've updated my code according to Erez's advice, but still not working properly:
has_one :latest_stat, class_name: "Stat", order: "id ASC"
#links = Link.includes(:latest_stat)
#links.each do |l|
puts l.latest_stat.indexed
end
=> true
=> true
=> true
=> false
=> true
=> true
=> true
Link.includes(:latest_stat).where("stats.indexed = ?", false).count
=> 6
Link.includes(:latest_stat).where("stats.indexed = ?", true).count
=> 7
It should return 1 and 6, but it's still checking all the stats rather than the latest only.
Sometimes, you gotta break through the AR abstraction and get your SQL on. Just a tiny bit.
Let's assume you have really simple relationships: Website has_many :links, and Link belongs_to :website and has_many :stats, and Stat belongs_to :link. No denormalization anywhere. Now, you want to build a query that finds, all of their links, and, for each link, the latest stat, but only for stats with some property (or it could be websites with some property or links with some property).
Untested, but something like:
Website
.includes(:links => :stats)
.where("stats.indexed" => true)
.where("stats.id = (select max(stats2.id)
from stats stats2 where stats2.link_id = links.id)")
That last bit subselects stats that are part of each link and finds the max id. It then filters out stats (from the join at the top) that don't match that max id. The query returns websites, which each have some number of links, and each link has just one stat in its stats collection.
Some extra info
I originally wrote this answer in terms of window functions, which turned out to be overkill, but I think I should cover it here anyway, since, well, fun. You'll note that the aggregate function trick we used above only works because we're determining which stat to use based on its ID, which exactly the property we need to filter the stats from the join by. But let's say you wanted only the first stat as ranked by some criteria other than ID, such as as, say, number_of_clicks; that trick won't work anymore because the aggregation loses track of the IDs. That's where window functions come in.
Again, totally untested:
Website
.includes(:links => :stats)
.where("stats.indexed" => true)
.where(
"(stats.id, 1) in (
select id, row_number()
over (partition by stats2.id order by stats2.number_of_clicks DESC)
from stat stats2 where stats2.link_id = links.id
)"
)
That last where subselects stats that match each link and order them by number_of_clicks ascending, then the in part matches it to a stat from the join. Note that window queries aren't portable to other database platforms. You could also use this technique to solve the original problem you posed (just swap stats2.id for stats2.number_of_clicks); it could conceivably perform better, and is advocated by this blog post.
I'd try this:
has_one :latest_stat, class_name: "Stat", order: "id ASC"
#links = Link.includes(:latest_stat)
#links.each do |l|
puts l.latest_stat
end
Note you can't print latest_stat.size since it is the stat object itself and not a relation.
Is this what you're looking for?
#user.websites.map { |site| site.links.map { |link| link.stats.last } }.flatten
For a given user, this will return an array with that contains the last stats for the links on that users website.

Searching issue with the rails-sunspot gem

I'm very new to Solr and the Rails Sunspot gem, but it looks very promising for complex searching on a big database.
What I'm trying to do is allow a model in my rails app to be searched on a few fulltext columns and then a collection of its "filters" (which are just a has_and_belongs_to_many association of names).
I tried setting up my model search block as follows
self.searchable do
text :name, :boost => 5
text :description, :instructions
text :filters do
filters.map(&:name)
end
end
And my controller looks like so:
#search = ModelName.search do
keywords params[:q].to_s
end
However, I cannot seem to produce any results based on keywords found in the filters association. Am I doing something wrong? This is all very new to me.
When you initially set up your classes for search, you need to reindex the data into Solr. Have you done that? If not:
rake sunspot:reindex

Updating attributes on several indexes in array. Using mongoid in Rails 3

Inside my test database, I would like to trigger a "new_item" flag for testing. The method already works in my tests. So now I am setting the created_at and published_at fields of all records to 1 month ago, then want to set a select few in an array to be published yesterday.
I use the following code to set all then 1 record: (In Rails 3.1.1)
yesterday = Time.now - 1.day
last_month = Time.now - 1.month
Item.update_all(:created_at => last_month, :published_at => last_month)
Item.visible[1].update_attributes(:created_at => yesterday, :published_at => yesterday)
Which works. However, how can I select multiple records in that array instead of just the [1] index. ie. [1,4,5,8,10] etc.
I believe update_attributes doesn't work on multiple records. And I'm not sure how to select multiple indexes in an existing array.
I hope this makes sense...
Thanks in advance,
Adam.
update_attributes is an instance method, i.e. it will work on an instance of the model. update_all is a criteria method(may be model class delegates to criteria) and can be chained to mongoid criteria. If you have some criteria for obtaining the indexes of documents on array you can do:
Item.visible.where(:some_other_field => "some_other_value").
update_all(:created_at => last_month, :published_at => last_month)
If you don't have a criteria, but are handpicking the items, you can do:
# instead of `model_array.update_attributes`
ids = model_array.map(&:id)
Item.all.for_ids(ids).update_all(:created_at => last_month, :published_at => last_month)