Query based on calculated fields with Mongoid - ruby-on-rails

I want to implement a scope overdue in a model Invoice to return all invoices that exceeded the date, until they had to be paid. I have the fields invoice_date, :type => Date and days_for_payment, :type => Integer.
In my previous version, which was built on ActiveRecord, I could use the query
Invoice.where("invoice_date + days_for_payment < ?", Date.today)
This query made the calculation on the DB side.
Is there a way to get the same thing done with Mongoid? Or does anyone know a good workaround (proc, lambda, etc.)?
I use mongoid '2.4.12'

Found the answer myself. With the prefix this.* I can reference to the fields. And I can use JavaScript functions. MongoDB gets cooler and cooler!
So here is my solution:
class Invoice
include Mongoid::Document
field :invoice_date, :type => Date
field :days_for_payment, :type => Integer
...
scope :overdue, where("(Math.round(this.invoice_date.getTime() / 1000) + (this.days_for_payment * 24 * 3600)) < #{Time.now.to_i}")
...
end
Timestamp creation in js works different. So I had to get rid of the last three numbers and round them.
If anybody knows a more elegant way, please let me know.
My only problem left is, that I can't store a Date object to MongoDB. It always tells me I have to use Time. I think I better upgrade mongoid to 3.0.1.

I am not sure about mongoid, if you are querying mongodb directly you can use the $where operator. It is not recommended as it doesn't use indexes. If you have another condition that filters the records to a small set then you can use the $where to further filter it.

Related

Is there a way to back a new Rails 5 attribute with two database columns

I'm looking at using the new Rails 5 attributes API for a custom data type, ideally storing the data in two database columns, one for the data value and one for some extra type information.
The Attributes API seems to be designed to work with just one database column and I'm wondering if I'm missing a way to use two columns.
Example
Imagine a Money object, with one decimal or integer column for value and one string column for currency code. I'd pass in my custom money object, store it two columns, and then reading it back would combine the two columns into a Money object.
I've considered serializing the value and currency into a single Postgres JSON column, but I want to be able to do fast SQL SUM queries and sorting on just the value columns, so this doesn't seem ideal.
Thanks in advance for any insight.
I guess you're thinking about creating a ValueObject within your model.
There is ActiveRecord::Aggregations for that. Example:
class Customer < ActiveRecord::Base
composed_of :balance, class_name: "Money", mapping: %w(balance amount)
end
class Money
include Comparable
attr_reader :amount, :currency
EXCHANGE_RATES = { "USD_TO_DKK" => 6 }
def initialize(amount, currency = "USD")
#amount, #currency = amount, currency
end
def exchange_to(other_currency)
exchanged_amount = (amount * EXCHANGE_RATES["#{currency}_TO_#{other_currency}"]).floor
Money.new(exchanged_amount, other_currency)
end
def ==(other_money)
amount == other_money.amount && currency == other_money.currency
end
def <=>(other_money)
if currency == other_money.currency
amount <=> other_money.amount
else
amount <=> other_money.exchange_to(currency).amount
end
end
end
Can't answer your question directly unfortunately, but your example got me thinking. the money-rails gem allows use of a separate currency column. Perhaps it would be worth it to dig thru that gem to see what they are doing behind the scenes.

Datamapper: Sorting results through association

I'm working on a Rails 3.2 app that uses Datamapper as its ORM. I'm looking for a way to sort a result set by an attribute of the associated model. Specifically I have the following models:
class Vehicle
include DataMapper::Resource
belongs_to :user
end
class User
include DataMapper::Resource
has n, :vehicles
end
Now I want to be able to query the vehicles and sort them by the name of the driver. I tried the following but neither seems to work with Datamapper:
> Vehicle.all( :order => 'users.name' )
ArgumentError: +options[:order]+ entry "users.name" does not map to a property in Vehicle
> Vehicle.all( :order => { :users => 'name' } )
ArgumentError: +options[:order]+ entry [:users, "name"] of an unsupported object Array
Right now I'm using Ruby to sort the result set post-query but obviously that's not helping performance any, also it stops me from further chaining on other scopes.
I spent some more time digging around and finally turned up an old blog which has a solution to this problem. It involves manually building the ordering query in DataMapper.
From: http://rhnh.net/2010/12/01/ordering-by-a-field-in-a-join-model-with-datamapper
def self.ordered_by_vehicle_name direction = :asc
order = DataMapper::Query::Direction.new(vehicle.name, direction)
query = all.query
query.instance_variable_set("#order", [order])
query.instance_variable_set("#links", [relationships['vehicle'].inverse])
all(query)
end
This will let you order by association and still chain on other scopes, e.g.:
User.ordered_by_vehicle_name(:desc).all( :name => 'foo' )
It's a bit hacky but it does what I wanted it to do at least ;)
Note: I'm not familiar with DataMapper and my answer might not be within the standards and recommendations of using DataMapper, but it should hopefully give you the result you're looking for.
I've been looking through various Google searches and the DataMapper documentation and I haven't found a way to "order by assocation attribute". The only solution I have thought of is "raw" SQL.
The query would look like this.
SELECT vehicles.* FROM vehicles
LEFT JOIN users ON vehicles.user_id = users.id
ORDER BY users.name
Unfortunately, from my understanding, when you directly query the database you won't get the Vehicle object, but the data from the database.
From the documentation: http://datamapper.org/docs/find.html. It's near the bottom titled "Talking directly to your data-store"
Note that this will not return Zoo objects, rather the raw data straight from the database
Vehicle.joins(:user).order('users.name').all
or in Rails 2.3,
Vehicle.all(:joins => "inner join users on vehicles.user_id = user.id", :order => 'users.name')

Rails 3 - how to add to autocomplete another condition?

I use the gem rails3-jquery-autocomplete for the autocomplete searching of items in my database. The autocomplete is working fine, but I would need to add another condition to the generated query.
If I start to write a searched string, then is generated following query:
SELECT persons.id, persons.name FROM "persons" WHERE (LOWER(persons.name) ILIKE 'jo%') ORDER BY persons.name ASC LIMIT 10
This returns me all rows, where the name starts at jo.
But how could I search all persons, which name starts at jo and simultaneously, for example, the column active_person=1?
Is there any helper for this purpose or something like that?
Thank you
If I understood the context correctly, then active_person is a boolean that indicates whether the user is active or not.
If this behavior is desirable most of time - that you search for users/people that are only active then you could include a default_scope in your Person model like this:
class Person < ActiveRecord::Base
default_scope where(:active_person => 1)
end
This way any query that is ever generated will be also checking that active_person = 1
More on default scopes here: http://apidock.com/rails/ActiveRecord/Base/default_scope/class
I understand that #Andrei 's answer is correct, but its not what I was looking for. I guess its same for #user984621 too. What I was looking for is I want to add an extra condition to the query.
By reading this https://github.com/crowdint/rails3-jquery-autocomplete/blob/master/lib/rails3-jquery-autocomplete/orm/active_record.rb
I found that rails autocomplete supports additional conditions, you just have to pass another parameter to the default usage, like this
autocomplete :user, :name, :where => { :role => 'employee'}
instead of this
autocomplete :user, :name
Change WHERE (LOWER(persons.name) ILIKE 'jo%') to WHERE (LOWER(persons.name) ILIKE 'jo%') AND active_person = 1
You can chain together loads of clauses with ANDs and OR in that way.
Note that this is really a question about SQL: your title and tags are a bit misleading.

How do I get the search to use the attr_accessor?

Ok so i have a date field that i need to search on, but i need to search on it by day like in a mysql query
search_conditions << ["DAY(open_date) != ?", event.thursday.day] if options[:thur].blank?
and i need to do this condition with Thinking Sphinx so i tried this
attr_accessor :event_day
def event_day
self.start_date.day
end
#thinking sphinx configurations for the event search
define_index do
indexes event_day
...
...
and in the search i tried this
search_string = "#event_day -#{event.thursday.day}" unless options[:thur].blank?
but i keep getting this error
index event_core: query error: no field 'event_day' found in schema
Any way to make this work
You can't use a ruby attribute in an SQL query. Rails isn't that clever.
You need to write SQL that replicates that function, or filter the results of a query through it, e.g.
#my_query.where(:a => "b").select { |rec| rec.some_method == "some value" }
As Michael's pointed out, Ruby attributes aren't accessible by Sphinx - it talks directly to your database.
So, either you can create a column that holds the event day value, and reference that via Sphinx, or you can create a field that uses a SQL function (which could vary, depending on MySQL or PostgreSQL) that extracts the day from the start_date column - not particularly complex. It'd probably end up looking like this:
indexes "GET_DAY_FROM_DATE(start_date)", :as => :event_day

Filtering Sphinx search results by date range

I have Widget.title, Widget.publish_ at, and Widget.unpublish_ at. It's a rails app with thinking_sphinx running, indexing once a night. I want to find all Widgets that have 'foo' in the title, and are published (publish _at < Time.now, unpublish _at > Time.now).
To get pagination to work properly, I really want to do this in a sphinx query. I have
'has :publish_at, :unpublish_at' to get the attributes, but what's the syntax for 'Widget.search("foo #publish_ at > #{Time.now}",:match _mode=>:extended'? Is this even possible?
Yep, easily possible, just make sure you're covering the times in your indexes:
class Widget < ActiveRecord::Base
define_index do
indexes title
has publish_at
has unpublish_at
...
end
To pull it based purely off the dates, a small amount of trickery is required due to sphinx requiring a bounded range (x..y as opposed to x>=y). The use of min/max value is very inelegant, but I'm not aware of a good way around it at the moment.
min_time = Time.now.advance(:years => -10)
max_time = Time.now.advance(:years => 10)
title = "foo"
Widget.search title, :with => {:publish_at => min_time..Time.now, :unpublish_at => Time.now..max_time}
I haven't used sphinx with rails yet.
But this is possible by the Sphinx API.
What you need to do is to set a datetime attribute at your sphinx.conf.
And don't forget to use UNIX_TIMESTAMP(publish_at), UNIX_TIMESTAMP(unpublish_at) at your index select.

Resources