In Rails how do I use find_each method with index? - ruby-on-rails

I can use Rails find_each method like :
User.find_each(:batch_size => 10000) do |user|
------
end
With find_each method is there any way to get the index of the array ? like :
User.find_each(:batch_size => 10000).with_index do |user, index|
------
end

As an update to this question. Active Record 4.1.4 has added support for find_each.with_index as pointed in the documentation.
User.find_each(:batch_size => 1000).with_index do |user, index|
user.call_method(index)
end

Update: As of Rails 4.1.4 this is possible. See this answer for more. Example:
User.find_each(:batch_size => 1000).with_index do |user, index|
user.call_method(index)
end
As you can see from the method definition, this is not possible.
def find_each(options = {})
find_in_batches(options) do |records|
records.each { |record| yield record }
end
end
In order to accomplish what you want to do, you need to either create your own modified version of the method
class User
def find_each(options = {})
find_in_batches(options) do |records|
records.each_with_index { |record| yield record, index }
end
end
end
User.find_each(:batch_size => 10000) do |user, index|
------
end
or use an instance variable.
index = 0
User.find_each(:batch_size => 10000) do |user|
# ...
index += 1
end
There is no other default solution as shown by the method implementation.

Your question is already implemented in the Rails master branch. To get this, it requires using Rails edge, as this has not been merged into any release as of yet. See this merged pull request: https://github.com/rails/rails/pull/10992.
So add this to your Gemfile:
gem 'rails', github: 'rails/rails'
This will allow you to run the code you described:
User.find_each(batch_size: 10000).with_index do |user, index|
puts "UserID: #{user.id.to_s} has index ##{index.to_s}"
end
Granted, running on the edge release is risky, so don't do this in production. But look at the pull request to see the small amount of code added to get this working. You can monkey patch until it is merged into a Rails release.

Just use an local variable:
index = 0
User.find_each(:batch_size => 10000) do |user|
------
index += 1
end

You can get all users and send them through a block along with their corresponding index as follows.
User.all.each_with_index do |user, index|
puts "email is " + "#{user.email}" + " and user index is " + "#{index}"
end

Related

Find a best way to work with redis cache on rails

I try to use Redis to cache on rails, but I get a challenge when trying to cache multi-language. Because my Redis needs to be cached with table_translations
I try with some code, but I don't think this is the best way
I have the instance variable to work with Erb template
def index
#posts = fetch_posts
#translations = fetch_translations
puts #posts
puts #translations
end
and Redis fetch like this
private
def fetch_posts
begin
posts = $redis.get "posts"
if posts.nil?
posts = []
Post.all.order("id ASC").each do |post|
posts << post
end
posts = posts.to_json
$redis.set "posts", posts
end
posts = JSON.load posts
rescue => error
puts error.inspect
posts = Post.all
end
posts
end
def fetch_translations
begin
translations = $redis.get "translations"
if translations.nil?
translations = []
Post.all.order("id ASC").each do |post|
post.translations.order("locale ASC").each do |translation|
translations << translation
end
end
translations = translations.to_json
$redis.set "translations", translations
end
translations = JSON.load translations
rescue => error
puts error.inspect
translations = Post.all
end
translations
end
I do that because I need to get all language version of a post, so I make a Redis key for translate
and my output:
{"id":1,"slug":"hello-world","thumb_url":"thumbs/null","thumb_file_name":null,"thumb_content_type":null,"thumb_file_size":null,"thumb_updated_at":null,"featured":false,"hidden":false,"created_at":"2019-04-18T07:05:09.117Z","updated_at":"2019-04-18T07:27:55.830Z"}
{"title":"Xin chao","description":"Day la bai viet dau tien, duoc viet tu rails CMS","body":"xin chao cac ban"}
{"title":"Hello World","description":"This is first post from rails CMS","body":"Hello every body"}
I find the best solution to make my output into a key, like this:
{"id":1,"slug":"hello-world","thumb_url":"thumbs/null","thumb_file_name":null,"thumb_content_type":null,"thumb_file_size":null,"thumb_updated_at":null,"featured":false,"hidden":false,"created_at":"2019-04-18T07:05:09.117Z","updated_at":"2019-04-18T07:27:55.830Z","title":"Xin chao","description":"Đay la bai viet đau tien, đuoc viet tu rails CMS","body":"xin chao cac ban"}
{"id":1,"slug":"hello-world","thumb_url":"thumbs/null","thumb_file_name":null,"thumb_content_type":null,"thumb_file_size":null,"thumb_updated_at":null,"featured":false,"hidden":false,"created_at":"2019-04-18T07:05:09.117Z","updated_at":"2019-04-18T07:27:55.830Z",title":"Hello World","description":"This is first post from rails CMS","body":"Hello every body"}
My code can work correctly, but I need your help to make it better, please help me to improve my skills
Thank for your help
You can use the built in Rails cache handler, this way you won't need to handle .nil? calls to cache keys.
private
def fetch_posts
posts = Rails.cache.fetch("posts") do
begin
Post.all.order("id ASC").as_json
rescue => error
puts error.inspect
Post.all
end
end
posts
end
def fetch_translations
translations = Rails.cache.fetch("translations") do
begin
Post.all.order("id ASC").map do |post|
post.translations.order("locale ASC").as_json
end.flatten
rescue => error
puts error.inspect
Post.all
end
end
translations
end
I found the solution by follow this stuff How do you add an array to another array in Ruby and not end up with a multi-dimensional result?
concept is flatten all attr in two array and then Hash again this into new array
def fetch_posts
posts = []
Post.all.order("id ASC").each do |post|
post.translations.each do |translation|
posts << [*post.attributes.slice('slug','thumb_url'),*JSON.parse(translation.to_json)].to_h
end
end
end
Hope this help to anyone have question same to me :)

How to track objects "called" inside a block?

Question:
I need to know the records' attributes that have been called inside a block (say I need something like the following):
def my_custom_method(&block)
some_method_that_starts_tracking
block.call
some_method_that_stops_tracking
puts some_method_that_returns_called_records_attributes
do_something_about(some_method_that_returns_called_records_attributes)
end
my_custom_method { somecodethatcallsauthorofbook1andemailandfirstnameofuser43 }
# this is the `puts` output above (just as an example)
# => {
# #<Book id:1...> => [:author],
# #<User id:43...> => [:email, :first_name]
# }
code inside the block can be anything
Specifically, I meant to track any instance of a subclass of ApplicationRecord, so it can be instance of any models like Book, User, etc...
Attempts:
From my understanding, this is similar to how rspec works when a method is expected to be called. That it somehow tracks any calls of that method. So, my initial attempt is to do something like the following (which does not yet fully work):
def my_custom_method(&block)
called_records_attributes = {}
ApplicationRecord.descendants.each do |klass|
klass.class_eval do
attribute_names.each do |attribute_name|
define_method(attribute_name) do
called_records_attributes[self] ||= []
called_records_attributes[self] << attribute_name
self[attribute_name]
end
end
end
end
block.call
# the above code will work but at this point, I don't know how to clean the methods that were defined above, as the above define_methods should only be temporary
puts called_records_attributes
end
my_custom_method { Book.find_by(id: 1).title }
# => {
# #<Book id: 1...> => ['title']
# }
the .descendants above probably is not a good idea because Rails use autoload if I'm not mistaken
as already said above in the comment, I do not know how to remove these "defined_methods" that are just supposed to be only temporary for the duration of this "block".
furthermore, my code above would probably have overriden the "actual" attribute getters of the models, if ever any has been already defined, which is bad.
Background:
I am writing a gem live_record which I am adding a new feature that will allow a developer to just simply write something like
<!-- app/views/application.html.erb -->
<body>
<%= live_record_sync { #book.some_custom_method_about_book } %>
</body>
... which will render #book.some_custom_method_about_book as-is on the page, but at the same time the live_record_sync wrapper method would take note of all the attributes that have been called inside that block (i.e. inside some_custom_method_about_book the #book.title is called), and then it sets these attributes as the block's own "dependencies", in which later when that specific book's attribute has been updated, I can already also update directly the HTML page of which this attribute is a "dependency" as like specified just above. I am aware that this is not an accurate solution, but I'd like to open up my chances by experimenting on this first.
-- Rails 5
Disclaimer: I believe this is just a mediocre solution, but hopefully helps anyone with the same problem.
I tried reading rspec source code, but because I couldn't easily comprehend what is happening under the hood, and that it occurred to me that rspec's (i.e.) expect(Book.first).to receive(:title) is different from what I really want because the methods there are already specified (i.e. :title), while what I want is to track ANY methods that are attributes, so because of these two reasons I skipped reading further, and attempted my own solution, which hopefully did somehow work; see below.
Note that I am using Thread local-storage here, so this code should be thread-safe (untested yet).
# lib/my_tracker.rb
class MyTracker
Thread.current[:my_tracker_current_tracked_records] = {}
attr_accessor :tracked_records
class << self
def add_to_tracked_records(record, attribute_name)
Thread.current[:my_tracker_current_tracked_records][{model: record.class.name.to_sym, record_id: record.id}] ||= []
Thread.current[:my_tracker_current_tracked_records][{model: record.class.name.to_sym, record_id: record.id}] << attribute_name
end
end
def initialize(block)
#block = block
end
def call_block_while_tracking_records
start_tracking
#block_evaluated_value = #block.call
#tracked_records = Thread.current[:my_tracker_current_tracked_records]
stop_tracking
end
def to_s
#block_evaluated_value
end
# because I am tracking record-attributes, and you might want to track a different object / method, then you'll need to write your own `prepend` extension (look for how to use `prepend` in ruby)
module ActiveRecordExtensions
def _read_attribute(attribute_name)
if Thread.current[:my_tracker_current_tracked_records] && !Thread.current[:my_tracker_is_tracking_locked] && self.class < ApplicationRecord
# I added this "lock" to prevent infinite loop inside `add_to_tracked_records` as I am calling the record.id there, which is then calling this _read_attribute, and then loops.
Thread.current[:my_tracker_is_tracking_locked] = true
::MyTracker.add_to_tracked_records(self, attribute_name)
Thread.current[:my_tracker_is_tracking_locked] = false
end
super(attribute_name)
end
end
module Helpers
def track_records(&block)
my_tracker = MyTracker.new(block)
my_tracker.call_block_while_tracking_records
my_tracker
end
end
private
def start_tracking
Thread.current[:my_tracker_current_tracked_records] = {}
end
def stop_tracking
Thread.current[:my_tracker_current_tracked_records] = nil
end
end
ActiveSupport.on_load(:active_record) do
prepend MyTracker::ActiveRecordExtensions
end
ActiveSupport.on_load(:action_view) do
include MyTracker::Helpers
end
ActiveSupport.on_load(:action_controller) do
include MyTracker::Helpers
end
Usage Example
some_controller.rb
book = Book.find_by(id: 1)
user = User.find_by(id: 43)
my_tracker = track_records do
book.title
if user.created_at == book.created_at
puts 'same date'
end
'thisisthelastlineofthisblockandthereforewillbereturned'
end
puts my_tracker.class
# => #<MyTracker ... >
puts my_tracker.tracked_records
# => {
# {model: :Book, record_id: 1} => ['title', 'created_at'],
# {model: :User, record_id: 43} => ['created_at']
# }
puts my_tracker
# => 'thisisthelastlineofthisblockandthereforewillbereturned'
# notice that `puts my_tracker` above prints out the block itself
# this is because I defined `.to_s` above.
# I need this `.to_s` so I can immediately print the block as-is in the views.
# see example below
some_view.html.erb
<%= track_records { current_user.email } %>
P.S. Maybe it's better that I wrap this up as a gem. If you're interested, let me know

How to sort the items in an array to match the order that it appears in another array

I want to grab the ratings from movies in the database and return an array of unique ratings, sorted in the following order:
G PG PG-13 R NC-17
A plain Array#sort wasn't enough:
["PG-13", "PG", "NC-17", "G", "R"].sort
# => ["G", "NC-17", "PG", "PG-13", "R"]
The following code gives me what I want, but seems like there's a better way to write it not having to use delete and <<. Any ideas would be appreciated.
class Movie < ActiveRecord::Base
def self.all_ratings
allRatings = []
Movie.all.each do |movie|
unless allRatings.include?(movie.rating)
allRatings << movie.rating
end
end
if allRatings.include?("NC-17")
allRatings.sort!
allRatings.delete("NC-17")
allRatings << "NC-17"
return allRatings
else
return allRatings.sort
end
end
end
UPDATE:
Using Sergio's tip, I was able to refactor the code. If anyone has some other ideas, I'd appreciate the feedback.
class Movie < ActiveRecord::Base
def self.all_ratings
allRatings = []
Movie.all.each do |movie|
unless allRatings.include?(movie.rating)
allRatings << movie.rating
end
end
allRatings.sort_by! {|t| t == 'NC-17' ? 'ZZZ' : t}
end
end
UPDATE:
Using ByScripts tip, this code works well and is very concise. I had to upgrade from Rails 3.1.0 to Rails 3.2.8 to get the pluck method. Looks like it was introduced in 3.2.1.
I also had to add .sort to get the desired output.
class Movie < ActiveRecord::Base
def self.all_ratings
all_ratings = Movie.pluck(:rating).uniq.sort# don't retrieve unnecessary datas
all_ratings << all_ratings.delete('NC-17') # directly inject NC-17 at the end if exists
all_ratings.compact # remove nil values
end
end
You can use this small trick
sorted = ["PG-13", "PG", "NC-17", "G", "R"].sort_by {|t| t == 'NC-17' ? 'ZZZ' : t }
sorted # => ["G", "PG", "PG-13", "R", "NC-17"]
Basically, for sorting purposes, you substitute "NC-17" with a "ZZZ" which sorts last.
That should works :
def self.all_ratings
all_ratings = Movies.order(:rating).pluck(:rating).uniq # don't retrieve unnecessary datas
all_ratings << all_rating.delete('NC-17') # directly inject NC-17 at the end if exists
all_ratings.compact # remove nil values
end
You can also do Movies.uniq.pluck(:rating)
That does a SELECT DISTINCT query (where pluck.uniq filters the array). Don't know if there is a performance impact (maybe a lower memory footprint ?).
Anyway, both should works the same.
I like Sergio's trick, but if you're looking for a simpler version of your original code that still does have delete and <<, try this
def sort(ratings)
ratings.sort!
return ratings unless ratings.include?("NC-17")
ratings.delete("NC-17")
ratings << "NC-17"
end
class Movie < ActiveRecord::Base
RatingOrder = %w[G PG PG-13 R NC-17]
def self.all_ratings
RatingOrder & Movie.all.map(&:rating)
end
end

Update on record would update all other records in model, global ordering in rails 3.1

I would like to update all records in a rails (3.1) model when i update an attribute on a single record.
Like self.update_attribute(:global_order => 1) then before or after save a would like to update all other records to update thier global_order (1, 2, 3, 4).
Right now with on after_save callback I get caught in a recursive loop, is skip callbacks the way to go? I would like the app to throw exceptions if anything seems strange in global_order.
Or are there any 3.1 gems that would solve my issue.
after_save :set_global_order
def set_global_order
#products = self.class.all(:order => :global_order)
#products.sort! {|a,b| a.global_order <=> b.global_order}
#products.reverse!
#products.each_with_index do |p, index|
p.update_attributes!({:global_order => index + 1})
end
end
Not sure if there's a gem, but you could definitely refactor this with the following considerations:
No need to pollute the object with an instance variable when a local one will do
The first three lines are sorting the same set, why not do that once?
...
def set_global_order
products = self.class.order('global_order DESC')
products.each_with_index do |p, index|
p.update_column(:global_order, index + 1)
end
end

Tell the end of a .each loop in ruby

If i have a loop such as
users.each do |u|
#some code
end
Where users is a hash of multiple users. What's the easiest conditional logic to see if you are on the last user in the users hash and only want to execute specific code for that last user so something like
users.each do |u|
#code for everyone
#conditional code for last user
#code for the last user
end
end
users.each_with_index do |u, index|
# some code
if index == users.size - 1
# code for the last user
end
end
If it's an either/or situation, where you're applying some code to all but the last user and then some unique code to only the last user, one of the other solutions might be more appropriate.
However, you seem to be running the same code for all users, and some additional code for the last user. If that's the case, this seems more correct, and more clearly states your intent:
users.each do |u|
#code for everyone
end
users.last.do_stuff() # code for last user
I think a best approach is:
users.each do |u|
#code for everyone
if u.equal?(users.last)
#code for the last user
end
end
Did you tried each_with_index?
users.each_with_index do |u, i|
if users.size-1 == i
#code for last items
end
end
h = { :a => :aa, :b => :bb }
h.each_with_index do |(k,v), i|
puts ' Put last element logic here' if i == h.size - 1
end
Another solution is to rescue from StopIteration:
user_list = users.each
begin
while true do
user = user_list.next
user.do_something
end
rescue StopIteration
user.do_something
end
You can use #meager's approach also for an either/or situation, where you're applying some code to all but the last user and then some unique code to only the last user.
users[0..-2].each do |u|
#code for everyone except the last one, if the array size is 1 it gets never executed
end
users.last.do_stuff() # code for last user
This way you don't need a conditional!
Sometimes I find it better to separate the logic to two parts, one for all users and one for the last one. So I would do something like this:
users[0...-1].each do |user|
method_for_all_users user
end
method_for_all_users users.last
method_for_last_user users.last
There are no last method for hash for some versions of ruby
h = { :a => :aa, :b => :bb }
last_key = h.keys.last
h.each do |k,v|
puts "Put last key #{k} and last value #{v}" if last_key == k
end

Resources