Reuse same object in other methods within same model - ruby-on-rails

Using Rails 3.2. I have the following code:
# photo.rb
class Photo < ActiveRecord::Base
before_create :associate_current_user
after_save :increase_user_photos_count
after_destroy :decrease_user_photos_count
private
def associate_current_user
current_user = UserSession.find.user
self.user_id = current_user.id
end
def increase_user_photos_count
current_user = UserSession.find.user
User.increment_counter(:photos_count, current_user.id)
end
def decrease_user_photos_count
current_user = UserSession.find.user
User.decrement_counter(:photos_count, current_user.id)
end
end
Before a new record is created, it searches for the current_user. This is alright if it's just 1 new record at a time. But if there are 100 records to be created, it's gonna search for the same current_user 100 times. There is definitely performance issue.
I don't want it to keep finding the current user every time a record is created/photos_count updated, etc.
After refactoring, does this affect other users who are also uploading their photos using their accounts?
Note: For some reasons, I can't use the counter_cache and photos_controller.rb because I am following this example: http://www.tkalin.com/blog_posts/multiple-file-upload-with-rails-3-2-paperclip-html5-and-no-javascript
Thanks.

Use this
def current_user
#current_user ||= UserSession.find.user
end
This will cache the value in the instance variable #current_user unless it's nil (first time in the request), in which case it will set it.

Related

Rails expire low level cache on update

I am using Rails low level cache in my controller but I don't know how to expire the cache when record is updated. Below is snippets of my controller
def show
#user = Rails.cache.fetch("users/params[:id]", expires_in: 2.minutes) do
User.find(params[:id])
end
end
So rails is creating cache fine. But when I update the record I want to expire old cache and create new one. e.g
User.first.touch
I found that rails have cache_key_with_version but I am not sure how to use that with my example
#user = User.first
#user.cache_key_with_version #=> "users/1-20220316023452830286"
I won't have #user object at the first call in my controller so I am not sure how to use #user.cache_key_with_version as my key.
#user = Rails.cache.fetch(#user.cache_key_with_version)
Above code will not work as #user is nil at this stage.
One way I can think of is use after_save callback in model to delete cache key on save. Some thing like this
class User < ApplicationRecord
after_save :delete_cache_key
private
def delete_cache_key
Rails.cache.delete("users/#{self.id}")
end
end
But may be there is better way to solve this.
If you want Rails to get expired when record update, add cache_key_with_version to your cache key, so the controller will be like
def show
#user = User.find(params[:id])
Rails.cache.fetch("users/#{#user.cache_key_with_version}", expires_in: 2.minutes) do
# some other query that need to be done
end
end
loading a record should not be a heavy load, if it is, than use .select(:id, :updated_at) may be a choice
def show
user_cache_key = User.select(:id, :updated_at).find(params[:id]).cache_key_with_version
#user = Rails.cache.fetch("users/#{user_cache_key}", expires_in: 2.minutes) do
User.find(params[:id])
end
end
cache key is made with updated_at and id, so we only these two column to make a cache key with version. When record update, it will update updated_at, too, the previous cache will be discarded

Comparing values of 2 columns from 2 tables and counting the total matches in rails

I have 2 tables in my rails application one of them is Products and the other is Users.
In the products table I have a created_by_id which is an integer that matches the id of the User who created it. I need to display a running total of the Products and I am having an issue doing so.
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
def ensure_logged_in
unless current_user
flash[:alert] = "Please log in"
redirect_to root_path
end
end
def totalprods
Product.all.joins(:users).where("created_by_id LIKE current_user.id").count
end
helper_method :current_user
private
def current_user
#current_user ||= User.find(session[:user_id]) if session[:user_id]
end
end
i'm new to rails so i am sorry if i am screwing something up
In your app/models/user.rb file, add:
has_many :products, foreign_key: 'created_by_id'
Then you can use:
current_user.products.count
PS: It's a good idea to call that column user_id instead of created_by_id since Rails can guess the association column if it's in the format <model_lower_case>_id.
Remove the quotes around 'current_user.id'. Also, have you defined a relationship between your user and product models?
Based off your question you could do it one of 2 ways
The Long Way:
def totalProds
count = 0
#products = Product.all
#products.each do |product|
count = count + 1
end
return count
end
If your models are setup properly you should be able to use the short method which would be
def totalProds
#products = Product.all
count = #products.count
return count
end
The second method would be the preferred unless you had to do something in conjunction with the count being added to your counter. Ruby offers a lot of small functions like this already.

Cache object in model for multiple operation in Rails

Using Rails 3.2. I have the following code:
# photo.rb
class Photo < ActiveRecord::Base
after_create :associate_current_user
private
def associate_current_user
current_user = UserSession.find.user
self.user_id = current_user.id
end
end
Before a new record is created, it searches for the current_user. This is alright if it's just 1 new record at a time. But if there are 100 records to be created, it's gonna search for the same current_user 100 times. There is definitely performance issue.
Is there a way I can cache the object for similar operation, or any suggestion?
Thanks.
you can do memoization, something as follow
class Photo < ActiveRecord::Base
extend ActiveSupport::Memoizable
after_create :associate_current_user
private
def associate_current_user
current_user = UserSession.find.user
self.user_id = current_user.id
end
associate_current_user
end

after_save callback to set the updated_by column to the current_user

I would like to use an after_save callback to set the updated_by column to the current_user. But the current_user isn't available in the model. How should I do this?
You need to handle it in the controller. First execute the save on the model, then if successful update the record field.
Example
class MyController < ActionController::Base
def index
if record.save
record.update_attribute :updated_by, current_user.id
end
end
end
Another alternative (I prefer this one) is to create a custom method in your model that wraps the logic. For example
class Record < ActiveRecord::Base
def save_by(user)
self.updated_by = user.id
self.save
end
end
class MyController < ActionController::Base
def index
...
record.save_by(current_user)
end
end
I have implemented this monkeypatch based on Simone Carletti's advice, as far as I could tell touch only does timestamps, not the users id. Is there anything wrong with this? This is designed to work with a devise current_user.
class ActiveRecord::Base
def save_with_user(user)
self.updated_by_user = user unless user.blank?
save
end
def update_attributes_with_user(attributes, user)
self.updated_by_user = user unless user.blank?
update_attributes(attributes)
end
end
And then the create and update methods call these like so:
#foo.save_with_user(current_user)
#foo.update_attributes_with_user(params[:foo], current_user)

How to get the current_user in a model observer?

Given the following models:
Room (id, title)
RoomMembers (id, room_id)
RoomFeed, also an observer
When a Room title is updated, I want to create a RoomFeed item, showing who the user is who made the update.
#room.update_attributes(:title => "This is my new title")
Problem is in my observer for RoomFeed:
def after_update(record)
# record is the Room object
end
The is no way for me to get the user.id of the person who just made the update. How do I go about doing that? is there a better way to do the update so I get the current_user?
I think what you are looking for is, room.updated_by inside your observer. If you don't want to persist the updated_by, just declare it as an attr_accessor. Before you push the update, make sure you assign the current_user to updated_by, may be from you controller.
This is a typical "separation of concern" issue.
The current_user lives in the controller and the Room model should know nothing about it. Maybe a RoomManager model could take care of who's changing the name on the doors...
Meanwhile a quick & dirty solution would be to throw a (non persistant) attribute at Room.rb to handle the current_user....
# room.rb
class Room
attr_accessor :room_tagger_id
end
and pass your current_user in the params when updating #room.
That way you've got the culprit! :
def after_update(record)
# record is the Room object
current_user = record.room_tagger_id
end
Create the following
class ApplicationController
before_filter :set_current_user
private
def set_current_user
User.current_user = #however you get the current user in your controllers
end
end
class User
...
def self.current_user
##current_user
end
def self.current_user= c
##current_user = c
end
...
end
Then use...
User.current_user wherever you need to know who is logged in.
Remember that the value isn't guaranteed to be set when your class is called from non-web requests, like rake tasks, so you should check for .nil?
I guess this is a better approach
http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
Update user.rb
class User < ActiveRecord::Base
cattr_accessor :current
end
Update application_controller.rb
class ApplicationController
before_filter :set_current_user
private
def set_current_user
User.current = current_user
end
end
Then you can get logged user by User.current anywhere. I'm using this approach to access user exactly in observers.

Resources