📜 ⬆️ ⬇️

ActiveRecord Hacks

Today I will share my set of not always obvious features and features of Active Record that I encountered during the development of Ruby on Rails applications or found in other people's blogs.



Validation bypass when using update_attributes
')
The standard update_attributes method does not have a key to bypass validation, so you have to resort to assign_attributes followed by save

@user = User.find(params[:id]) @user.assign_attributes(:name, "") @user.save(validate: false) 


Of course - it is better not to resort to this method very often :)

Division into 2 non-intersecting collections

Sometimes there is the task of dividing the selection of objects into 2 non-intersecting collections. You can do this by using this scope.

 Article < ActiveRecord::Base scope :unchecked, where(:checked => false) #or this, apologies for somewhat unefficient, but you already seem to have several queries scope :unchecked2, lambda { |checked| where(["id not in (?)", checked.pluck(:id)]) } end 


Well, accordingly, access to both collections can be obtained by

 Article.unchecked Article.unchecked2(@unchecked_articles) 


pluck

In the previous example, I used the pluck method. Surely each of you used something like

 Article.all.select(:title).map(&:title) 


or even

 Article.all.map(&:title) 


So - pluck makes it easier

 Article.all.pluck(:title) 


Access to the base class

In the process of working on one project, I was faced with a great nesting of classes of models and the need to get to the root class. The classes looked like this:

 class Art < ActiveRecord::Base end class Picture < Art end class PlainPicture < Picture end 


In order to get from PlainPicture to Art you can use the method becomes

 @plain_pictures = PlainPicture.all @plain_pictures.map { |i| if i.class < Art then i.becomes(Art) else i end }.each do |pp| #do something with Art end 


first_or_create and first_or_initialize

Another great method is first_or_create. From the title it is clear what he does, and we let's see how it can be used

 Art.where(name: "Black square").first_or_create 


We can also use it in block construction.

 Art.where(name: "Black square").first_or_create do |art| art.author = "Malevich" end 


And if you do not want to save - you can use first_or_initialize for example in this way

 @art = Art.where(name: "Black square").first_or_initialize 


scoped and none

Pay attention to 2 more remarkable methods - scoped and none. How they work - I will show by example, but I want to note that it is necessary to separate their behavior in rails3 and rails4, since it differs.

 def filter(filter_name) case filter_name when :all scoped when :published where(:published => true) when :unpublished where(:published => false) else none end end 


How the method behaves if it is transferred to it: published and: unpublished. I hope you understand it, there are no differences in the rails versions.

Using scoped in our example in the case of rails3 allows you to create an anonymous skop that can be used for complex compound queries. If you try to apply it in rails4, you can see the message that the method has become deprecated and it is proposed to use Model.all instead. In the case of rails3, Model.all returns not ActiveRecord :: Relation expected by us, but Array.

The situation with none is similar to scoped exactly the opposite :) This method returns an empty ActiveRecord :: Relation, but it works only in rails4. It is needed if you need to return zero results. For use in rails3 there is such a workaround:

 scope :none, where(:id => nil).where("id IS NOT ?", nil) 


or even such (for example in initializer)

 class ActiveRecord::Base def self.none where(arel_table[:id].eq(nil).and(arel_table[:id].not_eq(nil))) end end 


find_each

The find_each method is very convenient in order to process a large number of records from the database. One could of course make a sample of

 Article.where(published: true).each do |article| #do something end 


But in this case we will have to keep the entire sample in memory in its entirety, which is very unprofitable in the case of a large amount of data. In this case, it would be better to use this approach.

 Article.where(published: true).find_each do |article| #do something end 


which in small samples (1000 objects at a time by default) processes the data.

to_sql and explain

Two methods to help you figure out how your query works.

 Art.joins(:user).to_sql 


will return to you the sql query that the application will compile for asking for a database, and

 Art.joins(:user).explain 


will show technical information on request - approximate amount of time, sample size and other data.

scoping

This method allows you to make a sample within the sample, for example

 Article.where(published: true).scoping do Article.first end 


will request type

 SELECT * FROM articles WHERE published = true LIMIT 1 


merge

Another interesting method that allows you to cross several samples. for example

 class Account < ActiveRecord::Base # ... # Returns all the accounts that have unread messages. def self.with_unread_messages joins(:messages).merge( Message.unread ) end end 


allows you to make a selection of all accounts in which there are unread messages.

Source: https://habr.com/ru/post/181145/


All Articles