Use the result of checked method inside conditional statement - ruby

I'm trying to implement and Authorization module, because currently the authorization logic for a certain resource is separated on two or three different places and even though I'm not sure this is the best approach, at least I think it will provide some encapsulation.
Since I'm checking for several different things
Does the user have the right role
Is the resources in the right state to process the required action
Do the user has the right to perform the required action on this particular resource
So as you can see, there are several checks, I'm not pretending to be completely correct here, but this is pretty close to the real case, so I've decided to use something like a Result Object even though it's actually not an object but a struct and I'm not using Gem but pretty simple custom implementation.
So part of my Authorization module is this:
module Authorization
Result = Struct.new(:successfull?, :error)
extend self
def read(user, resource, message: 'Permission denied')
can_read =
[
condition1,
condition2,
condition3
]
return Result.new(can_read.any?, can_read.any? ? nil : message))
end
However within this Authorization module I have a lot of methods and some of them check read internally like so:
def assign(user, resource, message: 'Permission denied')
return read(user, resource) unless read(user, resource).successfull?
Result.new(true, nil)
end
So my main question is how to avoid this double call to read(user, resource). I guess one option would be to just call it before the check like:
result = read(user, resource)
return result unless result.successfull?
However I'm pretty new to Ruby and I suspect that maybe there is more ruby-like way to do this. Just to inline it somehow by assigning the result from read inside the condition check...However this is just wild guess.
And one more question, that came up while I was writing this. Currently if I want to send nil for message when the authorization passes I'm doing this:
return Result.new(can_read.any?, can_read.any? ? nil : message))
Because message unless can_read.any? is throwing and error even though I thought it would default to nil. So again, is there some more ruby-like way to do this?

First part can be written with Object#yield_self:
def assign(user, resource, message: 'Permission denied')
read(user, resource).yield_self do |res|
res.successful? ? Result.new(true, nil) : res
end
end
successfull? -> successful? for English reasons. I am not convinced this is more readable than using a local variable though. Alternatively:
(res = read(user, resource)).successful? ? Result.new(true, nil) : res
As for your second question, you'll need more parentheses
Result.new(can_read.any?, (message if can_read.none?))
the return is not needed.
I would also advise you to slow down with all the unlesses, try to swap your conditions to if whenever possible -- I find it quite useful to make Result a class and define a failed? method for it. Actually, I'd consider this:
class Result
def initialize(error)
#error = error
end
def successful?
#error.nil?
end
def failed?
!successful?
end
end
That depends on how complicated your Result gets, but for the use case shown, it would be a little cleaner imho.

Related

Hash method get on ruby

I need to get some values for a hash but, I don't know how can I do it!
My data:
data = {
name: "Pedro Álvares",
age: 35,
sensitive_data: {
cpf_cnpj=>27046645678,
fantasy_name: "I have the power"
}
}
I search for ruby methods and find the method values_at, but this method gets only the first_data like name, age.
If I use:
name = data.values_at(:name)
the field returned is Pedro Álvares
but if I try use:
fantasy_name = data.values_at(:fantasy_name)
the data returned is nil.
How can I get the field without using data[:sensitive_data][:fantasy_name]?
Because you know the correct structure of the hash you should write:
data[:sensitive_data][:fantasy_name]
#=> "I have the power"
You should not use dig here. Why? Suppose you accidently wrote
data.dig(:sesnitive_data, :fantasy_name)
The would return nil (because data has no key :sesnitive). Depending on the context the error might not surface until sometime later, making debugging more difficult than is necessary.
By contrast, if you wrote
data[:sesnitive_data][:fantasy_name]
data[:sesnitive_data] would return nil (because data has no key :sesnitive_data) and then nil[:sesnitive_data] would raise an exception, informing you that nil has no method []. That is precisely what you want to happen: you want to be notified of the error immediately, and have the reason for it it pinpointed, so you can easily correct your code.
Hash#dig, Array#dig and Struct#dig (which call each other) have their uses (when you do not know the structures of objects in advance--a hash's keys, for example), but those methods should not be used when an object's structure is known.
You could get the nested value by dig method:
data.dig(:sensitive_data, :fantasy_name) # => "I have the power"

Ruby method for values from all associations

This method works, but I'm sure the performance could be greatly improved. Also, I'm realizing how fun and awesome it is to take smelly code like this, and rubify it. But I need a little more help to get my Ruby skills to the level to refactor something like this.
An objective can have "preassign" objectives. These are pre-requisites that must be completed before the a student can try the objective in question.
ObjectiveStudent is the join model between an objective and a student. It has a method called "points_all_time" that finds the student's best score on that objective.
The check_if_ready method is the one that I'm trying to refactor in this question. It also belong to the ObjectiveStudent model.
It needs to check whether the student has passed ALL of the preassigns for a given objective. If so, return true. Return false if the student has a less-than-passing score on any of the preassigns.
def check_if_ready
self.objective.preassigns.each do |preassign|
obj_stud = self.user.objective_students.find_by(objective_id: preassign.id)
return false if obj_stud.points_all_time < 7
end
return true
end
Right now I suspect this method is making too many calls to the database. What I'm really hoping to find is some way to look at the scores for the pre-reqs with a single db call.
Thank you in advance for any insight.
The following should work for you:
def is_ready?
user.objective_students
.where(objective_id: objective.preassigns.select(:id))
.none? { |obj_stud| obj_stud.points_all_time < 7 }
end
We collect all the objective_students for the user where the objective_id is in the list of objective.preassigns ids. This results in one 1 query being executed.
Then we use Enumerable#none? to make sure that none of the objective_students have points_all_time less than 7.
You could also use the inverse .all? { |obj_stud| obj_stud.points_all_time >= 7 } if you wanted
One way you could "rubify" this method is to rewrite the signature as:
def is_ready?
It is common practice to append ? to functions that return a boolean value in Ruby. (Note: I also don't really see a reason to have the word 'check' in the declaration, but that's just an opinion).
Furthermore, if objective_id is the primary key for the objective_students model, you can simply write objective_students.find(preassign.id) instead of the find_by method.
I would also suggest having a separate method for returning a student's points (especially since I suspect you will need to get a student's points more than just once) :
def getPoints(preAssignId)
return self.user.objective_students.find_by(objective_id: preAssignId).points_all_time
end
Then your main method can be written in a more clear, self-describing manner as:
def is_ready?
self.objective.preassigns.each {|preassign| return false if getPoints(preassign) < 7 }
return true
end

Using Ruby to solve a quiz

So I found this quiz on a website that I was excited to solve with my newly acquired Ruby skills (CodeAcademy, not quite finished yet).
What I want to do is make an array with 100 entries, all set to "open". Then, I planned to create a method containing a for loop that iterates through every nth entry of the array and changes it to either "open" or "closed", based on what it was before. In the for loop, n should be increased from 1 to 100.
What I have so far is this:
change_state = Proc.new { |element| element == "open" ? element = "closed" : element = "open" }
def janitor(array,n)
for i in 1..n
array.each { |element| if array.index(element) % i == 0 then element.change_state end }
end
end
lockers = [*1..100]
lockers = lockers.map{ |element| element = "closed" }
result = janitor(lockers,100)
When trying to execute I receive an error saying:
undefined method `change_state' for "closed":String (NoMethodError)
Anybody an idea what is wrong here? I kinda think I'm calling the "change_state" proc incorrectly on the current array element.
If you know the quiz, no spoilers please!
As you have implemented change_state, it is not a method of any class, and definitely not one attached to any of the individual elements of the array, despite you using the same variable name element. So you cannot call it as element.change_state.
Instead, it is a variable pointing to a Proc object.
To call the code in a Proc object, you would use the call method, and syntax like proc_obj.call( params ) - in your case change_state.call( element )
If you just drop in that change, your error message will change to:
NameError: undefined local variable or method `change_state' for main:Object
That's because the change_state variable is not in scope inside the method, in order to be called. There are lots of ways to make it available. One option would be to pass it in as a parameter, so your definition for janitor becomes
def janitor(array,n,state_proc)
(use the variable name state_proc inside your routine instead of change_state - I am suggesting you change the name to avoid confusing yourself)
You could then call it like this:
result = janitor(lockers,100,change_state)
Although your example does not really need this structure, this is one way in which Ruby code can provide a generic "outer" function - working through the elements of an array, say - and have the user of that code provide a small internal custom part of it. A more common way to achieve the same result as your example is to use a Ruby block and the yield method, but Procs also have their uses, because you can treat them like data as well as code - so you can pass them around, put them into hashes or arrays to decide which one to call etc.
There may be other issues to address in your code, but this is the cause of the error message in the question.

ruby method with for loop, unexpected return

I have a model MyModel with a method to return a specific record (see logic below).
def self.find_future_rec #note2
rec = find(rand(MyModel.count)+1) #note1
while rec.nil? | (rec.expdate<Date.today)
rec = find(rand(MyModel.count)+1)
end
return rec
end
Every record of MyModel class has a variable expdate of Date class. (I know this is a horrible way to find a record, this is more for my own edification and also some test code.)
This method will iterate through several undesirable records before finding an appropriate record, but the record returned is always the one found at note 1, the first record queried. Is there some lazy assignment thing going on here?
I had to add self at note2 to be able to call the method in a static context. Is this the correct interpretation?
The find method never returns nil when you give it a single ID to find: if it can't find the record you're asking for, it raises an ActiveRecord::RecordNotFound exception. So, rec is always non-nil and rec.nil? is always false. That means that your loop is really like this:
while rec.expdate < Date.today
If your loop is always returning the rec from #note1 then you're never entering the while loop at all and you're always getting a desirable MyModel on the first try.
Other points to consider:
Sometimes things get deleted so Model.count + 1 is not necessarily the maximum ID.
find raises an exception to indicate failure so you need to rescue ActiveRecord::RecordNotFound rather than check for nil.
You are using | which is a boolean OR. Try using or or ||.
Adding self to make a method a class method ("static" is a decent approximation) is indeed one of the correct ways to approach this.

Expected behaviour? MATLAB ignores errors on deserialization, except when in debug mode

I don't understand Matlab's behaviour in the example below. On deserialization, it sets the properties of the object. This causes set.name to be executed. For the purpose of the example, I have constructed a case where an error is thrown in this method. In the first deserialization, the error is ignored and unhandled; the function simply stops execution at the error, code after the error is not executed. On the second deserialization, I have set dbstop if error, and now the error is triggered as I would expect. Questions follow below the example.
>> clear all;
>> dbstatus;
>> type Tester.m;
classdef Tester < handle
properties
name;
end
methods
function self = Tester()
disp('Creating Tester object');
end
function set.name(self, val)
global allnames
if isequal(allnames, [])
allnames = {};
end
if any(strcmp(allnames, val))
fprintf(1, 'Name already exists. Will issue error.\n');
error('Error: duplicate name %s', val);
fprintf(1, 'Still here?\n');
else
self.name = val;
allnames = [allnames self.name];
end
end
end
end
>> t = Tester();
Creating Tester object
>> t.name = 'abc';
>> save('/tmp/fubar.mat', 't');
>> load('/tmp/fubar.mat')
Name already exists. Will issue error.
>> dbstop if error
>> load('/tmp/fubar.mat')
Name already exists. Will issue error.
Error using Tester/set.name (line 18)
Error: duplicate name abc
18 error('Error: duplicate name %s', val);
K>> dbquit
Should I be surprised at this behaviour?
Is this MATLAB™ being strange, or would other programming languages engage similar behaviour?
Is there a good reason to behave like this?
Probably the deserialization code uses a try-catch construct that does not rethrow the errors. I can see some uses for this, as faults in the load code would at least give you partial access to your data. On the other hand, it should warn you when it does something like this.
When it really can't load the data (this happens e.g. when your classdef file is not in the path), it will show a notice.
So IMHO, you should be both happy and sad: an error would at least give you partial results, on the other hand MATLAB should at least throw a warning that such a thing happened.
With regard to your specific code: I think that global variable is not the best way to go, as a global variables is stored independently from your objects. I'd go with a class variable (i.e. static) if possible. Because now you depend on the global variable names in your workspace, which is not saved in the MAT file as far as I know.

Resources