Pundit Policies with Joins - ruby-on-rails

I have churches that use songs.
With a specific song id I am trying to get the most recent usage date and the total of usages limited by whichever church the user belongs to.
#usages = Usage.select("MAX(services.date) as date", :song_name, :song_id, "count(song_id) as count_song_id").joins(:service, :song).where(:services => {church_id: current_user.church_id}).group(:song_name, :song_id).order("count_song_id DESC")
The above code seems to be working but I've now started implementing Pundit authorisation and have run into some difficulties. My scope policy is very simple:
class Scope < Scope
def resolve
if user.admin?
scope.all
else
scope.where church_id: current_user.church_id
end
end
end
The problem is how to actually use it with joins. This doesn't seem right but I'm kind of at a loss:
#usages = policy_scope Usage.select("MAX(services.date) as date", :song_name, :song_id, "count(song_id) as count_song_id").joins(:service, :song).group(:song_name, :song_id).order("count_song_id DESC")

So what has ended up working (I think) is:
#usages = policy_scope(Usage.joins :service)
.select("MAX(services.date) as date", :song_name, :song_id, "count(song_id) as count_song_id")
.joins(:service, :song)
.group(:song_name, :song_id)
.order("count_song_id DESC")
The key being policy_scope(Usage.joins :service)

Related

What is the best way to reuse a scope in Rails?

I'm confused to reuse or writing a new scope.
for example,
one of my methods will return future subscription or current subscription or sidekiq created subscriptions.
as scopes will look like:
scope :current_subscription, lambda {
where('(? between from_date and to_date) and (? between from_time and to_time)', Time.now, Time.now)
}
scope :sidekiq_created_subscription, lambda {
where.not(id: current_subscription).where("(meta->'special_sub_enqueued_at') is not null")
}
scope :future_subscription, lambda {
where.not(id: current_subscription).where("(meta->'special_sub_enqueued_at') is null")
}
so these were used for separate purposes in different methods, so for me what I tried is to check whether a particular account record will come under which of three subscriptions.
so I tried like:
def find_account_status
accounts = User.accounts
name = 'future' if accounts.future_subscription.where(id: #account.id).any?
name = 'ongoing' if accounts.current_subscription.where(id: #account.id).any?
name = 'sidekiq' if accounts.sidekiq_enqued_subscription.where(id: #account.id).any?
return name
end
so here what my doubt is, whether using like this is a good way, as here we will be fetching the records based on the particular subscriptions and then we are checking whether ours is there or not.
can anyone suggest any better way to achieve this?
Firstly, you are over using the scopes here.
The method #find_account_status will execute around 4 Queries as below:
Q1 => accounts = User.accounts
Q2 => accounts.future_subscription
Q3 => accounts.current_subscription
Q4 => accounts.sidekiq_enqued_subscription
Your functionality can be achived by simply using the #account object which is already present in memory as below:
Add below instance methods in the model:
def current_subscription?
# Here I think just from_time and to_time will do the work
# but I've added from_date and to_date as well based on the logic in the question
Time.now.between?(from_date, to_date) && Time.now.between?(from_time, to_time)
end
def future_subscription?
!current_subscription? && meta["special_sub_enqueued_at"].blank?
end
def sidekiq_future_subscription?
!current_subscription? && meta["special_sub_enqueued_at"].present?
end
#find_account_status can be refactored as below:
def find_account_status
if #account.current_subscription?
'ongoing'
elsif #account.future_subscription?
'future'
elsif #account.sidekiq_future_subscription?
'sidekiq'
end
end
Additionally, as far as I've understood the code, I think you should also handle a case wherein the from_date and to_date are past dates because if that is not handled, the status can be set based on the field meta["special_sub_enqueued_at"] which can provide incorrect status.
e.g. Let's say that the from_date in the account is set as 31st Dec 2021 and meta["special_sub_enqueued_at"] is false or nil.
In this case, #current_subscription? will return false but #future_subscription? will return true which is incorrect, and hence the case for past dates should be handled.

How to refactor complex method in Rails model with Rspec?

I have the following complex method. I'm trying to find and implement possible improvements. Right now I moved last if statement to Access class.
def add_access(access)
if access.instance_of?(Access)
up = UserAccess.find(:first, :conditions => ['user_id = ? AND access_id = ?', self.id, access.id])
if !up && company
users = company.users.map{|u| u.id unless u.blank?}.compact
num_p = UserAccess.count(:conditions => ['user_id IN (?) AND access_id = ?', users, access.id])
if num_p < access.limit
UserAccess.create(:user => self, :access => access)
else
return "You have exceeded the maximum number of alotted permissions"
end
end
end
end
I would like to add also specs before refactoring. I added first one. How should looks like others?
describe "#add_permission" do
before do
#permission = create(:permission)
#user = create(:user)
end
it "allow create UserPermission" do
expect {
#user.add_permission(#permission)
}.to change {
UserPermission.count
}.by(1)
end
end
Here is how I would do it.
Make the check on the Access more like an initial assertion, and raise an error if that happens.
Make a new method to check for an existing user access - that seems reusable, and more readable.
Then, the company limit is more like a validation to me, move this to the UserAccess class as a custom validation.
class User
has_many :accesses, :class_name=>'UserAccess'
def add_access(access)
raise "Can only add a Access: #{access.inspect}" unless access.instance_of?(Access)
if has_access?(access)
logger.debug("User #{self.inspect} already has the access #{access}")
return false
end
accesses.create(:access => access)
end
def has_access?(access)
accesses.find(:first, :conditions => {:access_id=> access.id})
end
end
class UserAccess
validate :below_company_limit
def below_company_limit
return true unless company
company_user_ids = company.users.map{|u| u.id unless u.blank?}.compact
access_count = UserAccess.count(:conditions => ['user_id IN (?) AND access_id = ?', company_user_ids, access.id])
access_count < access.limit
end
end
Do you have unit and or integration tests for this class?
I would write some first before refactoring.
Assuming you have tests, the first goal might be shortening the length of this method.
Here are some improvements to make:
Move the UserAccess.find call to the UserAccess model and make it a named scope.
Likewise, move the count method as well.
Retest after each change and keep extracting until it's clean. Everyone has a different opinion of clean, but you know it when you see it.
Other thought, not related to moving the code but still cleaner :
users = company.users.map{|u| u.id unless u.blank?}.compact
num_p = UserAccess.count(:conditions => ['user_id IN (?) AND access_id = ?', users, access.id])
Can become :
num_p = UserAccess.where(user_id: company.users, access_id: access.id).count

selecting certain users in rails

I'm writing a zombie survival app, and I'm trying to select all my users marked "alive" where :alive is a boolean.
I was writing a private method in my users controller but can't get the ruby right, does anyone have a pointer?
def get_alive
#holder = (User.map {|user| user})
#user = #holder.each {|i| if i.alive #user << i}
end
thanks
Use a scope to find all alive users.
class User < ActiveRecord::Base
scope :alive, where(:alive => true)
# ... the rest of your model ...
end
Then you can do this:
#alive_users = User.alive
You could just select those users directly if User is active record:
User.where(:alive => true)
Or filter for just those users:
User.all.filter(&:alive)
you need to give a bit more details what "holder" is supposed to be... and why you are comparing against 'i'
otherwise:
User.where(:alive => true)
it's a good idea to wrap this in a scope as in Sean Hill's answer
You can even use this syntax in where query
User.where(alive: true)
Or use select over array of object. But select is slow
User.all.select{ |user| user.alive == true}

Sorting in rails using helper

I'm a novice in ruby-on-rails.
I have an applications counting distance between metro station and a ATM.
There's two models with many-to-many relation: Station, Cashpoint. And there's a controller SHOW, that should get the station and show ATMs in order there's proximity to the station.
class StationsController < ApplicationController
def show
#station = Station.find(params[:id])
#cashpoints = #station.cashpoints.find(:all)
respond_to do |format|
format.html
end
end
end
Also there's a helper that counts distance using Google Directions API.
module StationsHelper
def count_distance(origin,destination)
...
return {:text => ... # 1 min
, :value => ... # 60 (seconds)
}
end
end
All this works properly.
But I'm wondering how to order ATMs by :value returned by StationsHelper?
I tried to write something in controller similar to:
#cashpoints = #station.cashpoints.find(:all,
:order => count_distance(#station.address, cashpoint.address)[:value])
But it's evidently doesn't work 'cause I have know idea how to link single cashpoint object
to count_distance method parameter.
May be you can help me, it appears that my project structure is wrong to do this.
Try this:
#cashpoints = #station.cashpoints.find(:all).sort_by { |c| count_distance(c.address, #station.address) }

How to specify a Rails 3 scope.limit - with an offset?

So I have some posts, and would like to show n..m most recent entries in the sidebar (these numbers being set in a config)
I can get the latest n records easily enough
class Post < ActiveRecord::Base
default_scope :order => "created_at DESC"
scope :published, lambda { where("blog_entries.created_at <= ?", Time.zone.now) }
scope :latest, lambda { |n| published.limit(n) }
end
#posts = Post.latest(6)
But what I'd like is
#posts = Post.published.limit(6, 12)
but this gives wrong number of arguments, so is there any way in AR? Right now I'm playing with will_paginate, but it seems hacky to use it for this.
Ok, so the answer is, I think:
#posts = Post.published.limit(6).offset(5)
It will retrieve 6 posts, starting from the sixth.
edit2: About the limit([6, 12]), I find that strange:
attr_accessor :limit_value
def limit(value)
relation = clone
relation.limit_value = value
relation
end
def build_arel
...
arel.take(connection.sanitize_limit(#limit_value)) if #limit_value
...
end
def sanitize_limit(limit)
if limit.is_a?(Integer) || limit.is_a?(Arel::Nodes::SqlLiteral)
limit
elsif limit.to_s =~ /,/
Arel.sql limit.to_s.split(',').map{ |i| Integer(i) }.join(',')
else
Integer(limit)
end
end
So I don't really see how it works with an array. But I obviously missed something. Do you see what?
For rails 5 (not sure for rails 4). offset(x).limit(y) works correctly. limit(y).offset(x) still behaves as described in other answers.

Resources