Rails: How to eager load attachments belonging to an associated model? - ruby-on-rails-5

In my Rails v5.2.4.1 app (ruby v2.6.1), I am loading a bunch of messages using a working query. Each message belongs_to a :user model, and user has_one_attached :photo. I am eager loading users with all the messages like this:
Message.<query>.includes(:user)
Right now, this code photo.attached? ? photo : 'default_avatar.png' results in queries like this:
SELECT "active_storage_attachments".* FROM "active_storage_attachments" WHERE "active_storage_attachments"."record_id" = $1 AND "active_storage_attachments"."record_type" = $2 AND "active_storage_attachments"."name" = $3 LIMIT $4 [["record_id", 32], ["record_type", "User"], ["name", "photo"], ["LIMIT", 1]]
How do I eager load user.photo and user.photo.attached? with my query?

After some research, I found the answer to this. I had overlooked it in the docs. The problem is solved by using <association>_attachment in the includes. So in my question, the eager loading (for photo) is done using the following code
Message.<query>.includes(user: :photo_attachment)

Message.<query>.includes(user: { photo_attachements: :blob })
refrence: eagerload-active-storage-models

Related

From Ember to JSON API with Rails 5 and active_model_serializers - how to parse params hash in Rails controller

I followed the settings described in active_model_serializers guide to couple a Rails 5 API with Ember 3 app.
In Rails ShopLanguagesController, I modified shop_language_params method as described in the above guide:
private
def shop_language_params
#params.require(:shop_language).permit(:shop_id, :language_id, :modified_by)
ActiveModelSerializers::Deserialization.jsonapi_parse!(params, only: [:shop_id, :language_id, :modified_by] )
end
Here is what I get when I post the following data from Ember app (seen in Rails log console):
Started POST "/shops/613/languages" for 127.0.0.1 at 2018-04-09 16:53:05 +0200
Processing by ShopLanguagesController#create as JSONAPI
Parameters: {"data"=>{"attributes"=>{"modified_by"=>"Z28SCAMB"}, "relationships"=>{"shop"=>{"data"=>{"type"=>"shops", "id"=>"613"}}, "language"=>{"data"=>{"type"=>"languages", "id"=>"374"}}}, "type"=>"shop-languages"}, "shop_id"=>"613"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."username" = $1 LIMIT $2 [["username", "Z28SCAMB"], ["LIMIT", 1]]
Shop Load (0.4ms) SELECT "shops".* FROM "shops" WHERE "shops"."id" = $1 LIMIT $2 [["id", 613], ["LIMIT", 1]]
++++ params: {:modified_by=>"Z28SCAMB"}
The posted data seems to be correct.
How to extract the needed parameters (for example, language_id) from this JSON ?
Parsing JSON in Rails is quite straightforward:
parsed_json = ActiveSupport::JSON.decode(your_json_string)
Let's suppose, the object you want to associate the shortUrl with is a Site object, which has two attributes - short_url and long_url. Than, to get the shortUrl and associate it with the appropriate Site object, you can do something like:
parsed_json["results"].each do |longUrl, convertedUrl|
site = Site.find_by_long_url(longUrl)
site.short_url = convertedUrl["shortUrl"]
site.save
end
The solution that worked for me was to modify the private shop_language_params method in ShopLanguagesController as follows:
def shop_language_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params, only: [:shop, :language, :modified_by] )
end
As you see, I permit not foreign keys values for shop and language (shop_id and language_id) but the objects themselves: :shop and :language. Now you have shop_id and language_id values available in params hash as usually.
And, of course, you should call shop_language_params everywhere in the controller where you need do pass the shop_language params.
Example where I find a language in the same controller:
private
def find_language
#language = Language.find_by!(id: shop_language_params[:language_id])
end

Ruby on Rails : #any? returns wrong value

In my models, I have users (User) and stories (Story), with the relation: user has_many stories.
I've noticed something strange in my shell:
(dev) user.stories.any?
=> true
(dev) user.stories
Story Load (1.6ms) SELECT "stories".* FROM "stories" WHERE "stories"."user_id" = 703 ORDER BY created_at ASC [["user_id", 703]]
=> []
(dev) user.stories.any?
=> false
How does this work? Is this due to my code, or is it a bug of some sort in Rails and the way it queries the database?
A workaround I found (thanks to #house9) is to use:
user.stories.to_a.any?
user.stories.to_a.empty? # also works with empty?
This way, Rails is forced to make the query. And the overhead is quite low since doing to_a.any?several times only does the query once.
Or better, as #Jordan suggested, use:
user.stories.exists?

Does Kaminari load all records when call page method later?

In my User model
def activities
Activity.where actor_type: self.class.name
end
When I call current_user.activities.page(params[:page]) in controller , I want to know it will load all activity records or not ?
ActiveRecord is lazy-loading - it will only call the SQL query when you actually access the data in the result set.
It won't load Activity data until you do something like .all or .each or something else that actually requires access to the data.
No, kaminari will not load all records at once. You can test it in the rails console:
current_user.activities.page(params[:page]).to_sql # => "SELECT \"activities\".* FROM \"activities\" WHERE \"activities\".\"user_id\" = 1 LIMIT 25 OFFSET 25"
As you can see, LIMIT and OFFSET are both involved into the SQL query. And of course, it's eager loading.

findind all using .all vs. where

In my controller i am getting all entries form a table like this
#enums = Enuemerations.all
Then later i want to search and get the name from by doing
#enums.find(107).name
I get an error
undefined method `name' for #<Enumerator:0xb5eb9d98>
So I tried it out in the rails console and found this working
Enumeration.where(false).find(107)
where this does not work
Enumeration.all.find(107)
Can someone explain to me how this works?
Thanks
Using Enumeration.all instantly queries the database returning an Array with all the Enumeration records (if you only want a single record this would be very inefficient). It no longer knows about the ActiveRecord methods:
> Enumeration.all.class
Enumeration Load (0.1ms) SELECT "enumerations".* FROM "enumerations"
=> Array
Calling find on an Array uses Enumerable#find which would need a different syntax, e.g:
enums = Enumeration.all
enum = enums.find { |e| e.id == 2 }
=> #<Enumeration id: 2, name: "...">
Using Enumeration.where(false) only returns a lazy ActiveRecord::Relation, it doesn't actually hit the database (yet), this allows you to chain extra ActiveRecord methods such as find in your example above.
> Enumeration.where(false).class
=> ActiveRecord::Relation
> Enumeration.where(false).find(2)
Enumeration Load (0.2ms) SELECT "enumerations".* FROM "enumerations" WHERE "enumerations"."id" = ? LIMIT 1 [["id", 2]]
=> #<Enumeration id: 2, name: "...">

Rails SQL efficiency for where statement

Is there a more efficient method for doing a Rails SQL statement of the following code?
It will be called across the site to hide certain content or users based on if a user is blocked or not so it needs to be fairly efficient or it will slow everything else down as well.
users.rb file:
def is_blocked_by_or_has_blocked?(user)
status = relationships.where('followed_id = ? AND relationship_status = ?',
user.id, relationship_blocked).first ||
user.relationships.where('followed_id = ? AND relationship_status = ?',
self.id, relationship_blocked).first
return status
end
In that code, relationship_blocked is just an abstraction of an integer to make it easier to read later.
In a view, I am calling this method like this:
- unless current_user.is_blocked_by_or_has_blocked?(user)
- # show the content for unblocked users here
Edit
This is a sample query.. it stops after it finds the first instance (no need to check for a reverse relationship)
Relationship Load (0.2ms) SELECT "relationships".* FROM "relationships" WHERE ("relationships".follower_id = 101) AND (followed_id = 1 AND relationship_status = 2) LIMIT 1
You can change it to only run one query by making it use an IN (x,y,z) statement in the query (this is done by passing an array of ids to :followed_id). Also, by using .count, you bypass Rails instantiating an instance of the model for the resulting relationships, which will keep things faster (less data to pass around in memory):
def is_blocked_by_or_has_blocked?(user)
relationships.where(:followed_id => [user.id, self.id], :relationship_status => relationship_blocked).count > 0
end
Edit - To get it to look both ways;
Relationship.where(:user_id => [user.id, self.id], :followed_id => [user.id, self.id], :relationship_status => relationship_blocked).count > 0