Rails Limit results based on Date Range - ruby-on-rails

I have the following Strucuture in the database
db.slots.find().pretty()
{
"_id" : ObjectId("52ae8990bd521b2da7000003"),
"created_at" : ISODate("2013-12-16T05:03:12.345Z"),
"day_from" : "Mon",
"day_to" : "Sat",
"doctor_clinic_id" : ObjectId("52ae8990bd521b2da7000004"),
"evening_time_from" : 0,
"evening_time_from_period" : "AM",
"evening_time_to" : 0,
"evening_time_to_period" : "AM",
"morning_time_from" : 9,
"morning_time_from_period" : "AM",
"morning_time_to" : 2,
"morning_time_to_period" : "PM",
"store" : [
ISODate("2013-12-13T09:00:00Z"),
ISODate("2013-12-13T09:15:00Z"),
ISODate("2013-12-13T09:30:00Z"),
ISODate("2013-12-13T09:45:00Z"),
ISODate("2013-12-13T10:00:00Z"),
ISODate("2013-12-13T10:15:00Z"),
ISODate("2013-12-13T10:30:00Z"),
ISODate("2013-12-13T10:45:00Z"),
ISODate("2013-12-13T11:00:00Z"),
ISODate("2013-12-13T11:15:00Z"),
ISODate("2013-12-13T11:30:00Z"),
ISODate("2013-12-13T11:45:00Z"),
ISODate("2013-12-13T12:00:00Z"),
ISODate("2013-12-13T12:15:00Z"),
ISODate("2013-12-13T12:30:00Z"),
ISODate("2013-12-13T12:45:00Z"),
ISODate("2013-12-13T13:00:00Z"),
ISODate("2013-12-13T13:15:00Z"),
ISODate("2013-12-13T13:30:00Z"),
ISODate("2013-12-13T13:45:00Z"),
........
.....
..
ISODate("2013-12-25T13:15:00Z"),
ISODate("2013-12-25T13:30:00Z"),
ISODate("2013-12-25T13:45:00Z"),
ISODate("2013-12-25T14:00:00Z")
],
"updated_at" : ISODate("2013-12-16T05:03:12.345Z")
}
I want to only fetch the content of the Store which has the date of Today and the next five days.
When i try the following i get a single slot back
slots.where(:store.gte => Date.today)
Results
#<Mongoid::Criteria selector: {"doctor_clinic_id"=>"52ae8990bd521b2da7000004", "store"=>{"$gte"=>Mon, 16 Dec 2013}} options: {} class: Slot embedded: false>
slot.rb
class Slot
include Mongoid::Document
field :store, type: Array
belongs_to :clinic
end
slot.store.where(:store.gte => Date.today) resulting one output like above..!!!

First: What you are trying to achieve is called projection in MongoDB, which is used when you want to retrieve only certain fields from an element. Indeed what you are trying to do is retrieve certain elements from an array field (See Mongodb projection positional)
In mongo you try something like this:
query={}
projection = {day_from:1, day_to:1} //Retrieve only _id, day_from and day_to fields
db.slots.find(query,projection).pretty()
Mongodb projection positional is what you would need but the documentation says that it only returns the first element that matches the query.
So I guess that you cannot directly with mongo. I recommend you to try a different approach like a formatter and virtual attributes. Something similar to:
class Slot
include Mongoid::Document
field :store, type: Array
def with_dates_after date
self.virtual_store= self.store.select{|elem| elem >= date}
end
def virtual_store= arg_arr
#_virtual_store = arg_arr
end
def virtual_store
#_virtual_store ||= []
end
end

Related

How can i convert string to array of object json in Rails

i just create a migration like this
def change
add_column :articles, : information, :json,
default: '[{ "type:c":"12", "temperature":12 }]', null: false
end
i just migrated that table
right now, to create a new article, i am sending this in my request with postman, i am sending properly the 'information' as string?
{
"section":"139",
"information":"[{ type:c, temperature:9 }]" //string
}
so far every thing is ok
now i want to get the param information as a array not string
I want to convert information in an array of json object, how can i do that?
This can be achieved in two ways.
require 'yaml'
input_params = {"section":"139","information":"[{ type: c, temperature: 9 }]"}
#=> {"section"=>"139", "information"=>"[{ type: c, temperature: 9 }]"}
YAML.load(input_params['information'])
#=> [{"type"=>"c", "temperature"=>9}]
or If you want to use JSON.parse then input_params should be as below:
require 'json'
input_params = {"section"=> "139", "information" =>'[{ "type":"c", "temperature":"9" }]'}
#=> {"section"=>"139", "information"=>"[{ \"type\":\"c\", \"temperature\":\"9\" }]"}
JSON.parse(input_params['information'])
#=> [{"type"=>"c", "temperature"=>9}]
I hope this will help you

Rails translate enum in an array

I am currently working on statistics, so I get an array containing all my data. The problem is that this data contains enums and that I would like to translate them without overwriting the rest.
Here is a given example that contains my array (it contains several hundred) :
#<Infosheet id: 90, date: "2018-04-22 00:00:00", number: 7, way: "home", gender: "man", age: "age1", district: "", intercommunal: "", appointment: true, othertype: "", otherorientation: "", user_id: 3, created_at: "2018-04-22 17:51:16", updated_at: "2018-04-22 17:51:16", typerequest_id: 168, orientation_id: 188, info_number: nil, city_id: 105>
I would like to translate the enums of "way" or "gender" or "age", while retaining the rest of the data, because currently, if I make a translation in the console, it crushes everything else.
Do you know how to make that ?
Thanks !
You can just loop over all the enum attributes and get their values. Later you can merge and pass a new hash containing converted values
ENUM_COLUMNS = %i[way gender age] # Equivalent to [:way, :gender, :age]
def convert_enums
overrided_attributes = {}
ENUM_COLUMNS.each { |column| overrided_attributes[column.to_s] = self[column] }
attributes.merge(overrided_attributes)
end
NOTE:
While infosheet.gender returns you male or female
infosheet[:gender] will return you the respective integer value 0 or 1
You can test this if you use translate enum gem :
a = Infosheet.group(:gender).count
{“male”=>30, “female”=>6532}
Create a hash
r = Hash.new
And populate this with :
a.each {|s| puts r[Infosheet.translated_gender(s[0])]=s[1] }
r
result :
{“homme”=>30, “femme”=>6532}

Ruby sort array with objects by first character of string

this is my first try using ruby, this is probably a simple problem, I have been stuck for an hour now, I have a ruby array with some objects in it, and I want that array to sort by the first character in the objects name property (which I make sure is always a number.)
the names are similar to:
4This is an option
3Another option
1Another one
0Another one
2Second option
I have tried:
objectArray.sort_by {|a| a.name[0].to_i}
objectArray.sort {|a,b| a.name[0].to_i <=> b.name.to_i}
In both cases my arrays sorting doesnt change.. (also used the destructive version of sort! and sort_by!)
I looped through the array like this:
objectArray.each do |test|
puts test.name[0].to_i
puts "\n"
end
and sure enough I see the integer value it should have
Tried with an array like this one:
[
{ id: 5, name: "4rge" },
{ id: 7, name: "3gerg" },
{ id: 0, name: "0rege"},
{ id: 2, name: "2regerg"},
{ id: 8, name: "1frege"}
]
And I don't have any issues with #sagarpandya82's answer:
arr.sort_by { |a| a[:name][0] }
# => [{:id=>0, :name=>"0rege"}, {:id=>8, :name=>"1frege"}, {:id=>2, :name=>"2regerg"}, {:id=>7, :name=>"3gerg"}, {:id=>5, :name=>"4rge"}]
Just sort by name. Since strings are sorted in lexicographic order, the objects will be sorted by name's first character :
class MyObject
attr_reader :name
def initialize(name)
#name = name
end
def to_s
"My Object : #{name}"
end
end
names = ['4This is an option',
'3Another option',
'1Another one',
'0Another one',
'2Second option']
puts object_array = names.map { |name| MyObject.new(name) }
# My Object : 4This is an option
# My Object : 3Another option
# My Object : 1Another one
# My Object : 0Another one
# My Object : 2Second option
puts object_array.sort_by(&:name)
# My Object : 0Another one
# My Object : 1Another one
# My Object : 2Second option
# My Object : 3Another option
# My Object : 4This is an option
If you want, you could also define MyObject#<=> and get the correct sorting automatically :
class MyObject
def <=>(other)
name <=> other.name
end
end
puts object_array.sort
# My Object : 0Another one
# My Object : 1Another one
# My Object : 2Second option
# My Object : 3Another option
# My Object : 4This is an option

Ruby Array#sort_by on array of ActiveRecord objects seems slow

I'm writing a controller index method that returns a sorted array of ActiveRecord Contact objects. I need to be able to sort the objects by attributes or by the output of an instance method. For example, I need to be able to sort by contact.email as well as contact.photos_uploaded, which is an instance method that returns the number of photos a contact has.
I can't use ActiveRecord's native order or reorder method because that only works with attributes that are columns in the database. I know from reading that normally array#sort_by is much faster than array#sort for complex objects.
My question is, how can I improve the performance of this block of code in my controller method? The code currently
contacts = company.contacts.order(last_name: :asc)
if params[:order].present? && params[:order_by].present? && (Contact::READ_ONLY_METHOD.include?(params[:order_by].to_sym) || Contact::ATTRIBUTES.include?(params[:order_by].to_sym))
contacts = contacts.sort_by do |contact|
if params[:order_by] == 'engagement'
contact.engagement.to_i
else
contact.method(params[:order_by].to_sym).call
end
end
contacts.reverse! if params[:order] == 'desc'
end
The root problem here (I think) is that I'm calling sort_by on contacts, which is an ActiveRecord::Relation that could have several hundred contacts in it. Ultimately I paginate the results before returning them to the client, however they need to be sorted before they can be paginated. When I run the block of code above with 200 contacts, it takes an average of 900ms to execute, which could be a problem in a production environment if a user has thousands of contacts.
Here's my Contact model showing some relevant methods. The reason I have a special if clause for engagement is because that method returns a string that needs to be turned into an integer for sorting. I'll probably refactor that before I commit any of this to return an integer. Generally all the methods I might sort on return an integer representing the number of associated objects (e.g. number of photos, stories, etc that a contact has). There are many others, so for brevity I'm just showing a few.
class Contact < ActiveRecord::Base
has_many :invites
has_many :responses, through: :invites
has_many :photos
has_many :requests
belongs_to :company
ATTRIBUTES = self.attribute_names.map(&:to_sym)
READ_ONLY_METHOD = [:engagement, :stories_requested, :stories_submitted, :stories_published]
def engagement
invites = self.invites.present? ? self.invites.count : 1
responses = self.responses.present? ? self.responses.count : 0
engagement = ((responses.to_f / invites).round(2) * 100).to_i.to_s + '%'
end
def stories_requested
self.invites.count
end
def stories_submitted
self.responses.count
end
def stories_published
self.responses.where(published: true).count
end
end
When I run a query to get a bunch of contacts and then serialize it to get the values for all these methods, it only takes ~80ms for 200 contacts. The vast majority of the slowdown seems to be happening in the sort_by block.
The output of the controller method should look like this after I iterate over contacts to build a custom data structure, using this line of code:
#contacts = Hash[contacts.map { |contact| [contact.id, ContactSerializer.new(contact)] }]
I've already benchmarked that last line of code so I know that it's not a major source of slowdown. More on that here.
{
contacts: {
79: {
id: 79,
first_name: "Foo",
last_name: "Bar",
email: "t#t.co",
engagement: "0%",
company_id: 94,
created_at: " 9:41AM Jan 30, 2016",
updated_at: "10:57AM Feb 23, 2016",
published_response_count: 0,
groups: {
test: true,
test23: false,
Test222: false,
Last: false
},
stories_requested: 1,
stories_submitted: 0,
stories_published: 0,
amplify_requested: 1,
amplify_completed: 1,
photos_uploaded: 0,
invites: [
{
id: 112,
email: "t#t.co",
status: "Requested",
created_at: "Jan 30, 2016, 8:48 PM",
date_submitted: null,
response: null
}
],
responses: [ ],
promotions: [
{
id: 26,
company_id: 94,
key: "e5cb3bc80b58c29df8a61231d0",
updated_at: "Feb 11, 2016, 2:45 PM",
read: null,
social_media_posts: [ ]
}
]
}
}
}
if params[:order_by] == 'stories_submitted'
contact_ids = company.contact_ids
# count all invites that have the relevant contact ids
invites=Invite.where(contact_id:contact_ids).group('contact_id').count
invites_contact_ids = invites.map(&:first)
# Add contacts with 0 invites
contact_ids.each{|c| invites.push([c, 0]) unless invites_contact_ids.include?(c)}
# Sort all invites by id (add .reverse to the end of this for sort DESC)
contact_id_counts=invites.sort_by{|r| r.last}.map(&:first)
# The [0, 10] limits you to the lowest 10 results
contacts=Contact.where(id: contact_id_counts[0, 10])
contacts.sort_by!{|c| contact_id_counts.index(c.id)}
end

Filter data in JSON string

I have a JSON string as pulled from some API
[{"_id"=>"56aefb3b653762000b400000",
"checkout_started_at"=>"2016-02-01T07:32:09.120+01:00",
"created_at"=>"2016-02-01T07:29:15.695+01:00", ...}]
I want to filter data in this string based on created_at, e.g. letting the user chose a specific date-range and then only show the data from this range.
E.g.
#output = my_json.where(created_at: start_date..end_date)
My first thought was to somehow transfer the JSON string to Hashie, to interact with JSON as the data were objects:
my_json = (my_json).map { |hash| Hashie::Mash.new(hash) }
but that didn't work out
undefined method `where' for Array:0x007fd0bdeb84e0
How can I filter out data from a JSON string based on specific criteria or even SQL queries?
This simplest possible way would be to use Enumberable#select directly on the array of hashes:
require 'time'
myjson.select { |o| Time.iso8601(o["created_at"]).between?(start_date, end_date) }
If you want a fancy interface surrounding the raw data:
require 'time' # not needed in rails
class Order < Hashie::Mash
include Hashie::Extensions::Coercion
coerce_key :created_at, ->(v) do
return Time.iso8601(v)
end
end
class OrderCollection
def initialize(json)
#storage = json.map { |j| Order.new(j) }
end
def between(min, max)
#storage.select { |t| t.created_at.between?(min, max) }
end
end
orders = OrderCollection.new(myjson)
orders.between(Time.yesterday, Time.now)

Resources