Currently, in my controller I am assigning values to a variable like so:
#tasks = #list.tasks.where(:importance => 'high', :active => true).search(params[:search])
What I'm wondering is if there is a way with .where or any other way to use a model method for selection selection. I have a model method that returns a string representing a relative due date:
def due
if self.due_date < Date.today + 2.days
return 'now'
elsif self.due_date < Date.today + 1.week
return 'soon'
else
return 'later'
end
end
I don't want to store this .due value in the database, as it'll be changing constantly based off what the date is. But what I, in theory, would want to do is something like this in my controller for selection:
#tasks = #list.tasks.where(:importance => 'high', :active => true, :due => 'now).search(params[:search])
But this will not work as .due is not in the database. Should I be doing this with scopes? Anyway, appreciate the help.
You could do scopes:
scope :due_now, lambda { where("due_date < ?", Date.today + 2.days) }
scope :due_soon, lambda { where("due_date < ?", Date.today + 1.week) }
scope :due_later, lambda { where("due_date >= ?", Date.today + 1.week) }
Alternatively, you could just use your class method like so:
#tasks = #list.tasks.where(:importance => 'high', :active => true).select{ |task| task.due == 'now' }.search(params[:search])
Related
It appears as if filterrific does not take content in translation tables into account (Globalize).
Is there anyway to search translation tables as well? My setup works perfectly well if the content is in the actual model. However, once the fields are empty and only entered in the translation table no results are being displayed (obviously).
My Model:
class Manual < ApplicationRecord
translates :title, :content, :teaser, :slug
extend FriendlyId
friendly_id :title, :use => :globalize
belongs_to :user
belongs_to :support_category
has_many :manual_faqs
has_many :faqs, :through => :manual_faqs
validates :title, presence: true
validates :content, presence: true
validates :user_id, presence: true
update_index('manuals#manual') { self }
filterrific(
default_filter_params: { sorted_by: 'created_at_desc' },
available_filters: [
:sorted_by,
:search_query,
:with_user_id,
:with_created_at_gte
]
)
scope :with_user_id, lambda { |user_ids|
where(user_id: [*user_ids])
}
scope :search_query, lambda { |query|
# Searches the students table on the 'first_name' and 'last_name' columns.
# Matches using LIKE, automatically appends '%' to each term.
# LIKE is case INsensitive with MySQL, however it is case
# sensitive with PostGreSQL. To make it work in both worlds,
# we downcase everything.
return nil if query.blank?
# condition query, parse into individual keywords
terms = query.downcase.split(/\s+/)
# replace "*" with "%" for wildcard searches,
# append '%', remove duplicate '%'s
terms = terms.map { |e|
('%' + e.gsub('*', '%') + '%').gsub(/%+/, '%')
}
# configure number of OR conditions for provision
# of interpolation arguments. Adjust this if you
# change the number of OR conditions.
num_or_conds = 2
where(
terms.map { |term|
"(LOWER(manuals.title) LIKE ? OR LOWER(manuals.content) LIKE ?)"
}.join(' AND '),
*terms.map { |e| [e] * num_or_conds }.flatten
)
}
scope :sorted_by, lambda { |sort_option|
# extract the sort direction from the param value.
direction = (sort_option =~ /desc$/) ? 'desc' : 'asc'
case sort_option.to_s
when /^created_at_/
# Simple sort on the created_at column.
# Make sure to include the table name to avoid ambiguous column names.
# Joining on other tables is quite common in Filterrific, and almost
# every ActiveRecord table has a 'created_at' column.
order("manuals.created_at #{ direction }")
else
raise(ArgumentError, "Invalid sort option: #{ sort_option.inspect }")
end
}
scope :created_at_gte, lambda { |reference_time|
where('manuals.created_at >= ?', reference_time)
}
def self.options_for_sorted_by
[
['Date received (newest first)', 'created_at_desc'],
['Date received (oldest first)', 'created_at_asc']
]
end
end
My Controller:
def index
#filterrific = initialize_filterrific(
Manual,
params[:filterrific],
select_options: {
sorted_by: Manual.options_for_sorted_by,
with_user_id: User.options_for_select
}
) or return
#manuals = #filterrific.find.page(params[:page])
respond_to do |format|
format.html
format.js
end
rescue ActiveRecord::RecordNotFound => e
# There is an issue with the persisted param_set. Reset it.
puts "Had to reset filterrific params: #{ e.message }"
redirect_to(reset_filterrific_url(format: :html)) and return
#respond_with(#references)
end
I don't know filterrific at all but I do know Globalize, and since filterrific is based on AR scopes it should be simply a matter of joining the translation table to get results to show up.
Here's your search_query scope modified to join and search the joined translations table (without the comments for clarity):
scope :search_query, lambda { |query|
return nil if query.blank?
terms = query.downcase.split(/\s+/)
terms = terms.map { |e|
('%' + e.gsub('*', '%') + '%').gsub(/%+/, '%')
}
num_or_conds = 2
where(
('(LOWER(manual_translations.title) LIKE ? OR'\
' LOWER(manual_translations.content) LIKE ?)' * (terms.count)).join(' AND '),
*terms.map { |e| [e] * num_or_conds }.flatten
).with_translations
}
Notice I've only changed two things: (1) I've appended with_translations, a method described in this SO answer which joins the translations for the current locale, and (2) I've swapped the manuals table for the manual_translations table in the query.
So if you call this query in the English locale:
Manual.search_query("foo")
you get this SQL:
SELECT "manuals".* FROM "manuals"
INNER JOIN "manual_translations" ON "manual_translations"."manual_id" = "manuals"."id"
WHERE (LOWER(manual_translations.title) LIKE '%foo%' OR
LOWER(manual_translations.content) LIKE '%foo%')
AND "manual_translations"."locale" = 'en'"
Notice that with_translations is automatically tagging on that manual_translations.locale = 'en' so you filter out only results in your locale, which I assume is what you want.
Let me know if that works for you.
I have a method in my model
class Announcement < ActiveRecord::Base
def self.get_announcements
#announcements = Announcement.where("starts <= :start_date and ends >= :end_date and disabled = false",
{:start_date => "#{Date.today}", :end_date => "#{Date.today}"})
return #announcements
end
end
I am trying to write rspec for this method, as i am new to rspec cant proceed
describe ".get_announcements" do
before { #result = FactoryGirl.create(:announcement) }
it "return announcements" do
end
end
Please help
Solution for my question
describe ".get_announcements" do
before { #result = FactoryGirl.create(:announcement) }
it "return announcement" do
Announcement.get_announcements.should_not be_empty
end
end
describe ".get_announcements" do
let!(:announcements) { [FactoryGirl.create!(:announcement)] }
it "returns announcements" do
expect(Announcement.get_announcements).to eq announcements
end
end
Note the use of let! to immediately (not lazily) assign to announcements.
Does the class method really need to define an instance variable? If not, it could be refactored to:
class Announcement < ActiveRecord::Base
def self.get_announcements
Announcement.where("starts <= :start_date and ends >= :end_date and disabled = false",
{:start_date => "#{Date.today}", :end_date => "#{Date.today}"})
end
end
I have the following Scope in my Rails app, which is used to fetch active Choices from the database based on the current_user. This works just fine, but if there is no current_user the the code fetches alle the Choices in the database. Here I just want it to fetch nothing.
scope :active, lambda{|user| user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : {} }
How do I rewrite thee above to return nothing if there is no current_user?
The problem is that I'm using Pusher to push new data to the website, but if the user session expires then all data are pushed instead of nothing.. hopes this makes sense :)
As scopes return an ActiveRecord::Relation instance so it would be more correct to return empty ActiveRecord::Relation object like it's described here.
So, you have to add :none scope which does the trick:
scope :none, limit(0)
and then use it inside your scope like:
scope :active, ->(user = nil) { user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : none }
scope :active, lambda{|user| user ? { :conditions => ["deliverydate = ? and user_id = ?", Date.tomorrow, user.id], :order => 'id DESC'} : nil }
That is because the empty hash ({}) has no conditions, which basically means return all rows.
Based on the way your code is structured, you could make a condition that is something like :id => -1, :id => nil or 1=0 or something that is always false so it won't return any rows.
(And as was mentioned in the comment below your question, scopes should not return nil since it cannot be chained.)
I have to create a hash of the form h[:bill] => ["Billy", "NA", 20, "PROJ_A"] by login where 20 is the cumulative number of hours reported by the login for all task transactions returned by the query where each login has multiple reported transactions. Did I do this in a bad way or this seems alright.
h = Hash.new
Task.find_each(:include => [:user], :joins => :user, :conditions => ["from_date >= ? AND from_date <= ? AND category = ?", Date.today - 30, Date.today + 30, 'PROJ1']) do |t|
h[t.login.intern] = [t.user.name, 'NA', h[t.login.intern].nil? ? (t.hrs_per_day * t.num_days) : h[t.login.intern][2] + (t.hrs_day * t.workdays), t.category]
end
Also if I have to aggregate this data not just by login but login and category how do I accomplish this?
thanks,
ash
I would make this
h[:bill] => ["Billy", "NA", 20, "PROJ_A"]
a hash like so
{ :user => t.user.name, :your_key_name => 'NA', :cumulative_hours => 20, :category => 'PROJ_A' }
so the values are accessible with keys instead of element indexes which becomes a bit hard to see when you are not iterating through a array
To access the data by user and category you can do some thing like this
user_hash = {}
Task.find_each(:include => [:user], :joins => :user, :conditions => ["from_date >= ? AND from_date <= ? AND category = ?", Date.today - 30, Date.today + 30, 'PROJ1']) do |task|
user_hash[task.login.intern] ||= {}
user_hash[task.login.intern][task.category] = { :user => task.user.name, :your_key_name => 'NA', :cumulative_hours => cumulative_hours(user_hash, task), :category => task.category }
end
def cumulative_hours(user_hash, task)
if user_hash[task.login.intern] && user_hash[task.login.intern][task.category]
return user_hash[task.login.intern][task.category][:cumulative_hours] + (task.hrs_day * task.workdays)
else
return task.hrs_per_day * task.num_days
end
end
For readability reasons I have added meaningful variable names and also created a method to calculate cumulative_hours to keep the code clear, separate code concern and to follow Single Responsibility Principle.
I'm used to Django where you can run multiple filter methods on querysets, ie Item.all.filter(foo="bar").filter(something="else").
The however this is not so easy to do in Rails. Item.find(:all, :conditions => ["foo = :foo", { :foo = bar }]) returns an array meaning this will not work:
Item.find(:all, :conditions => ["foo = :foo", { :foo = 'bar' }]).find(:all, :conditions => ["something = :something", { :something = 'else' }])
So I figured the best way to "stack" filters is to modify the conditions array and then run the query.
So I came up with this function:
def combine(array1,array2)
conditions = []
conditions[0] = (array1[0]+" AND "+array2[0]).to_s
conditions[1] = {}
conditions[1].merge!(array1[1])
conditions[1].merge!(array2[1])
return conditions
end
Usage:
array1 = ["foo = :foo", { :foo = 'bar' }]
array2 = ["something = :something", { :something = 'else' }]
conditions = combine(array1,array2)
items = Item.find(:all, :conditions => conditions)
This has worked pretty well. However I want to be able to combine an arbitrary number of arrays, or basically shorthand for writing:
conditions = combine(combine(array1,array2),array3)
Can anyone help with this? Thanks in advance.
What you want are named scopes:
class Item < ActiveRecord::Base
named_scope :by_author, lambda {|author| {:conditions => {:author_id => author.id}}}
named_scope :since, lambda {|timestamp| {:conditions => {:created_at => (timestamp .. Time.now.utc)}}}
named_scope :archived, :conditions => "archived_at IS NOT NULL"
named_scope :active, :conditions => {:archived_at => nil}
end
In your controllers, use like this:
class ItemsController < ApplicationController
def index
#items = Item.by_author(current_user).since(2.weeks.ago)
#items = params[:archived] == "1" ? #items.archived : #items.active
end
end
The returned object is a proxy and the SQL query will not be run until you actually start doing something real with the collection, such as iterating (for display) or when you call Enumerable methods on the proxy.
I wouldn't do it like you proposed.
Since find return an array, you can use array methods to filter it, on example:
Item.find(:all).select {|i| i.foo == bar }.select {|i| i.whatever > 23 }...
You can also achive what you want with named scopes.
You can take a look at Searchlogic. It makes it easier to use conditions on
ActiveRecord sets, and even on Arrays.
Hope it helps.
You can (or at least used to be able to) filter like so in Rails:
find(:all, :conditions => { :foo => 'foo', :bar => 'bar' })
where :foo and :bar are field names in the active record. Seems like all you need to do is pass in a hash of :field_name => value pairs.