How to avoid N+1 in Pundit policy for show?/update?/destroy? - activeadmin

I'm using ActiveAdmin gem together with Pundit (and Rolify) gem.
This is how I wrote my policy (taken from: https://github.com/activeadmin/activeadmin/blob/master/spec/support/templates/policies/application_policy.rb):
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
def show?
scope.where(id: record.id).exists?
end
def create?
user.has_role?(:staff, record.company)
end
def update?
scope.where(id: record.id).exists?
end
def destroy?
scope.where(id: record.id).exists?
end
def destroy_all?
true
end
def scope
Pundit.policy_scope!(user, record.class)
end
class Scope
attr_reader :user, :scope
def initialize(user, scope)
#user = user
#scope = scope
end
def resolve
if user.admin?
scope.all
else
company_ids = Company.with_role(:staff, user).map(&:id)
scope.where(company_id: company_ids)
end
end
end
end
This causes N+1 query each time scope.where(id: record.id).exists?. On index page, show?, update? and destroy? are called for each record in the table.
How can I avoid the N+1 query in this case?
I'm trying to:
1) Include/preload roles together with user for calls to current_user
2) I'm trying to memoize the scope or use some array method to prevent hitting the db with where and exists? methods. But scope.find still makes the db query for every new row.
Thanks!

first of all, I suggest adding a method to the User object to return company_ids where it is staff helps.
class User #or AdminUser right?
def company_ids
#company_ids ||= Company.with_role(:staff, self).map(&:id)
end
end
than you can change
def destroy?
scope.where(id: record.id).exists?
end
to
def destroy?
return true user.admin?
user.company_ids.include?(record.company_id)
end
and resolve method for Scope now looks this way
def resolve
if user.admin?
scope.all
else
scope.where(company_id: user.company_ids)
end
end
end

Related

Trying to figure out how to pass variables from one class to another in python while calling a class from a dictionary

So I am getting used to working with OOP in python, it has been a bumpy road but so far things seem to be working. I have, however hit a snag and i cannot seem to figure this out. here is the premise.
I call a class and pass 2 variables to it, a report and location. From there, I need to take the location variable, pass it to a database and get a list of filters it is supposed to run through, and this is done through a dictionary call. Finally, once that dictionary call happens, i need to take that report and run it through the filters. here is the code i have.
class Filters(object):
def __init__ (self, report, location):
self.report = report
self.location = location
def get_location(self):
return self.location
def run(self):
cursor = con.cursor()
filters = cursor.execute(filterqry).fetchall()
for i in filters:
f = ReportFilters.fd.get(i[0])
f.run()
cursor.close()
class Filter1(Filters):
def __init__(self):
self.f1 = None
''' here is where i tried super() and Filters.__init__.() etc.... but couldn't make it work'''
def run(self):
'''Here is where i want to run the filters but as of now i am trying to print out the
location and the report to see if it gets the variables.'''
print(Filters.get_location())
class ReportFilters(Filters):
fd = {
'filter_1': Filter1(),
'filter_2': Filter2(),
'filter_3': Filter3()
}
My errors come from the dictionary call, as when i tried to call it as it is asking for the report and location variables.
Hope this is clear enough for you to help out with, as always it is duly appreciated.
DamnGroundHog
The call to its parent class should be defined inside the init function and you should pass the arguments 'self', 'report' and 'location' into init() and Filters.init() call to parent class so that it can find those variables.
If the error is in the Filters1 class object, when you try to use run method and you do not see a location or a report variable passed in from parent class, that is because you haven't defined them when you instantiated those object in ReportFilters.fd
It should be:
class ReportFilters(Filters):
fd = {
'filter_1': Filter1(report1, location1),
'filter_2': Filter2(report2, location2),
'filter_3': Filter3(report3, location3)
}
class Filter1(Filters):
def __init__(self, report, location):
Filters.__init__(self, report, location)
self.f1 = None
def run(self):
print(self.get_location())

Access method from different package in Python

I am trying to automate android game. I have so many methods so I break my code in 3 parts.
main, functions and collectors.
I have a method in functions which is:
def search_image(image, confidence=.6, click=True):
location = pyautogui.locateCenterOnScreen(image, confidence=confidence)
if location is not None:
if click:
pyautogui.click(location)
return location
else:
return False
But I cant access it in collectors, like this:
from functions import*
def collect_product():
if search_image(r'Resources\NewOrderAvailable.png') is not False:
search_image(r'Resources\NewOrderAvailable2.png')
for item in range(0, 6):
search_image(r'Resources\Collect.png', confidence=.8)
search_image(r'Resources\Back.png')
search_image(r'Resources\CloseOrderMenu.png')
else:
return False
I got NameError: name 'search_image' is not defined. I need to duplicate that method to make it work. I was wondering what went wrong and how to fix it?
Your function is not an instance of the class. You need to add a self keyword to make it accessible outside.
def search_image(self, image, confidence=.6, click=True):
location = pyautogui.locateCenterOnScreen(image, confidence=confidence)
if location is not None:
if click:
pyautogui.click(location)
return location
else:
return False
Try
import functions
functions.search_image('...')

How can I make this body of code through a for loop?

So, I'm trying to get this code to work in a cleaner way, mainly, through the use of a for loop, but having a really hard time trying to do so. I haven't been able to make a loop that assigns each value of the dictionary to a correspondent variable, so it can be used in the class. For context, the dictionary contains values obtained from another class, I just put those in the dict and sent it to this class, so I don't need to calculate those again.
def get_ipr_data(self):
self.reservoir_result_dict = ReservoirDataFrame.reservoir_result_dict
self.pb = self.reservoir_result_dict.get("pb")
self.rs = self.reservoir_result_dict.get("rs")
self.bo = self.reservoir_result_dict.get("bo")
self.uo = self.reservoir_result_dict.get("uo")
self.re = self.reservoir_result_dict.get("re")
self.j_index = self.reservoir_result_dict.get("j_index")
self.q_max = self.reservoir_result_dict.get("q_max")
self.pws = self.reservoir_result_dict.get("pws")
self.qb = self.reservoir_result_dict.get("qb")
You can use setattr function
for name in ["pb", "rs", "bo", "uo", "re", "j_index", "q_max", "pws", "qb"]:
setattr(self, name, self.reservoir_result_dict.get(name))
Documentation of setattr:
https://docs.python.org/3/library/functions.html#setattr
Delegating attributes is done by defining the __getattr__ method. You should store the dictionary only and then define __getattr__.
class Foo:
...
def get_ipr_data(self):
self.reservoir_result_dict = ReservoirDataFrame.reservoir_result_dict
def __getattr__(self, item):
return self.reservoir_result_dict[item]

how to restrict other user from updating an db object in Flask?

I am new to flask and building an ticket assigner application. Generator end point will assign always the oldest ticket(status=q) from the system to the person logged in. Then either he will resolve(post method = s) the ticket or can update its status to pending (post method = p).
#main.route('/ticket/generator', methods=['GET', 'POST'])
#login_required
def generate_ticket():
ticket = Ticket.query.filter_by(status='q').order_by(Ticket.create_date).first()
form = GenerateTicketForm(obj=ticket)
if form.validate_on_submit():
ticket.ticket_id = form.ticket_id.data
ticket.status = form.status.data
db.session.add(ticket)
db.session.commit()
flash('Ticket Status Update Successfully.')
return redirect(url_for('main.generate_ticket'))
return render_template('generate_ticket.html', form=form)
I want to change the status when the ticket already assigned to someone, so that other do not get the same ticket. So I have created a class method to change the status when getting the oldest ticket:
#classmethod
def activate_tkt_flag(cls, ticket_id_, create_date_):
ticket_db_obj = cls(ticket_id=ticket_id_, create_date=create_date_)
ticket_in_memory = Ticket.query.get(ticket_db_obj.ticket_id)
ticket_in_memory.status = 'a'
db.session.commit()
return ticket_in_memory
If call that after the ticket object in the first query it rewrite the loop some how and assign a new ticket and forget about the previous one the it can not rewrite the the ticket any more and I am getting that error: Key (ticket_id)=(T5) already exists UPDATE ticket SET ticket_id=%(ticket_id)s, status=%(status)s WHERE ticket.ticket_id = %(ticket_ticket_id)s'] [parameters: {'ticket_id': 'T5', 'status': 's', 'ticket_ticket_id': 'T1'}]. If some one has better idea how to do it will be grateful, I am kind of stuck here. Here is the form looks like.
Finally I was able to solve it. I just create one end point which execute first and update the ticket status and pass the ticket id to the next end point. So the last end point show the ticket with updated status to render template html.
#main.route('/ticket/generator/<ticket_id>', methods=['GET', 'POST'])
#login_required
def generate_ticket(ticket_id):
ticket = Ticket.query.get(ticket_id)
form = GenerateTicketForm(obj=ticket)
if form.validate_on_submit():
ticket.ticket_id = form.ticket_id.data
ticket.status = form.status.data
db.session.add(ticket)
db.session.commit()
# flash('Ticket Status Update Successfully.')
return redirect(url_for('main.display_status', ticket_id=ticket.ticket_id))
return render_template('generate_ticket.html', form=form)
#main.route('/ticket/assign')
def assign_ticket():
ticket = Ticket.query.filter_by(status='q').order_by(Ticket.create_date).first_or_404()
ticket.status = 'a'
db.session.commit()
return redirect(url_for('main.generate_ticket', ticket_id=ticket.ticket_id))
The assign end point execute first then the ticket id handed over to generator end point.

Access from within a class

Im wondering if there is a way of accessing outside the class with an instance.
I want to the instance to be put in an array at once, when it is created.
Example.
class Student():
def __init__(self,table):
self.table = table
global sclass
sclass[self.table].sclass(self) # This is the challenge
def sclass_maker(num_students):
sclass = [[] for _ in range(num_students)]
for table in range(num_students):
Student(table) # I want these to put them self in the "sclass"- list.
return sclass
Thanks for taking the time helping!
(The reason for "global sclass" is that i first used this method when sclass was created as a global element. This do not work now, when sclass is created by the function)
one easy way to solve your problem is to just make the list sclass a global variable (by declaring it at the start of the function global sclass) and then have the__init__ function add itself to that global list
something like
class Student():
def __init__(self,table):
global sclass
sclass[self.table].append(self)
#rest of code ....
def sclass_maker(num_students):
global sclass
sclass = [[] for _ in range(num_students)]
#rest of code ....

Resources