I've got a route constraint that matches column values. Works fine, but it seems to cache the values so that new values don't match. How can I force a reload for this class?
class ClientCodeConstraint
def matches?(request)
#client_code = request.path_parameters[:client_code]
users.each { |u| return true if #client_code == u.client_code }
false
end
private
def users
#users ||= User.all
end
end
I need to force update it somehow.
First of all, your code seems to be very bad. If it possible, you should make just one DB query instead of retrieving all users.
class ClientCodeConstraint
def matches?(request)
User.where(client_code: request.path_parameters[:client_code]).any?
end
end
I think the problem is here #users ||= User.all. You are caching User.all result in instance variable, so it does not updated. You don't need to use instance variable at all. Choose your users method to:
def users
User.all
end
Or if it possible, just use my solution.
Related
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
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.
I have a model User. I want a function that sets the attribute emotion to equal happy for each user record.
class User < ActiveRecord::Base
attr_accessor :emotion
def self.add_a_happy_emotion
users = User.all
users.each do |user|
user.emotion = "happy"
end
users
end
end
When I call > User.add_a_happy_emotion in my console, the users do not have any emotion attribute. They will only have the default model attributes. Will an attribute not be set if it is called in a do loop? Or is this related to attr_accessor? Or maybe am I missing something else?
If you are using Rails 4. User.all will return an active record relation and not an array.
So you will have to modify your code to get users will updated attribute.
def self.add_a_happy_emotion
users = User.all
result_users = users.map do |user|
user.emotion = "happy"
user
end
result_users
end
may be it is returning the cached copy. How about this
def self.add_a_happy_emotion
users = User.all
users.each do |user|
user.emotion = "happy"
end
end
2 problems with it:
1 - Your attr_accessor is overriding Rails'. You really don't need it in Rails. Just put the attributes of your model in the migration and you'll be alright.
2 - If you modify the user objects and don't save them, the changes will never be persisted. You can use update_attribute for this purpose.
So you'll end up with something like:
class User < ActiveRecord::Base
#no more attr_accessor here
def self.add_a_happy_emotion
users = User.all
users.each do |user|
user.update_attribute(:emotion, "happy") #this persists the entities to the DB
end
end
end
I have a Rails helper method...
def current_user
#user = User.find(cookies[:user_id]) if cookies[:user_id]
end
And I want to add a non-database property to it...
self.custom_property = true
So I can access it in my view.
<%= current_user.custom_property %>
But, Rails says "Undefined method: custom_property"
Here's the full code:
def current_user
#user = User.find(cookies[:user_id]) if cookies[:user_id]
#user.ribbon_array ||= []
self.is_moderator = #user.ribbon_array.include?(1) ? true : false
end
I'd like to do it like this so in my view I can check current_user.is_moderator. Elsewhere in my code I have an #is_moderator variable that's specific to each page, but this one would be used across the whole app and specific to the current_user as opposed to the user on the profile.
To accomplish this
I'd like to do it like this so in my view I can check
current_user.is_moderator. Elsewhere in my code I have an
#is_moderator variable that's specific to each page, but this one
would be used across the whole app and specific to the current_user as
opposed to the user on the profile.
I recommend you to implement a method on your user model called
def is_moderator_of(page)
# returns true or false here
end
Or better yet, use a authorization gem like cancan
EDIT:
Also, this
#user = User.find(cookies[:user_id]) if cookies[:user_id]
#user.ribbon_array ||= []
will generate "Method not found: ribbon_array for nil" if there is no cookies[:user_id]
Edit
My bad without too much thought. In this case maybe a better solution is to add another helper method. Helpers themselves are not so object oriented, so you should be able to tolerate this kind of solution as well.
def current_user
#current_user = User.find(cookies[:user_id]) if cookies[:user_id]
end
def is_moderator?
ribbon = #current_user.ribbon_array || []
ribbon.include?(1)
end
In your app/models/user.rb file:
class User << ActiveRecord::Base
attr_accessor :custom_property
...
end
Then your view code should work.
Alternatively, you could just set #custom_property = true in the helper, and use #custom_property in the view.
in my project.rb model, I'm trying to create a scope with a dynamic variable:
scope :instanceprojects, lambda {
where("projects.instance_id = ?", current_user.instance_id)
}
I get the following error:
undefined local variable or method `current_user' for #<Class:0x102fe3af0>
Where in the controller I can access current_user.instance_id... Is there a reason the model can't access it and a way to get access? Also, is this the right place to create a scope like the above, or does that belong in the controller?
This doesn't make much sense, as you already pointed. The current_user doesn't belong to model logic at all, it should be handled on the controller level.
But you can still create scope like that, just pass the parameter to it from the controller:
scope :instanceprojects, lambda { |user|
where("projects.instance_id = ?", user.instance_id)
}
Now you can call it in the controller:
Model.instanceprojects(current_user)
The already accepted answer provides a really correct way to achieve this.
But here's the thread-safe version of User.current_user trick.
class User
class << self
def current_user=(user)
Thread.current[:current_user] = user
end
def current_user
Thread.current[:current_user]
end
end
end
class ApplicationController
before_filter :set_current_user
def set_current_user
User.current_user = current_user
end
end
This works as expected, however it can be considered dirty, because we basically define a global variable here.
Ryan Bates lays out a pretty safe way to implement this kind of strategy in this railscast
You can browse the source code here
Here he creates a current_tenant method, but you could easily substitute current_user instead.
Here are the key bits of code...
#application_controller.rb
around_filter :scope_current_tenant
private
def current_tenant
Tenant.find_by_subdomain! request.subdomain
end
helper_method :current_tenant
def scope_current_tenant
Tenant.current_id = current_tenant.id
yield
ensure
Tenant.current_id = nil
end
#models/tenant.rb
def self.current_id=(id)
Thread.current[:tenant_id] = id
end
def self.current_id
Thread.current[:tenant_id]
end
Then in the model you can do something like...
default_scope { where(tenant_id: Tenant.current_id) }
You don't need to use scopes. If you have set the appropriate associations in models, following piece of code placed in controller should do the trick:
#projects = current_user.instance.projects