Named scope not cooperating with timezone? - ruby-on-rails

A really dodgy problem I've got. Here's my model:
class Entry < ActiveRecord::Base
default_scope :order => 'published_at DESC'
named_scope :published, :conditions => ["published_at < ?", Time.zone.now], :order => 'published_at DESC'
belongs_to :blog
end
Now if I do
#entries = Entry.published.paginate_by_blog_id #blog.id,
:page => params[:page],
:order => 'published_at DESC',
It does not return posts unless i move published_at back one hour. BUT:
#entries = Entry.paginate_by_blog_id #blog.id,
:page => params[:page],
:conditions => ["published_at < ?", Time.zone.now],
:order => 'published_at DESC',
And it works fine!
I'm going nuts here, anyone has any ideas of where to even start debugging?

named scopes are not run dynamically, so the Time.zone.now is the value at class load time. If you want the named scope to use a different value with each call, then the conditions need to be the result of a lambda.
Take a look at http://railscasts.com/episodes/108-named-scope and http://ryandaigle.com/articles/2008/3/24/what-s-new-in-edge-rails-has-finder-functionality
For example:
named_scope :recent, lambda { { :conditions => ['created_at > ?', 1.week.ago] } }
This way 1.week.ago is calculated every time the scope is invoked.

Related

Ruby on Rails logic in active record condition

I'm building a site for users to post events they wish to sell tickets for.
I'm writing a query where the conditions are the follow:
active equals true
sales_stop is < Time.now
The problem I am having is coming up with a condition which tests whether or not a record's sales_stop time is less than Time.now.
Below is what I have as of now:
#events = Event.paginate :page => params[:page],
:conditions => {:active => true},
:order => "created_at DESC"
In turn, I've been toying around with the sales_stop condition with no luck.
I've been trying something like this:
#events = Event.paginate :page => params[:page],
:conditions => {:active => true, :sales_stop < Time.now},
:order => "created_at DESC"
This of course doesn't work.
Does anyone know how I can set this query up so that I only retrieve records where the sales_stop attribute is less than Time.now?
Thank you.
Use the alternate syntax for :conditions, which uses a bind-style:
#events = Event.paginate :page => params[:page],
:conditions => ['active = ? AND sales_stop < ?', true, Time.now],
:order => "created_at DESC"
This should work:
#events = Event.paginate :page => params[:page],
:conditions => ['active=? AND sales_stop < ?', true, Time.now],
:order => "created_at DESC"
Just a different syntax.

How to make Ruby Modules Dynamic?

I have four models, User, Profile, Badge, and Membership,
that have almost identical previous and next methods. The following example is from my User model.
def next
self.class.find :first,
:conditions => [ "created_at > ? AND user_id = ?",
self.created_at, self.user_id ],
:order => "created_at ASC"
end
def previous
self.class.find :first,
:conditions => [ "created_at < ? AND user_id = ?",
self.created_at, self.user_id ],
:order => "created_at DESC"
end
Instead of having the essentially same methods repeated four times once for each model, I'm attempting to put these methods into an external module Extensions::Utility so that each model can include Extensions::Utility.
What is the best way to implement this method so that it supports dynamic substitution of user for other models?
My environment is Ruby/Rails 3.0.6.
Tilo's answer has a point. I changed the method next to nekst.
module Extensions
module Utility
def id; "#{self.class.downcase}_id" end
def nekst
self.class.find :first,
:conditions => [ "created_at > ? AND #{id} = ?",
self.created_at, self.send(id) ],
:order => "created_at ASC"
end
def previous
self.class.find :first,
:conditions => [ "created_at < ? AND #{id} = ?",
self.created_at, self.send(id) ],
:order => "created_at DESC"
end
end
end
Be aware that "next" is a keyword in the Ruby language!!
I would recommend not to define any methods with names that are part of the Ruby language..

Calling all comments for certain posts

I am working with a Rails 2.3.9 application and in this case posts are workouts. I am trying to get all comments for the following workouts.
#workouts = Workout.today_only.all(:include => {:user => :memberships}, :conditions => ["workouts.public = '1' AND memberships.box_id = ?", #box.id], :order => "workouts.created_at DESC")
My associations are correct:
Workout has_many :comments
Comment belongs_to :workout
How would I get at list of comments on those specifc workouts ordered by created_at DESC?
Note: today_only is a named_scope and I can post the code if it is relevant.
In your Workout model, add:
has_many :comments, :order => 'created_at DESC'
then you can do:
Workout.today_only.all(:include => [:comments, {:user => :memberships}], ...

Rails: Using will_paginate with a complex association find

I'm encountering a problem with will_paginate while doing a complex find.
:photo has_many :tags, :through => :tagships
:item has_many :photos
:photo belongs_to :item
#photos = #item.photos.paginate :page => params[:page],
:per_page => 200,
:conditions => [ 'tags.id IN (?)', tag_ids],
:order => 'created_at DESC',
:joins => :tags,
:group => "photos.id HAVING COUNT(DISTINCT tags.id) = #{tag_count}"
I want to fetch all the photos who have all the tags in the tag_ids array. MySQL's IN usually does "or" searches, but I need "and". I found how to modify IN to mimic "and" behavior here and it works fine when using model.find(), also works as long as the number of records fetched is lower than my :per_page count. But if it has to paginated, the SQL that is generated is similar to:
SELECT count(*) AS count_all, photos.id HAVING COUNT(DISTINCT tags.id) = 1 AS photos_id_having_count_distinct_tags_id_1 FROM `photos`(...)
which doesn't work. Other have seen this bug and were able to move their count() out of the query, but I don't think that's possible in my case.
Is there a better way to do this search that might work with will_paginate? If its the only way to do this, I guess I should look into another pagination plugin?
Thanks!
FYI, here's what I finally found to fix this:
#photos = WillPaginate::Collection.create(current_page, per_page) do |pager|
result = #item.photos.find :all, :conditions => [ 'tags.id IN (?)', tag_ids] ,:order => 'created_at DESC', :joins => :tags, :group => "photos.id HAVING COUNT(DISTINCT tags.id) = #{#tags.count}", :limit => pager.per_page, :offset => pager.offset
pager.replace(result)
unless pager.total_entries
pager.total_entries = #item.photos.find(:all, :conditions => [ 'tags.id IN (?)', tag_ids] ,:order => 'created_at DESC', :joins => :tags, :group => "photos.id HAVING COUNT(DISTINCT tags.id) = #{#tags.count}").count
end
end
You have to manually construct the paginated set using the page number as an offset and using the tags to make a join query. Kinda clunky.
My first stab at this (sorry don't have time to test it right now... will update if I do) would be something like the following (added the :select and changed the :group):
#photos = #item.photos.paginate :page => params[:page],
:per_page => 200,
:select => "photos.*, COUNT(DISTINCT tags.id) AS tag_count",
:conditions => [ 'tags.id IN (?)', tag_ids ],
:order => 'created_at DESC',
:joins => :tags,
:group => "photos.id HAVING tag_count = #{tag_count}"

Thinking Sphinx and acts_as_taggable_on plugin

I installed Sphinx and Thinking Sphinx for ruby on rails 2.3.2.
When I search without conditions search works ok. Now, what I'd like to do is filter by tags, so, as I'm using the acts_as_taggable_on plugin, my Announcement model looks like this:
class Announcement < ActiveRecord::Base
acts_as_taggable_on :tags,:category
define_index do
indexes title, :as => :title, :sortable => true
indexes description, :as => :description, :sortable => true
indexes tags.name, :as => :tags
indexes category.name, :as => :category
has category(:id), :as => :category_ids
has tags(:id), :as => :tag_ids
end
For some reason, when I run the following command, it will bring just one announcement, that has nothing to do with what I expect. I've got many announcements, so I expected a lot of results instead.
Announcement.search params[:announcement][:search].to_s, :with => {:tag_ids => 1}, :page => params[:page], :per_page => 10
I guess something is wrong, and it's not searching correctly.
Can anyone give my a clue of what's going on?
Thanks,
Brian
Thinking Sphinx relies on associations in model. In common situations you only have to put index definition below your associations.
With acts_as_taggable_on plug-in you don't have tag-related associations in model file and when you write
indexes tags.name, :as => :tags
TS interprets it like:
CAST(`announcements`.`name` AS CHAR) AS `tags`
(look at sql_query in development.sphinx.conf, in my case).
I suppose that you have attribute name in model Announcement and don't run into error when rebuild index.
But we expect:
CAST(GROUP_CONCAT(DISTINCT IFNULL(`tags`.`name`, '0') SEPARATOR ' ') AS CHAR) AS `tags`
and:
LEFT OUTER JOIN `taggings` ON (`announcements`.`id` = `taggings`.`taggable_id`)
LEFT OUTER JOIN `tags` ON (`tags`.`id` = `taggings`.`tag_id`) AND taggings.taggable_type = 'Announcement'
To get things working just add tag-related associations in your model before you rebuild index:
class Announcement < ActiveRecord::Base
acts_as_taggable_on :tags,:category
has_many :taggings, :as => :taggable, :dependent => :destroy, :include => :tag, :class_name => "ActsAsTaggableOn::Tagging",
:conditions => "taggings.taggable_type = 'Announcement'"
#for context-dependent tags:
has_many :category_tags, :through => :taggings, :source => :tag, :class_name => "ActsAsTaggableOn::Tag",
:conditions => "taggings.context = 'categories'"
In define_index method:
indexes category_tags(:name), :as => :tags
has category_tags(:id), :as => :tag_ids, :facet => true
In controller:
#announcement_facets = Announcement.facets params[:search], :with => {:tag_ids => [...]}
#announcements = #announcement_facets.for.paginate( :page => params[:page], :per_page => 10 )
I found that simply defining the index thus:
Class Thing < ActiveRecord::Base
acts_as_taggable
define_index do
..other indexing...
indexes taggings.tag.name, :as => :tags
end
end
worked fine.
One possibility is that you need to declare the type for tag_ids as :multi because TS can get confused (I just discovered this here http://groups.google.com/group/thinking-sphinx/browse_thread/thread/9bd4572398f35712/14d4c1503f5959a9?lnk=gst&q=yanowitz#14d4c1503f5959a9).
But why not use the tag names to search? E.g.,
Announcement.search params[:announcement][:search].to_s, :conditions => {:tags => "my_tag"}, :page => params[:page], :per_page => 10
Or, if you need to search for multiple tags:
Announcement.search( "#{params[:announcement][:search].to_s} (#tags my_tag | #tags your_tag)", :page => params[:page], :per_page => 10 )
(as aside, you may want to sanitize/remove sphinx-control-characters from the user-provided query before using it).
For debugging, I would go into console and strip down your query as much as possible (eliminate pagination arguments, even the query (just do ""), etc.).

Resources