After upgrading CanCan 1.4 to CanCanCan 2.0, I can not anymore cache current_ability into memcached with Dalli.
I got error below :
Cache write: profils/11-20170821121414000000::ability
Marshalling error for key 'development:profils/11-20170821121414000000::ability': can't dump hash with default proc
You are trying to cache a Ruby object which cannot be serialized to memcached.
/usr/local/rvm/gems/ruby-2.4.0#rails5.1.1/gems/dalli-2.7.6/lib/dalli/server.rb:414:in `dump'
I store current_ability in memcached through Dalli by overriding CanCan.Ability::current_ability in app/controllers/application_controller.rb
def current_ability
Rails.cache.fetch("#{current_user.profil.cache_key}::ability") do
super
end
end
Why I store current_ability in cache ?
cancan slowing down page loads, Because I have a lot of custom actions in many controllers, each page reload all abilities...
Any pointers in regards to cacheing / optimizing / best practices would be most appreciated. Thanks!
update 1
Add app/models/ability.rb
class Ability
include CanCan::Ability
def initialize(user)
alias_action :edit, :to => :read
role = user.profil
if role.label == 'administrateur'
can :manage, :all
else
role.rights.each do |right|
model = right.resource.constantize rescue nil
next unless model
[:read, :create, :update, :destroy].each do |capability|
can capability, model if right.send("#{capability}?")
end
end
# Custom actions
can([:collective, :collective_week, :presence, :day_details, :day_details_json], Movement) if can?(:read, Movement)
can(:quick_add, Movement) if can?(:create, Movement)
can([:actions, :set_action, :choose, :previous, :families_addresses, :ios,:families_members,:potential_relationships,:list_families,:list_decisions], Person) if can?(:read, Person)
can(:addresses, Family) if can?(:read, Family)
...
...
...
end
end
end
Update 2
Print an object of Ability (below is just a snippet of ability but the stucture is the same) That's the data that I try to store in memcached :
#<Ability:0x000000093030a0
#aliased_actions={:read=>[:index, :show, :edit], :create=>[:new], :update=>[:edit]},
#expanded_actions=
{[:index, :show, :edit]=>[:index, :show, :edit]},
#rules=
[#<CanCan::Rule:0x00000008fb3e18
#actions=[:read],
#base_behavior=true,
#block=nil,
#conditions={},
#match_all=false,
#subjects=[AcceptanceReason(id: integer, label: string, created_at: datetime, updated_at: datetime, institution_position: integer, acceptance_number: integer, department_type_id: integer)]>,
#<CanCan::Rule:0x00000008fb3760
#actions=[:create],
#base_behavior=true,
#block=nil,
#conditions={},
#match_all=false,
#subjects=[AcceptanceReason(id: integer, label: string, created_at: datetime, updated_at: datetime, institution_position: integer, acceptance_number: integer, department_type_id: integer)]>],
#rules_index=
{AcceptanceReason(id: integer, label: string, created_at: datetime, updated_at: datetime, institution_position: integer, acceptance_number: integer, department_type_id: integer)=>[0, 1, 2, 3],
Activity(id: integer, created_at: datetime, updated_at: datetime, institution_id: integer, label: string, activity_type_id: integer, employee_id: integer, department_id: integer, group_id: integer, beginning_at: date, ending_at: date, location: text, comments: text, objectif: text, evaluation: text, colour: string, collective_intervention_mean_id: integer, collective_intervention_status_id: integer, archive: boolean, activity_beginning_time: time, activity_ending_time: time, moyens_materiels: text, transports: text, autres_intervenants: text, budget: text, frequency: string)=>
[4, 5, 6, 7, 645],
ActivityCode(id: integer, label: string, created_at: datetime, updated_at: datetime)=>[8, 9, 10]}
Related
I have three models, related with has_many :through associations:
class Account < ApplicationRecord
has_many :account_owners
has_many :employees, through: account_owners
def is_owned_or_belongs_to_team_of_employees(employee)
employee.team.any? { |m| employees.include?(m) }
end
end
class AccountOwner < ApplicationRecord
belongs_to :account
belongs_to :employee
end
class Employee < ApplicationRecord
has_many :account_owners
has_many :accounts, through: :account_owners
def team
self.class.where(
'id IN (?)',
self. class.find_by_sql(['WITH RECURSIVE search_tree(id, path) AS (
SELECT id, ARRAY[id]
FROM employees
WHERE id = ?
UNION ALL
SELECT employees.id, path || employees.id
FROM search_tree
JOIN employees ON employees.manager_id = search_tree.id
WHERE NOT employees.id = ANY(path)
)
SELECT id FROM search_tree ORDER BY path',
self.id])
).order(:id)
end
end
I'm manually testing, in the Rails console in my development environment (using some fixtures that I first loaded on the database), the Account#is_owned_or_belongs_to_team_of_employees method.
When I run the method in the console this is what happens:
> a = Account.first
=> #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55">
> e = Employee.find_by(first_name: 'Elena')
=> #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>
> e.team
=> #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]>
> a.is_owned_or_belongs_to_team_of e
=> nil
> a.is_owned_or_belongs_to_team_of e
=> true
As you can see, the method returns nil (wrong!) the first time, and returns true (correct!) the following times.
The amazing thing is that I can correct the problem if I define the method like this:
def is_owned_or_belongs_to_team_of employee
puts "employees are #{employees.inspect}"
employee.team.any? { |m| employees.include?(m) }
end
Now the execution is correct, and the method returns consistently the same result (true in my example):
> a = Account.first
=> #<Account id: 534788375, name: "Sales Rep 1 (elena)-owned account", code: "EEE", created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55">
> e = Employee.find_by(first_name: 'Elena')
=> #<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>
> e.team
=> #<ActiveRecord::Relation [#<Employee id: 701979064, first_name: "Elena", last_name: "López", manager_id: 1069403509, created_at: "2018-07-15 09:41:55", updated_at: "2018-07-15 09:41:55", mobile: nil, work: nil>]>
> a.is_owned_or_belongs_to_team_of e
=> true
> a.is_owned_or_belongs_to_team_of e
=> true
If I remove the puts statement, we are back to square one: the method returns nil the first time, and true the following times.
And, amazingly, if I keep the puts statement but remove the inspect (that is, I just do puts "employees are #{employees}" we are also back to square one: nil the first time, and true the following times.
Any idea? What is going on here?
By the way, I'm running Ruby 2.5.1 y Rails 5.2.0.
I'm glad I stumbled upon this Unicorn of a bug!
After debugging this for hours, I found out the following:
any? had new changes in rails 5.2 release that was supposed to delegate it to Enumerable
the surprising thing, that if you put a binding.pry in the implementation of any? and call super it returns true even the first time and then the method returns nil. ~/.rbenv/versions/2.5.1/lib/ruby/gems/2.5.0/gems/activerecord-5.2.0/lib/active_record/relation.rb # line 228 ActiveRecord::Relation#any?:
if you add to employee.team .to_a everything works consistently.
if you put any? { |_| true } it returns true.
If you check for the value inside the block for include? it returns true but any? still returns nil!!!
If you avoid resolving the has_may through association (by calling .to_a before the block) or even using a different association inside the any? block everything works as expected.
using any other ruby version fixes the problem.
Summary
The problem was introduced in ruby 2.5.1 rails v5.2.0 when ActiveRecord::Relation started to include Enumerable.It happens with %w(none? any? one? many?) while trying to resolve a has many through association in its block.
This is driving me insane. I've stripped this down to the bare minimum without losing context (I think!)
All I am trying to do is check that when I update a value and save it to the database, that the value was saved. I want to do this because I need to write some other code that conditionally prevents this in the before_save callback, and I can't test that until I'm sure this is working!
The factories and the spec are below, I'm sure its something really stupid but I just can't figure it out...
FactoryGirl.define do
factory :programme do
name 'Trainee Programme'
end
factory :membership do
programme
end
factory :specialty do
sequence(:name) { |n| "Specialty #{n}" }
end
factory :user do
sequence(:email) { |n| "factorygirl-user-#{n}#remailer.org" }
password 'password'
password_confirmation 'password'
factory :trainee, class: User do
sequence(:email) { |n| "factorygirl-trainee-#{n}#remailer.org" }
name 'Factory Girl Trainee'
after(:create) do |user|
FactoryGirl.create(:membership, user: user, start_date: 1.day.ago)
end
end
end
end
describe Membership do
let(:trainee) { FactoryGirl.create(:trainee) }
it 'sets specialty' do
puts trainee.current_membership.inspect
trainee.current_membership.specialty = specialty
puts trainee.current_membership.inspect
trainee.current_membership.save!
puts trainee.current_membership.inspect
expect(trainee.current_membership.specialty).to eq(specialty)
end
end
The spec is failing because the expect sees a nil value. When I run the code the debug output I get is:
#<Membership id: 11, user_id: 11, programme_id: 11, start_date: "2015-03-10", end_date: nil, created_at: "2015-03-11 22:02:51", updated_at: "2015-03-11 22:02:51", options: {}, specialty_id: nil, membership_type_id: nil>
#<Membership id: 11, user_id: 11, programme_id: 11, start_date: "2015-03-10", end_date: nil, created_at: "2015-03-11 22:02:51", updated_at: "2015-03-11 22:02:51", options: {}, specialty_id: nil, membership_type_id: nil>
#<Membership id: 11, user_id: 11, programme_id: 11, start_date: "2015-03-10", end_date: nil, created_at: "2015-03-11 22:02:51", updated_at: "2015-03-11 22:02:51", options: {}, specialty_id: nil, membership_type_id: nil>
So its as if the assignment of specialty never happens??
Try reloading trainee, e.g.
expect(trainee.reload.current_membership.specialty).to eq(specialty)
Thanks to BroiState and Mori giving me some pointers I was able to establish that it was related to persistence (in particular one of my object methods not respecting it!)
The code for trainee.current_membership is as follows:
def current_membership
return unless memberships.current.any?
memberships.current.first
end
which uses these related scopes in Membership...
scope :started, -> { self.where("#{table_name}.#{_start_field}::TIMESTAMP < '#{Time.now}'") }
scope :not_ended, -> { self.where("#{table_name}.#{_end_field} IS NULL OR #{table_name}.#{_end_field}::TIMESTAMP > '#{Time.now}'") }
scope :current, -> { self.started.not_ended }
So each call to trainee.current_membership was giving me a new instance of the 'current' membership record
by explicitly using the same object the spec passed fine, i.e.:
it 'sets specialty' do
membership = trainee.current_membership
membership.specialty = specialty
membership.save!
expect(membership.specialty).to eq(specialty.reload)
end
The only reason I could think of is that speciality is a new, non-persisted record. Since membership belongs_to :speciality and membership is already persisted, it will not save associated object neither on assignment nor on save. In short, make sure that speciality is saved before creating association.
previous rails 4 I had in a model
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation
...
end
But now strong_parameters replaced the protected_attributes so I comment it and use permit.
Now I discovered that I can access attribute without permitting it.
In rails c I manage to do this:
2.0.0p247 :002 > User.new(admin: "1")
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil, password_digest: nil, remember_token: nil, admin: true>
2.0.0p247 :016 > user = User.new(name: 'Nir', email: 'nir#example.com', password: 'foobar', password_confirmation: 'foobar', admin: "1")
=> #<User id: nil, name: "Nir", email: "nir#example.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$xVnY8ydd5SoaLVipK5j4Del40FrOmu4bKypGjBEwvms7...", remember_token: nil, admin: true>
When obviously I should not be able to set and change the admin attribute. Only user.toggle(:admin) should be able to.
So what am I not understanding or should do right.
And how to make this test pass:
describe "accessible attributes" do
it "should not have allow access to admin" do
expect do
User.new(admin: "1")
end.to raise_error(ActiveModel::MassAssignmentSecurity::Error)
end
end
To prevent a user from setting the admin property, you should not add it as a parameter of the permit method.
params.require(:user).permit(:name, :whatever_else_you_allow)
Keywords in this are: params (it deals with parameters) and permit (you tell rails which properties to allow for).
Strong Parameters will make Action Controller parameters forbidden to be used in Active Model mass assignment until they have been whitelisted. In your test however, you set the property directly on the model. Nothing prevents you from doing that.
So I am trying to ensure that a callback happens on save... My test is:
user = create_user
login user
visit new_post_path
fill_in "post_title", :with => "my post"
user.expects(:publish_post!).once
click_button "post_submit"
and I get:
1) Failure:
test: Post should place a post. (PostTest)
[test/integration/post_test.rb:72:in __bind_1311708168_640179'
/test/test_helper.rb:37:inexpectation_on'
test/integration/post_test.rb:70:in `__bind_1311708168_640179']:
not all expectations were satisfied
unsatisfied expectations:
- expected exactly once, not yet invoked: #.publish_post!(any_parameters)
satisfied expectations:
- allowed any number of times, not yet invoked: Post(id: integer, title: string, method_of_exchange: string, you_tube_url: string, lat: decimal, lng: decimal, bounty: integer, distance: integer, user_id: integer, consider_similar_offers: boolean, description: text, created_at: datetime, updated_at: datetime, address: string, city: string, state: string, zip: string, country: string, county: string, category_id: integer, slug: string, status: string, popularity: integer, delta: boolean, share_count: integer, needs_reply: decimal, needs_offer: decimal, district: string).facets(any_parameters)
- allowed any number of times, not yet invoked: {}.for(any_parameters)
- expected never, not yet invoked: #.publish_post!(any_parameters)
Yet my post model does:
class Post < ActiveRecord::Base
belongs_to :user
after_create :publish
def publish
user.publish_post!
end
end
and my posts controller's create action does indeed assign the user the the post...
class PostsController < ApplicationController
def create
post = Post.new(params[:post])
post.user = current_user
post.save
end
end
...
The functionality works fine when testing manually.. So I don't get why this is not working when automated?
I really wouldn't try to use mocking expectations in an integration test. That kind of testing belongs in low-level unit tests for the Post class itself. Try to think about integration tests as just looking at the system from the outside, as a user would. What effect does the callback have? Can you just check for the outcome of that effect?
Here is the trivial inheritance (STI) setup:
class Parent < ActiveRecord::Base
end
class Daughter < Parent
end
class Son < Parent
end
Quick try in console. Expecting Parent.subclasses to return two subclasses, but got nothing!
ruby-1.9.2-p0 > Parent.subclasses
=> []
Also, calling
ruby-1.9.2-p0 > Daughter.subclasses
=> []
,which correctly returns no children, makes Parent start recognizing Daughter as subclass:
ruby-1.9.2-p0 > Parent.subclasses
=> [Daughter(id: integer, type: string, created_at: datetime, updated_at: datetime)]
The same works for another subclass:
ruby-1.9.2-p0 > Son.subclasses
=> []
ruby-1.9.2-p0 > Parent.subclasses
=> [Daughter(id: integer, type: string, created_at: datetime, updated_at: datetime), Son(id: integer, type: string, created_at: datetime, updated_at: datetime)]
This is rails 3, but the same behavior exhibits on 2.3.10
This is a known issue
One workaround is to register the subclasses at the bottom of the base class file.
%w(daughter son).each {|r| require_dependency r } if Rails.env.development?
I suppose it's a autoloading issue. The class are load only when you really need. You can try by example with the cache_classes = true configuration and see if this result is allways the same. I suppose is not.