Just wondering if there is any function that allows to do that:
MyModel.find_by_conditon(method_a - method_b > 0)
MyModel.class
def method_a
next_service_date.to_i
end
def method_b
last_service_date.to_i
end
def next_service_date
service_date.present? ? calculated_time_estimation : row_time_estimation
end
def calculated_time_estimation
service_date + calculated_service_period
end
def calculated_service_period
case(service_frequency_period)
when 'Year'
service_frequency_number.to_i.year
when 'Month'
service_frequency_number.to_i.month
when 'Week'
service_frequency_number.to_i.day * 7
when 'Day'
service_frequency_number.to_i.day
end
end
service_frequency_number, service_frequency_period, service_date are attributes for MyModel
Assuming that method_a and method_b are actually attributes, you can do:
MyModel.where('method_a - method_b > ?', 0)
Edit
I think the only way that you're going to be able to query on a calculated field is to move the calculation to the DB, where you would have a scalar function to return the results. Then you could do MyModel.where('next_service_date_on_db(service_date) > 0'). Unfortunately, that would tie you to a DB-specific implementation, but you're not going to be able to query that way without a server-side function.
Now, if you have the entire collection, you could filter based on those conditions, but you'll have to load the entire set. For example:
MyModel.all.select {|m| m.method_a - m.method_b > 0}
#all returns an array of all of the objects, and select filters based on the condition of the block. Unfortunately, this solution loads all of the records to sort application side.
Not entirely sure what you're trying to achieve .. but you may have to resort to SQL?
(and what's method_a and b?)
MyModel.where('? > 0', (method_a - method_b))
Related
Ok so I have an app that allows users to pull App Store data, specifically top free top paid etc. The various attributes are quite limited, but users can filter by category and country. So obviously this leads to a lot of repeated queries, now normally this wouldn't be a problem, but I also use this data with google api which has a credits system. So What I want to do is save these results in my database if the results are unique. I have this all set up and fine but my only hang up is how I determine if a query has been made before, so my solution is to make a hashtable that stores all queries that have been made before and if not NULL(nil) then I call the api to fetch the data then create a new record.
Issue is the App Store refreshes every day or so(not exactly sure the schedule but will look it up later). I would like to have this Hashtable reference function refresh or reset itself to all NULL at this interval.
What would be the most efficient or simple way to start a refresh for this? Additionally I am kinda new to rails, so where should I place this function? In the helper modules? Controller?
Thanks!
Edit:
ok so here is my HashTable helper module
module MapsHelper
queryHistoryLookUp = {}
i = 0
31.times do |i|
queryTableLookup.merge!(i =>[] )
end
def queryTableLookup(asciiNum, queryString)
if queryTableLookup[asciiNum % 31].size == 0
queryTableLookup[asciiNum % 31].push(queryString)
else
a = queryTableLookup[asciiNum % 31].size
arrayOfQueries = queryTableLookup[asciiNum % 31]
a.times do |i|
if arrayOfQueries[i] == queryString
return true
else
return false
end
end
end
end
end
def queryHash(query)
asciSum = 0
query.each_char do |i|
asciSum += i.sum
end
queryTableLookup(asciSum, query)
end
end
additionally I am kinda new to rails, can I interact with these functions using Javascript, since on the client side I create the string query.
In my opinion, your best bet would be to use the Rails cache system. It provides a method of caching data, with an optional expires_in time.
From the docs:
http://guides.rubyonrails.org/caching_with_rails.html#low-level-caching
class MyModel < ActiveRecord::Base
def self.get_api_data(key)
Rails.cache.fetch("my_model/api_data:#{key}", expires_in: 12.hours) do
SomeService::API.get_data(key)
end
end
end
In your hash (which I think it could exist in a class variable) you can store both the query and the last access datetime:
Suppose you have a hash as class variable to the Foo class with name cache and that the query variable is your current query that you want to check.
if Foo.cache[query].nil? || (DateTime.now - Foo.cache[query].last_fetch).to_i > 0
results = your_method_to_fetch_data_for(query)
Foo.cache[query] = {:results => results, :last_fetch => Datetime.now}
else
results = Foo.cache[query][:results]
end
Given this model:
class User < ActiveRecord::Base
has_many :things
end
Then we can do this::
#user = User.find(123)
#user.things.find_each{ |t| print t.name }
#user.thing_ids.each{ |id| print id }
There are a large number of #user.things and I want to iterate through only their ids in batches, like with find_each. Is there a handy way to do this?
The goal is to:
not load the entire thing_ids array into memory at once
still only load arrays of thing_ids, and not instantiate a Thing for each id
Rails 5 introduced in_batches method, which yields a relation and uses pluck(primary_key) internally. And we can make use of the where_values_hash method of the relation in order to retrieve already-plucked ids:
#user.things.in_batches { |batch_rel| p batch_rel.where_values_hash['id'] }
Note that in_batches has order and limit restrictions similar to find_each.
This approach is a bit hacky since it depends on the internal implementation of in_batches and will fail if in_batches stops plucking ids in the future. A non-hacky method would be batch_rel.pluck(:id), but this runs the same pluck query twice.
You can try something like below, the each slice will take 4 elements at a time and them you can loop around the 4
#user.thing_ids.each_slice(4) do |batch|
batch.each do |id|
puts id
end
end
It is, unfortunately, not a one-liner or helper that will allow you to do this, so instead:
limit = 1000
offset = 0
loop do
batch = #user.things.limit(limit).offset(offset).pluck(:id)
batch.each { |id| puts id }
break if batch.count < limit
offset += limit
end
UPDATE Final EDIT:
I have updated my answer after reviewing your updated question (not sure why you would downvote after I backed up my answer with source code to prove it...but I don't hold grudges :)
Here is my solution, tested and working, so you can accept this as the answer if it pleases you.
Below, I have extended ActiveRecord::Relation, overriding the find_in_batches method to accept one additional option, :relation. When set to true, it will return the activerecord relation to your block, so you can then use your desired method 'pluck' to get only the ids of the target query.
#put this file in your lib directory:
#active_record_extension.rb
module ARAExtension
extend ActiveSupport::Concern
def find_in_batches(options = {})
options.assert_valid_keys(:start, :batch_size, :relation)
relation = self
start = options[:start]
batch_size = options[:batch_size] || 1000
unless block_given?
return to_enum(:find_in_batches, options) do
total = start ? where(table[primary_key].gteq(start)).size : size
(total - 1).div(batch_size) + 1
end
end
if logger && (arel.orders.present? || arel.taken.present?)
logger.warn("Scoped order and limit are ignored, it's forced to be batch order and batch size")
end
relation = relation.reorder(batch_order).limit(batch_size)
records = start ? relation.where(table[primary_key].gteq(start)) : relation
records = records.to_a unless options[:relation]
while records.any?
records_size = records.size
primary_key_offset = records.last.id
raise "Primary key not included in the custom select clause" unless primary_key_offset
yield records
break if records_size < batch_size
records = relation.where(table[primary_key].gt(primary_key_offset))
records = records.to_a unless options[:relation]
end
end
end
ActiveRecord::Relation.send(:include, ARAExtension)
here is the initializer
#put this file in config/initializers directory:
#extensions.rb
require "active_record_extension"
Originally, this method forced a conversion of the relation to an array of activrecord objects and returned it to you. Now, I optionally allow you to return the query before the conversion to the array happens. Here is an example of how to use it:
#user.things.find_in_batches(:batch_size=>10, :relation=>true).each do |batch_query|
# do any kind of further querying/filtering/mapping that you want
# show that this is actually an activerecord relation, not an array of AR objects
puts batch_query.to_sql
# add more conditions to this query, this is just an example
batch_query = batch_query.where(:color=>"blue")
# pluck just the ids
puts batch_query.pluck(:id)
end
Ultimately, if you don't like any of the answers given on an SO post, you can roll-your-own solution. Consider only downvoting when an answer is either way off topic or not helpful in any way. We are all just trying to help. Downvoting an answer that has source code to prove it will only deter others from trying to help you.
Previous EDIT
In response to your comment (because my comment would not fit):
calling
thing_ids
internally uses
pluck
pluck internally uses
select_all
...which instantiates an activerecord Result
Previous 2nd EDIT:
This line of code within pluck returns an activerecord Result:
....
result = klass.connection.select_all(relation.arel, nil, bound_attributes)
...
I just stepped through the source code for you. Using select_all will save you some memory, but in the end, an activerecord Result was still created and mapped over even when you are using the pluck method.
I would use something like this:
User.things.find_each(batch_size: 1000).map(&:id)
This will give you an array of the ids.
I have a dashboard(esque) view in a Rails app which is showing some data in similar ways but broken out into many time periods.
I have some code in my controller like so:
#issues_this_month = Issue.where('issues.created_at BETWEEN ? AND ?', DateTime.now.in_time_zone.beginning_of_month, DateTime.now.in_time_zone.end_of_month)
and I also want to create a variables which shows issues this year and issues all time so I have this code:
#issues_this_year = Issue.where('issues.created_at BETWEEN ? AND ?', DateTime.now.in_time_zone.beginning_of_year, DateTime.now.in_time_zone.end_of_year)
I am curious if someone can think of a good way of doing one query, and from that inferring the date ranges all while avoiding the extra queries. Should I pass the results to a helper method and do the logic there?
in the model... you can define
def date
self.created_at.to_date
end
then in the controller
start = Date.today.beginning_of_year
end = Date.today.end_of_year
#issues_this_year = Issue.where(create_at: start..end).group_by(&:date)
now you have a hash of [month_1, {issues that exist in month_1}, month_2, {issues that exist in month_2}, etc]. play with it in the console to find the proper keys... #issues_this_year.keys
How about defining a method like
def self.in_timeframe(start_time=DateTime.now.in_time_zone.beginning_of_month,
end_time=DateTime.now.in_time_zone.end_of_month)
Issue.where('issues.created_at BETWEEN ? AND ?', start_time, end_time)
end
You can now invoke this as follows:
Issue.in_timeframe # For issues in the month
Issue.in_timeframe(x,y) # For issues within x-y timeframe
If you want the data in a single query, you could do stuff like:
def self.in_timeframes(time_frames)
data = {}
times_frames.each do |time_frame|
data[time_frame[:name]] = Issue.in_timeframe(time_frame[:srtart]. time_frame[:end])
end
data
end
You can invoke the above method using:
time_frames = [{:name=>"month"},
{:name=>"x-y", :start=>x, :end=>y}]
Issue.in_timeframes(time_frames)
I have a table in Rails and I would like to find all records of a table where a certain function returns true.
What is the best way to do this? I could of course iterate over all the records in the table and use conditional statements to test whether the function, given the individual record, returns true and add it to a list.
Is there any easier way of doing this something along the lines of Model.find(:all, :conditions => {...}) maybe?
Thanks
Class MyModel < ActiveRecord
def self.targetted
find_each.select(&:predicate_method?)
end
def predicate_method?
#something that returns either true or false
end
end
this is a bit more Rails idiomatic :
find_each will fetch your record by batches of 1000. it is better than all for your memory
&:predicate_method : transforming a symbol into a Proc (with the # operator) will actually make your code call the method on each of the passed objects
def record_that_returns_true_for_xfunction
Model.all.select {|record| xfunction(record.some_column) == true}
end
This is seems like what you are looking for. This method will return an array of all the records where xfunction(record.some_column) == true.
Hello I'd like to sort an array in a Rails Controller. I want to sort the array before I loop over it in the View
#projects = Project.all.sort #throws error
#and
#projects = Project.all
#projects.sort # throws error
throws this error: undefined method <=> for #<Project:0x101f70b28>
but when I query:
#projects.respond_to?('sort')
I get true
How can I sort the array? Should it be done in the View or in the Controller?
Any help is highly appreciated!
Ruby doesn't know how to sort your project. You must specify the field to use for the sort. Example for created_at:
#projects = Project.all.sort { |p1, p2| p1.created_at <=> p2.created_at }
or
#projects = Project.all.sort_by &:created_at
Alternatively, you can sort them at database level:
#projects = Project.find(:all, :order => 'created_at')
When you try and sort an array of objects, ruby needs to know how to decide which objects come first.
If your objects have an intrinsic order, e.g. They have a "number" to be sorted by, then implement a method in your Project like this:
def <=> other
number <=> other.number
end
The <=> method is used by ruby to compare two objects and determine which appears first. In this example we just delegate the sorting to the number attribute (strings and numbers both already have a built in order)
The alternative, if there may be many ways to sort your objects, is to specify at sort time how to sort. As True Soft explained there are a few ways to do that, my favourite being
#projects = Project.all.sort_by &:created_at
..to sort by the created_at field
The simplest way is to override <=> in Project:
def <=>(other_project)
self.some_comparable_field <=> other_project.some_comparable_field
# or otherwise return 1, 0 or -1 based on custom comparison rule
end
Then your original code will work.
See: http://ruby-doc.org/core/classes/Comparable.html