Rails CanCan, failing unless I have a Rails.logger.info --- why? - ruby-on-rails

If I have this:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
Rails.logger.info 'XXXX'
end
CanCan works properly but if I remove the logger, it fails:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
end
Any ideas what's going on here with CanCan? or my code? :) thanks

From the fine manual:
If the conditions hash does not give you enough control over defining abilities, you can use a block along with any Ruby code you want.
can :update, Project do |project|
project.groups.include?(user.group)
end
If the block returns true then the user has that :update ability for that project, otherwise he will be denied access. The downside to using a block is that it cannot be used to generate conditions for database queries.
Your first block:
can [:manage], GroupMember do |group_member|
wall_member.try(:user_id) == current_user.id
Rails.logger.info 'XXXX'
end
Will always return a true value because Rails.logger.info 'XXXX' returns "XXXX\n" (info is just a wrapper for add and you have to read the source to see what add returns as it isn't very well documented). Without the Rails.logger.info call, the block returns just:
wall_member.try(:user_id) == current_user.id
and that must be false for you.

Related

Rails/Ruby extract if block to helper or guard

In my Rails app I need to implement authentication for web app, I need to use an external resource to make it work. To do so I'm using custom Devise Strategies. After a tremendous amount of work, I finally managed to implement a code that covers all scenarios - the code is working but unfortunately my eyes bleed when I see the code below:
module Devise
module Strategies
class AwareLogin < Authenticatable
def authenticate!
# some logic
# (...)
if login.valid_password?(password) && aware_response.success?
success!(login)
elsif login.valid_password?(password) && !aware_response.success?
success!(login)
elsif login.id.nil? && aware_response.success?
login.set_user_tokens(aware_response)
success!(login)
elsif !login.valid_password?(password) && !aware_response.success?
raise ActiveRecord::Rollback
elsif !login.valid_password?(password) && aware_response.success?
fail!(:aware_auth)
end
rescue SupervisorRollback => s
#user_to_rollback = s.user_id
raise ActiveRecord::Rollback
end
end
end
end
end
end
Is there any way to replace that if block by something clearer like guard or even maybe external helper instead?
You can consolidate the logic a bit but given that the branches perform different actions you will still need some of the branching.
My recommended consolidation
def authenticate!
begin
if login.valid_password?(password) || (set_token = login.id.nil? && aware_response.success?)
login.set_user_tokens(aware_response) if set_token
success!(login)
else
aware_response.success? ? fail!(:aware_auth) : raise(ActiveRecord::Rollback)
end
rescue SupervisorRollback => s
#user_to_rollback = s.user_id
raise ActiveRecord::Rollback
end
end
Reasoning:
Your first 2 conditions only differ in their check of aware_response.success?; however whether this is true or false they perform the same action so this check is not needed.
Third branch performs 1 extra step of setting a token. Since this branch is unreachable unless !login.valid_password?(password) we have simply added an or condition to the first branch to conditionally set the token if this condition is true
The 4th and 5th conditions can be reduced to an else because we checked if login.valid_password?(password) is true in the first branch thus reaching this branch means it is false. Now the only difference is how we respond to aware_response.success? which I just converted to a ternary.

Conditional CanCanCan Abilities

I have a ClientServer that has many ClientApplications. I want users to only be able to destroy Servers that are associated with them. Every ClientApplication object has an application_owner_email that is matched against the current_user. If the emails match, they should have destroy permission for the associated Server.
In the abilities.rb, I have the following
if user.has_role?(:application_owner)
can :destroy, ClientServer.all.each do |server|
server.client_applications.each do |app|
app.application_owner_email == user.email
end
end
But this isn't working. I have set up similar conditions. For instance, the below condition works fine:
can :destroy, ClientApplication.all.each do |app|
app.application_owner_email == user.email
end
A user can only destroy ClientApplications where they are the application_owner.
Any help on this would be appreciated.
Enumerable#each returns object self, you probably want .all? or .any?:
can :destroy, ClientServer do |server|
server.client_applications.all?{|app| app.application_owner_email == user.email }
end
Note that all? returns true for empty array.
Also note that abilities with blocks cannot generate scopes and are usually slower.

Reset scopes set in config/initializers/doorkeeper.rb on Heroku

I'm trying to create a feature in my OAuth2 service where developers can create, read, and destroy scopes for use by their applications that use my service.
In order to do this, I've created a basic Scope model and I want Doorkeeper to update its #optional_scopes / #scopes with whatever scopes a user comes and creates/destroys. (Note: scopes can only be destroyed if they aren't in use.)
Note (TL;DR): This all works perfectly in development, but it isn't working in production on Heroku -- so the crux of the question is really surrounding how to update the instance variables inside of Doorkeeper that are normally set upon the app's initialization.... And if it's possible at all!
I've set the initializer to grab all the scopes in the DB and set them to optional_scopes.
In config/initializers/doorkeeper.rb:
Doorkeeper.configure do
...
default_scopes :public
optional_scopes( *Scope.where.not(name: 'public').map{ |s| s.name } )
...
end
I have a basic controller for my "CRD" of scopes which has a filter to reset the scopes list after one has been created or destroyed:
class ScopesController < ApplicationController
after_action :set_optional_scopes, only: [ :create, :destroy ]
...
def set_optional_scopes
Doorkeeper.configuration.instance_variable_set(
'#optional_scopes',
Scope.where.not(name: 'public').map{ |s| s.name }
)
end
end
In the view for my linked applications, I have a loop of the scopes which offers the user checkboxes for the scopes.
views/doorkeeper/applications/_form.html.erb:
<% Doorkeeper.configuration.optional_scopes.each do |scope| %>
<%= check_box_tag(
scope,
'true',
application_has_scope?( application, scope.to_s )
) %>
<%= label_tag(
scope,
scope.to_s,
class: 'no-style display-inline-block'
) %>
<br>
<% end %>
Note how I'm calling Doorkeeper.configuration.optional_scopes to populate the checkboxes.
Concerned with this code updating appropriately across Heroku instances, I also overwrote Doorkeeper's self.configuration method from:
module Doorkeeper
...
def self.configuration
#config || (fail MissingConfiguration)
end
...
end
to:
module Doorkeeper
def self.configuration
if #config
# Reset the scopes every time the config is called
#config.instance_variable_set(
'#scopes',
Scope.all.map{ |s| s.name }
)
#config
else
(fail MissingConfiguration)
end
end
end
So, as I said above, this is working well in development. However, in production it fails to update the list of checkboxes, which means that Doorkeeper.configuration.optional_scopes doesn't get appropriately reset after the create action.
Thanks so much for your time and any help!
Okay, well, in the process of writing this, I slowed down and figured out the solution, which was right in front of my nose...
In the override of Doorkeeper's self.configuration method, all I needed to do was reset optional_scopes instead of scopes as scopes gets defined as default_scopes + optional_scopes anyway.
So it looks like this:
def self.configuration
if #config
# Reset the scopes every time the config is called
#config.instance_variable_set(
'#optional_scopes',
Scope.where.not(name: 'public').map{ |s| s.name }
)
#config
else
(fail MissingConfiguration)
end
end
This caused all my tests to fail due to a NoMethodError for the super class of Doorkeeper::OAuth::Scopes then I realized I needed to rewrite that method to include an elsif for Array. So, here's that method:
module OAuth
class Scopes
def +(other)
if other.is_a? Scopes
self.class.from_array(all + other.all)
elsif other.is_a? Array
self.class.from_array(all + other)
else
super(other)
end
end
end
end
You can see the original here.
I hope all of this helps someone someday!

Rspec test passing only when there's a PUTS in the model

The puts statement must be having some kind of weird effect that I'm not seeing here...
I have an Order model. There's a callback on the model where the callback requires the model to be fully committed; i.e., I need to use an after_commit. However, the determinant of if the callback should run or not requires ActiveRecord::Dirty and therefore requires a before_save (or after_save, but I use before_save based on some other non-essential info).
I have combined the two thusly:
class Order
# not stored in DB, used solely to help the before_save to after_commit transition
attr_accessor :calendar_alert_type, :twilio_alerter
before_save
if self.calendar_alert_type.nil?
if self.new_record?
self.calendar_alert_type = "create, both"
elsif self.email_changed?
self.calendar_alert_type = "update, both"
elsif self.delivery_start_changed? || self.delivery_end_changed? || (type_logistics_attributes_modified.include? "delivery")
self.calendar_alert_type = "update, start"
elsif self.pickup_start_changed? || self.pickup_end_changed? || (type_logistics_attributes_modified.include? "pickup")
self.calendar_alert_type = "update, end"
end
end
puts "whatever"
end
after_commit do
if self.calendar_alert_type.present?
calendar_alert(self.calendar_alert_type)
end
end
end
def calendar_alert(alert_info)
puts "whatever"
alert_type = alert_info.split(",")[0].strip
start_or_end = alert_info.split(",")[1].strip
if start_or_end == "both"
["start","end"].each do |which_end|
Calendar.send(alert_type, which_end, self.id)
end
else
Calendar.send(alert_type, start_or_end, self.id)
end
end
All of the private methods and the ActiveRecord::Dirty statements are working appropriately. This is an example of a spec:
it "email is updated" do
Calendar.should_receive(:send).with("update", "start", #order.id).ordered
Calendar.should_receive(:send).with("update", "end", #order.id).ordered
find("[name='email']").set("nes#example.com")
find(".submit-changes").click
sleep(1)
end
it "phone is updated" do
... #same format as above
end
Literally all the specs like the above pass ONLY when EITHER puts statements is present. I feel like I'm missing something very basic here, just can't put my finger on it. It's super weird because the puts statement is spitting out random text...
*Note, I'm totally aware that should_receive should be expect_to_receive and that I shouldn't use sleep and that expectation mocks on feature tests aren't good. Working on updating the specs separately from bad code days, but these shouldn't be causing this issue... (feel free to correct me)
This behavior depends on your Rails version. Before Rails 5 you can return anything except false value to keep on running. A false will abort the before_* callback chain. puts 'whatever' returns a nil. So every thing works. Your if block seems to return a false (custom implemation for calendar_alert_type?). In this case the chain is holded.
With Rails 5 you have to throw(:abort) to stop callback handling.

payment_state and shipment_state not persisting in spree with custom payment gateway

I'm implementing a custom payment gateway in Spree 2.2. It's one of those gateways where you redirect to the gateway's own website to take payment, and then the bank redirects back to you with a bunch of get params.
I'm having an issue where the order's payment_state and shipment_state end up as null in the database, despite the fact that they are not null in the order object itself, if I put a debugger in the code. Calling order.save doesn't seem to help.
I've implemented a dirty hack to workaround it:
# This is a hack - for some reason the payment_state and shipment_state weren't being persisted
# and where being stored in the database as null. Really the spree checkout process
# should take care of this and we shouldn't have to set them manually.
# We must be doing something wrong...
order.update_attribute :payment_state, 'paid'
order.update_attribute :shipment_state, 'ready'
But I'd really like to know what the actual issue is - why aren't those states being persisted? (I should add, before I call the code above, the values for order.payment_state and order.shipment_state respectively are balance_due and pending - but that's another issue. If I can get them to save in any way, that's the main issue.
Any ideas what I'm doing wrong?
Full code for my controller and gateway is below.
class Spree::CommBankController < Spree::StoreController
def secure_payment
order = current_order
#order_info = 'Espionage Online order ' + order.number
payment_params = {
"Title" => 'Espionage Online',
"vpc_AccessCode" => payment_method.preferred_access_code,
"vpc_Amount" => (order.total * 100).to_i, # convert to cents
"vpc_Desc" => #order_info,
"vpc_MerchTxnRef" => order.number,
"vpc_Merchant" => payment_method.preferred_merchant_id_no,
"vpc_OrderInfo" => #order_info,
"vpc_ReturnURL" => secure_payment_callback_url(payment_method_id: payment_method.id),
}
payment_request = ::CommWeb::PaymentRequest.new(payment_params, payment_method.preferred_secure_secret)
redirect_to payment_request.url
end
def secure_payment_callback
# Next line - see http://stackoverflow.com/questions/4116545/how-do-i-get-only-the-query-string-in-a-rails-route
order = current_order
query_params = params.except(*request.path_parameters.keys)
payment_response = ::CommWeb::PaymentResponse.new(query_params, payment_method.preferred_secure_secret)
if !secure_hash_matches?(payment_response)
flash.notice = 'Error with payment - secure hash did not match. Please try again.'
redirect_to checkout_state_path(order.state)
return
end
payment = order.payments.create!({
:source => Spree::CommbankCheckout.create({
:params_hash => payment_response.params.to_s,
:success => payment_response.success?,
:desc => payment_response.description,
:trx_response_code => payment_response.trx_response_code,
:message => payment_response.message,
}),
:amount => order.total,
:payment_method => payment_method,
:response_code => payment_response.trx_response_code,
})
payment.pend
if payment_response.success?
# Set payment to completed after order.next because
# spree expects at least one incomplete payment to process an order to complete
order.next!
payment.complete
debugger
# This is a hack - for some reason the payment_state and shipment_state weren't being persisted
# and where being stored in the database as null. Really the spree checkout process
# should take care of this and we shouldn't have to set them manually.
# We must be doing something wrong...
order.update_attribute :payment_state, 'paid'
order.update_attribute :shipment_state, 'ready'
else
payment.failure
end
if order.complete?
flash.notice = Spree.t(:order_processed_successfully)
session[:order_id] = nil
redirect_to completion_route(order)
else
flash.notice = 'Error: ' + payment_response.message + '. Please try again.'
redirect_to checkout_state_path(order.state)
end
end
def secure_hash_matches? payment_response
payment_response.secure_hash_matches?
end
def payment_method
#payment_method ||= Spree::PaymentMethod.find(params[:payment_method_id])
end
def completion_route(order)
order_path(order)
end
end
and the gateway...
# Partly inspired from https://github.com/spree-contrib/spree-adyen (the hosted payment page class)
module Spree
class Gateway::CommBank < Gateway
preference :merchant_id_no, :string
preference :access_code, :string
preference :secure_secret, :string
def auto_capture?
true
end
# Spree usually grabs these from a Credit Card object but when using
# Commbank's 3 Party where we wouldn't keep the credit card object
# as that's entered outside of the store forms
def actions
%w{capture}
end
# Indicates whether its possible to void the payment.
def can_void?(payment)
!payment.void?
end
# Indicates whether its possible to capture the payment
def can_capture?(payment)
payment.pending? || payment.checkout?
end
def method_type
'commbank'
end
def capture(*args)
ActiveMerchant::Billing::Response.new(true, "", {}, {})
end
def source_required?
false
end
def provider_class
self.class
end
def provider
self
end
def purchase
# This is normally delegated to the payment, but don't do that. Handle it here.
# This is a hack copied from the Spree Better Paypal Express gem.
Class.new do
def success?; true; end
def authorization; nil; end
end.new
end
end
end
Check order.state_changes. Do they show changes to the two states?
I am encountering the same issue while using "spree-adyen". The order.state_changes shows that the payment_state and shipment_state have changed to ready. However, it doesn't persist in the order. This happens randomly with 10% of the orders. I am currently calling order.update! manually on the such order, but would really like to know as well what the issue is.
Also, I am not quite sure if order.update! is a good solution, as it executes a lot of queries and can be very expensive.
Umm. So apparently order.update! will solve my issue. Woops.
Still, a call to order.update! isn't something I've seen in other Spree Payment Gateway gems (https://github.com/spree-contrib/better_spree_paypal_express or https://github.com/spree-contrib/spree-adyen), so I'd be interested to know if I'm doing something really stupid. (I ended up noticing it in the code of https://github.com/coinbase/coinbase-spree/blob/master/app%2Fcontrollers%2Fspree%2Fcoinbase_controller.rb)

Resources