Rails - Returning all records for who a method returns true - ruby-on-rails

I have a method:
class Role
def currently_active
klass = roleable_type.constantize
actor = Person.find(role_actor_id)
parent = klass.find(roleable_id)
return true if parent.current_membership?
actor.current_membership?
end
end
I would like to return all instances of Role for who this method is true, however can't iterate through them with all.each as this takes around 20 seconds. I'm trying to use where statements, however they rely on an attribute of the model rather than a method:
Role.where(currently_active: true)
This obviously throws an error as there is no attribute called currently_active. How can I perform this query the most efficient way possible, and if possible using Active Records rather than arrays?
Thanks in advance

It seems impossible, in your case you have to do iterations. I think the best solution is to add a Boolean column in your table, so you can filter by query and this will be much faster.
After seeing your method after edit, it seems that it's not slow because of the loop, it is slow because Person.find and klass.find , you are doing alot of queries and database read here. (You better use associations and do some kind of eager loading, it will be much faster)
Another work-around is you can use ActiveModelSerializers , in the serializer you can get the attributes on the object based on condition. and after that you can work your logic to neglect the objects that have some kind of flag or attribute.
See here the documentation of active model serializer
Conditional attributes in Active Model Serializers

Wherever possible you better delegate your methods to SQL through activerecord when you're seeking better efficiency and speed and avoid iterating through objects in ruby to apply the method. I understand this is an old question but still many might get the wrong idea.
There is not enough information on current_membership? methods on associations but here's an example based on some guess-work from me:
roleables = roleable_type.pluralize
roleable_type_sym = roleable_type.to_sym
Role.joins(roleables_sym).where(" ? BETWEEN #{roleables}.membership_start_date AND #{roleables}.membership_end_date", DateTime.current).or(Role.joins(:person).where(" ? BETWEEN persons.membership_start_date AND persons.membership_end_date", DateTime.current))
so you might have to re-implement the method you have written in the model in SQL to improve efficiency and speed.

Try the select method: https://www.rubyguides.com/2019/04/ruby-select-method/
Role.all.select { |r| r.currently_active? }

The above can be shortened to Role.select(&:currently_active?)

Related

Why is this a ReadOnly record?

So I am building an associated object through a main object like so:
item.associated_items.build({name: "Machine", color: "Grey"})
and then in another method calling item.save. However I am getting an ActiveRecord::ReadOnlyRecord error. I read in the docs that
Records loaded through joins with piggy-back attributes will be marked as read only since they cannot be saved.
so I think that is what is happening here. But
I dont't know why that is happening. I have called save on an object with a new associated record before and had no problems.
What do the docs mean when they say "piggy-back attributes"?
Is there a way to make the save happen by doing something like item.associated_items.readonly(false).build(attributes). I tried that and it didnt work, but I'm hoping there is another way.
edit: I just tried
new_associated_item = AssociatedItem.new({attributes})
item.associated_items << new_associated_item
and the later method calls
item.save
and the read only exception still happens.
edit2: MurifoX asked me about how Item is being loaded. The above code is happening in a couple of service objects. The process is
Controller
owner = Owner.includes(:medallions).references(:medallions).find_by_id(params[:id])
later
creator = NoticeCreator.new(owner)
creator.run
NoticeCreator
def initialize #effectively
medallion_notice_creators = []
owner.medallions.some_medallion_scope.each do |medallion|
medallion_notice_creator = MedallionNoticeCreator.new(medallion)
medallion_notice_creator.prepare
medallion_notice_creators << medallion_notice_creator
end
end
later after looping through the medallion notice creators
def prepare
medallion.notices.build(attributes)
end
later
medallion_notice_creators.each do |medallion_notice_creator|
medallion_notice_creator.medallion.save
end
Apologies if the code seems convoluted. There is a bunch of stuff going on and I'm trying to condense the code and anonymize it.
Objects created with joins or includes, which is your case, are marked read-only because you are making a giant query with joins and stuff and preloading nested objects within your main one. (ActiveRecord can become confused with so many attributes and don't know how to build the main object, so it marks readonly on it.)
As you have noticed, this won't happen if you create you object with a simple find, as the only attributes received from the query are from the object itself.
I don't know why you are eager loading all of this associations, maybe it is from some rule in your code, but you should try to create a simple owner object using Owner.find(params[:id]), and lazy loading the associations when needed, so this way you can build nested associations on the simple object and save them.

Rails: cache.fetch vs cache.read/write

is there any performance difference between
Rails.cache.fetch("key") { Model.all }
and
models = Rails.cache.read("key")
if models.nil?
models = Model.all
Rails.cache.write("key", models)
end
If I must guess, i would say the upper one is just a shorthand for the other one.
If you check the source code, you'll notice that fetch does nothing more than call read and write.
Since it does some other operations (like checking if a block has been given, etc.) one could say that fetch is heavier, but I think it's totally negligible.

Ruby partially retrieve large amount of records and iterate over them

I'm newbie in Ruby but I have a lot of experience in other programming languages. I need to iterate over large amount of records (from db or any persistent storage). Storage engine allows me to retrieve records partially by ranges. In PHP I usually write custom iterator that loads range of records iterate over them and when need loads next part of records and forget about previous part. Some trade-off between script memory usage and count of request to storage. Something like this (copied from comments here):
class Database_Result_Iterator {
...
private $_db_resource = null;
private $_loaded = false;
private $_valid = false;
function rewind() {
if ($this->_db_resource) {
mysql_free($this->_db_resource);
$this->_db_resource = null;
}
$this->_loaded = false;
$this->_valid = false;
}
function valid() {
if ($this->_loaded) {
$this->load();
}
return $this->_valid;
}
private function load() {
$this->_db_resource = mysql_query(...);
$this->_loaded = true;
$this->next(); // Sets _valid
}
}
How such approach is transformed in Ruby? I.e. I have some class Voter and method get_votes that returns all votes belong to current voter object. It is possible to retrieve not an array with all votes but collection of votes with possibility to iterate over it. How should I implement it?
UPDATE
Please not consider ActiveRecord and RDBMS as only one possible storage. And what about Redis as storage and commands like LRANGE? I'm interested in common code pattern for solution such kind of problem in Ruby.
From the guides on Ruby on Rails:
User.all.each do |user|
NewsLetter.weekly_deliver(user)
end
Is very innefficient. You probably want to do most of the filtering in the database, to start with. ActiveRecord offers a method called find_each for this:
User.find_each(:batch_size => 5000) do |user|
NewsLetter.weekly_deliver(user)
end
The :batch_size parameter allows to fetch slices of data instead of getting the entire resultset. Extremely helpfull in most cases.
But, you probably don't want to operate on all records in the first place:
User.with_newsletter.each do |user|
NewsLetter.weekly_deliver(user)
end
Where with_newsletter is a so called scope.
I really don't see the point of this question.
AR is an API for querying RDBMS and that's how you do it in AR.
If you want to do redis you'll have to either write it yourself at the driver level or find a similar abstraction to AR for Redis... I think DataMapper had a redis adapter.
If there is a universal way to do this for any data store it is likely in DataMapper, but the basic pattern to follow when creating your own would be to look at how AR implements find_each/find_in_batches and do it for your store of choice.
It sounds like you want to use find_each (http://apidock.com/rails/ActiveRecord/Batches/ClassMethods/find_each). This lets you iterate through a large dataset by loading in a small number, iterating over them, then loading in another batch and so on.
User.find_each do |user|
user.do_some_stuff
end
will iterate through all users without loading a bajillion of them into memory at once.

grails delete all data from table / domain class, i.e. "deleteAll"

I've got a domain class, Widget, that I need to delete all instances out of -- clear it out. After that, I will load in fresh data. What do you suggest as a mechanism to do this?
P.S. Note this is not at bootstrap time, but at "run-time".
The easiest way is to use HQL directly:
DomainClass.executeUpdate('delete from DomainClass')
DomainClass.findAll().each { it.delete() }
If you want to avoid any GORM gotchas, such as needing to delete the object immediately and checking to make sure it actually gets deleted, add some arguments.
DomainClass.findAll().each { it.delete(flush:true, failOnError:true) }
Fairly old post, but still actual.
If your table is very large (millions of entries), iterating using findall()*.delete() might not be the best option, as you can run into transaction timeouts (e.g. MySQL innodb_lock_wait_timeout setting) besides potential memory problems stated by GreenGiant.
So at least for MySQL Innodb, much faster is to use TRUNCATE TABLE:
sessionFactory.currentSession
.createSQLQuery("truncate table ${sessionFactory.getClassMetadata(MyDomainClass).tableName}")
.executeUpdate()
This is only useful if your table is not referenced by other objects as a foreign key.
From what I learnt, I agree with #ataylor the below code is fastest IF there are no associations in your domain object (Highly unlikely in any real application):
DomainClass.executeUpdate('delete from DomainClass')
But if you have assiciations with other domains, then the safest way to delete (and also a bit slower than the one mentioned above) would be the following:
def domainObjects = DomainClass.findAll()
domainObjects.each {
it.delete(flush:it==domainObjects.last, failOnError:true)
}
If you have a list of objects and want to delete all elements, you can use * operator.
'*' will split the list and pass its elements as separate arguments.
Example.
List<Book> books = Book.findAllByTitle('grails')
books*.delete()

How to update all when you need callbacks fired?

Let's say I've got 15 user ids in an array called user_ids.
If I want to, say, change all of their names to "Bob" I could do:
users = User.find(user_ids)
users.update_all( :name => 'Bob' )
This doesn't trigger callbacks, though. If I need to trigger callbacks on these records saving, to my knowledge the only way is to use:
users = User.find(user_ids)
users.each do |u|
u.name = 'Bob'
u.save
end
This potentially means a very long running task in a controller action, however.
So, my question is, is there any other better / higher performance / railsier way to trigger a batch update to a set of records that does trigger the callbacks on the records?
Instead of using each/find_each, try using update method instead:
models.update(column: value)
Which is just a wrapper for the following:
models.each{|x| x.update(column: value)}
Here's another way of triggering callbacks. Instead of using
models.update_all(params)
you can use
models.find_each { |m| m.update_attributes(params) }
I wouldn't recommend this approach if you're dealing with very large amounts of data, though.
Hope it helps!
No, to run callbacks you have to instantiate an object which is expensive operation. I think the only way to solve your problem is to refactor actions that you're doing in callback into separate method that could use data retrieved by select_all method without object instantiation.

Resources