What's the best way to search by date in Rails? - ruby-on-rails

HI
whats the best way to implement a simple search in rails to retrieve all models with the same date?
e.g retrieve all customer with birthday date on 23-10-1975, which will be of a date format.
i.e
create_table "customer", :force => true do |t|
t.string "f_name"
t.string "m_name"
t.string "l_name"
t.date "date_of_birth"
end

I presume you want to congratulate all users that have their birthday today?
Well, I'm not on a dev-machine now, so I can't test it, but I would go for something like this:
#bday_customers = Customer.find(:all, :conditions => { :birthday => Date.today } )
or even
#bday_customers = Customer.find(:all, :conditions => { :birthday => (Date.today)..(Date.today + 2.weeks) } )
This will cause your database to do the work as it its optimised for such searches.

Assuming date is a string of the form 23-10-1975 then something like this;
Customer.all(:conditions => { :date_of_birth => Date.strptime(date, '%d-%m-%Y') })

Related

Rails 5 - How to get records with OR condition instead of AND

I'm trying to get a complicated piece of data from rails.
What I want are all users, the tasks that are associated with them and limited to specific project.
Here's the task schema for reference:
create_table "tasks", force: :cascade do |t|
t.date "start_date"
t.date "end_date"
t.integer "hours"
t.string "priority"
t.integer "project_id"
t.integer "user_id"
t.string "name"
t.string "description"
end
I accomplish parts of this with this call
users = User.includes(:tasks).where('tasks.end_date Between ? AND ?', Date.today.beginning_of_week, Date.today.end_of_week).references(:tasks).where('tasks.start_date Between ? AND ?', Date.today.beginning_of_week, Date.today.end_of_week).references(:tasks).where('tasks.project_id = ?', #project.id).map { |user| user.as_json.merge({tasks: user.tasks.as_json}) }
My problem is that my query is not finding the tasks based on their dates correctly.
I am trying to find all tasks within a week range that either have a start_date or end_date within that week.
Is this possible within one query or do I require more advanced logic?
If you are using Rails 5 you can make user of or method
User.joins(:tasks).where(tasks: {end_date: Date.today.beginning_of_week..Date.today.end_of_week})
.or(
User.joins(:tasks).where(tasks: {start_date: Date.today.beginning_of_week..Date.today.end_of_week})
)
For sake of brevity, I haven't included project where clause.
I haven't tested this but I think what was happening was your where grabbing all of the users with tasks that end_date happens between given times and then querying those models with every user with tasks that start_date happens between given times. Giving you only users whose tasks start_date and end_date happen between the given times.
users = User.includes(:tasks).where('((tasks.end_date BETWEEN ? AND ?) OR (tasks.start_date BETWEEN ? AND ?)) AND tasks.project_id = ?', Date.today.beginning_of_week, Date.today.end_of_week, Date.today.beginning_of_week, Date.today.end_of_week, #project.id).references(:tasks).map { |user| user.as_json.merge({tasks: user.tasks.as_json}) }
Hope it helps. Cheers!
Here's what ended up working for me, thanks #vijay
User.includes(:tasks).where(tasks: {end_date:Date.today.beginning_of_week..Date.today.end_of_we‌​ek}).or(User.include‌​s(:tasks).where(task‌​s: {start_date: Date.today.beginning_of_week..Date.today.end_of_week})).wher‌​e('tasks.project_id = ?', #project.id).map { |user| user.as_json.merge({tasks: user.tasks.as_json}) }

ThinkingSphinx: dynamic indices on the SQL-backed indices?

I am trying to use ThinkingSphinx (with SQL-backed indices) in my Rails 5 project.
I need some dynamic run-time indices to search over.
I have a Message model:
class Message < ApplicationRecord
belongs_to :sender, class_name: 'User', :inverse_of => :messages
belongs_to :recipient, class_name: 'User', :inverse_of => :messages
end
and its indexer:
ThinkingSphinx::Index.define :message, :with => :active_record, :delta => true do
indexes text
indexes sender.email, :as => :sender_email, :sortable => true
indexes recipient.email, :as => :recipient_email, :sortable => true
indexes [sender.email, recipient.email], :as => :messager_email, :sortable => true
has sender_id, created_at, updated_at
has recipient_id
end
schema.rb:
create_table "messages", force: :cascade do |t|
t.integer "sender_id"
t.integer "recipient_id"
t.text "text"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "read", default: false
t.boolean "spam", default: false
t.boolean "delta", default: true, null: false
t.index ["recipient_id"], name: "index_messages_on_recipient_id", using: :btree
t.index ["sender_id"], name: "index_messages_on_sender_id", using: :btree
end
The problem is about so-called "dialogs". They don't exist in the database - they are determined at run-time. A dialog - that's a set of messages between 2 users, where each user may be either a sender or a receiver.
The task is to search through my dialogs and to find the dialog (dialog's messages) by the piece of the correspondent email. So complicated!
Here's my effort:
conditions = {messager_email: search_email}
with_current_user_dialogs =
"*, IF(sender_id = #{current_user.id} OR recipient_id = #{current_user.id}, 1, 0) AS current_user_dialogs"
messages = Message.search search_email, conditions: conditions,
select: with_current_user_dialogs,
with: {'current_user_dialogs' => 1}
This is almost fine - but still not. This query correctly searches only within my dialog (within the messages I sent or received) and only within :sender and :recipient fields simultaneously (which is not best).
Say my email is "client1#example.com". Other emails are like "client2#example.com", "client3#example.com", "manager1#example.com".
The trouble is that when I search for "client1" - I get all the messages where I was either a sender or a receiver. But I should get nothing in response - I need to search only across my correspondents emails - not mine.
Even worse stuff happens also while querying "client" - I get back the correct correspondents with "client2#example.com", "client3#example.com" - but the result is spoiled with wrong "client1#example.com".
I need a way to choose at run-time - which index subset to search within.
I mean this condition is not enough for me:
indexes [sender.email, recipient.email], :as => :messager_email, :sortable => true
It searches (for "client") within all the sender.email and all the recipient.email at once.
But I need to dynamically choose like: "search only within sender.email values conforming to if sender.id != current_user.id" OR "search only within recipient.email conforming to if recipient.id != current_user.id" (because I can be as a sender as a receiver).
That's what I call a "dynamic index".
How to do that? Such "dynamic index" surely would depend on the current current_user value - so it will be different for the different users - even on the same total messages set.
It is clear that I can't apply whatever post-search cut-offs (what to cut off?) - I need to somehow limitate the search itself.
I tried to search over some scope - but got the error that "searching is impossible over scopes" - something like that.
Maybe I should use the real-time indexing instead of the SQL-backed indexing?
Sorry for the complexity of my question.
Would the following work?
other = User.find_by :email => search_email
with_current_user_dialogs = "*, IF((sender_id = #{current_user.id} AND recipient_id = #{other.id}) OR (recipient_id = #{current_user.id} AND sender_id = #{other.id}), 1, 0) AS current_user_dialogs"
Or do you need partial matches on the searched email address?
[EDIT]
Okay, from the discussion in the comments below, it's clear that the field data is critical. While you can construct a search query that uses both fields and attributes, you can't have logic in the query that combines the two. That is, you can't say: "Search field 'x' when attribute 'i' is 1, otherwise search field 'y'."
The only way I can possibly see this working is if you're using fields for both parts of the logic. Perhaps something like the following:
current_user_email = "\"" + current_user.email + "\""
Message.search(
"(#sender_email #{current_user_email} #recipient_email #{search_email}) | (#sender_email #{search_email} #recipient_email #{current_user_email})"
)

How to show entries from the current month?

For the sake of explanation, I'm writing an app where a User can log their expenses.
In the User's show view, I want to only show the User's expenses from the current month.
My expenses table looks like this:
create_table "expenses", force: :cascade do |t|
t.date "date"
t.string "name"
t.integer "cost"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "user_id"
end
The date field is in the date format, so looks like: Thu, 14 Apr 2016
In my controller, I've got something like:
def show
month = Date.today.strftime("%m")
#user = User.find(params[:id])
#expenses = Expense.where(:user_id => #user.id, :date => month)
end
Obviously, this isn't going to work, but it will be something along these lines, I'm guessing?
Any help would be great, thanks!
Usually you can tackle it this way:
Expense.where(date: (Date.today.beginning_of_month..Date.today.end_of_month))
Where that defines a range that can be used as a BETWEEN x AND y clause.
If this is a common operation you might want to express the date as a separate column in YYYYMM format so that these are easily retrieved.
If you're using MySQL, you can use the extract function, to create a .where like:
def show
month = Date.today.strftime("%m")
year = Date.today.strftime("%y")
#user = User.find(params[:id])
#expenses = Expence.where('extract(month from `date`) = ? AND extract(year from `date`) = ? AND `user_id` = ?', month, year, #user.id)
end
Havent tested, although it should work.
Sources:
https://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html

sum hash values inside db column and place result in another

I have a table called google_records in which every row is a snapshot of a google adwords account for that day.
Here is the schema -
create_table "google_records", :force => true do |t|
t.string "user_id"
t.string "date"
t.text "stats"
t.text "account_name"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.decimal "total_cost", :precision => 12, :scale => 2, :default => 0.0, :null => false
t.integer "total_conversions", :default => 0, :null => false
end
add_index "google_records", ["date"], :name => "index_google_records_on_date"
stats contains a hash of the day's stats by campaign and an entry would look something like this:
:Carpet Cleaning:
:conversions: 3
:cost: 391.47
:Upholstery Cleaning:
:conversions: 0
:cost: 69.96
:Air Duct Cleaning:
:conversions: 0
:cost: 8.68
I just added those total_cost and total_conversion columns and I'd like to update those with the totals for each respective value each day.
I can get the total I want to work in my console like so(the cost total doesn't match up with the sample I gave but its only because I shortened the sample to fit better - the total is correct) -
user = User.find(6)
GoogleRecord.where(user_id: user).where(date: "20140328").map {|m| m.stats}.map{ |s| s.map{ |key, value| value[:cost] || 0 } }.map {|m| m.inject(:+)}.compact.reduce(&:+)
=> 660.26
I'd like to update all records in the table this way for conversions and cost and save them but when I try something like:
GoogleRecord.all.each do |g|
g.map {|m| m.stats}.map{ |s| s.map{ |key, value| value[:cost] || 0 } }.map {|m| m.inject(:+)}.compact.reduce(&:+)
end
I keep getting NoMethodError: undefined method map for GoogleRecord:0x5f09868
It seems like since I'm just grabbing one record in the example that is working, I should be able to apply code to each record.
Inside your example code, what the where method return is something like array, so map can work. But inside the GoogleRecord.all.each block, the g is reference to the db record. Remove the first map call, and see whether it will work.
GoogleRecord.all.each do |g|
g.stats.map{ |s| s.map{ |key, value| value[:cost] || 0 } }.map {|m| m.inject(:+)}.compact.reduce(&:+)
end

Additional Attributes Appearing in Rails Request Params

I've been seeing some functionality in my rails app whereby the parameters I pass through to rails are being encapsulated somewhere between the request and params in the controller.
It hasn't mattered before but I recently had an issue around this and so decided to try and find out why it was happening. I haven't had any luck and am hoping someone here can point me in the right direction.
My client request payload looks like this:
{
"id"=>"1",
"email" => "peter.hamilton10#imperial.ac.uk",
"first_name" => "Peter",
"last_name" => "Hamilton",
"year" => 3,
"private_attr" => "something"
}
And in rails, the logs show this
Started PUT "/students/1" for 127.0.0.1 at 2012-11-29 13:20:56 +0000
Processing by StudentsController#update as JSON
Parameters: {"id"=>"1",
"email" => "peter.hamilton10#imperial.ac.uk",
"first_name" => "Peter",
"last_name" => "Hamilton",
"year" => 3,
"private_attr" => "something",
"student" => {
"email"=>"peter.hamilton10#imperial.ac.uk",
"first_name"=>"Peter",
"last_name" => "Hamilton",
"year"=>3
}
}
Where is this student attribute coming from and how are it's fields generated?
I know attributes which can't be mass assigned don't appear (illustrated above by private_attr) so I assume its extracting fields for the model for the current controller but otherwise I'm slightly at a loss...
UPDATE: Model
# Schema
# create_table "students" do |t|
# t.string "email"
# t.string "first_name"
# t.string "last_name"
# t.integer "year"
# t.integer "private_attr"
# end
class Student < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name, :year
end
(NOTE: I don't expect private_attr to come through, I just did it as a test and thought it might be useful)
The request isn't actually coming from a form, it's coming from an ajax request generated manually.
Have a look at wrapped parameters:
http://edgeapi.rubyonrails.org/classes/ActionController/ParamsWrapper.html
I don't know if you're using Rails 4 or not but this is the latest version of what is happening under the hood.
In controller add following code:
wrap_parameters :model, include: Model.column_names + [:attr]
Model - your model, :attr - additional attribute

Resources