Execute a dynamic method using eval or public_send - ruby-on-rails

I have these code that executes a dynamic method. I'm using eval here to execute it but what I wanted to do is changed it to public_send because I was told so and it's much safer.
Current code:
# update workstep logic here.
incoming_status = params[params[:name]]
# grab workflow, this is current data, use this to compare status to in comming status
workflow = get_workorder_product_workstep(params[:workflow_id])
# check current status if its pending allow to update
# security concern EVAL!
if eval("workflow.can_#{incoming_status}?")
# update status
eval("workflow.#{incoming_status}")
# updated attribute handled_by
workflow.update_attributes(handled_by_id: #curr_user.id)
workflow.save
else
flash[:notice] = 'Action not allowed'
end
The eval here is the concern. How can I changed this to public_send?
Here's what I did.
public_send("workflow.can_#{incoming_status}?")
public_send("#{workflow}.can_#{incoming_status}?")
both of them doesn't work. gives me an error of no method. The first public error returns this undefined method workflow.can_queue? for #<Spree::Admin::WorkordersController:0x00007ff71c8e6f00>
But it should work because I have a method workflow.can_queue?
the second error on public is this
undefined method #<Spree::WorkorderProductWorkstep:0x00007ff765663550>.can_queue? for #<Spree::Admin::WorkordersController:0x00007ff76597f798>
I think for the second workflow is being evaluated separately? I'm not sure.

Working with public_send you can change the relevant lines to:
if workflow.public_send("can_#{incoming_status}?")
# update status
workflow.public_send(incoming_status.to_s)
# ...
A note about security and risks
workflow.public_send("can_#{xyz}?") can only call methods on workflow that are public and which start with the prefix can_ and end with ?. That is probably only a small number of methods and you can easily decide if you want to allow all those methods.
workflow.public_send("#{incoming_status'}) is different because it allows all public methods on workflow – even destroy. That means using this without the "can_#{incoming_status}?" is probably a bad idea. Or you should at least first check if incoming_status is in a whitelist of allowed methods.
eval is the worst because it will evaluate the whole string without any context (e.q. an object like workflow). Imaging you have eval("workflow.#{incoming_status}") without to check first if incoming_status is actually allowed. If someone then sends an incoming_status like this "to_s; system('xyz')"then xyz could be everything – like commands to send a hidden file via email, to install a backdoor or to delete some files.

Related

Style/OptionalBooleanParameter: Use keyword arguments when defining method with boolean argument

I am working on my rails RESTful API and I have set up a versioning feature on some of the endpoints. I have a class ApiVersion which Is responsible for determining which controllers to render base on the arguments passed to it on initialization.
The class definition looks as follows:
class ApiVersion
attr_reader :version, :default
def initialize(version, default = false)
#version = version
#default = default
end
# check whether version is specified or is default
def matches?(request)
check_headers(request.headers) || default
end
private
def check_headers(headers)
# check version from Accept headers; expect custom media type 'suits'
accept = headers[:accept]
accept&.include?("application/vnd.suits.#{version}+json")
end
end
The requests work perfectly fine but when I run rubocop -A I get an error that says:
Style/OptionalBooleanParameter: Use keyword arguments when defining method with boolean argument.
def initialize(version, default = false)
I searched on the internet how to fix this type of error & got some interesting ideas which could not work in my case. For example I found one post that said I should alternate the def initialize(version, default = false) with def initialize(version, default: false) which passes the rubocop tests but then I get an internal server error with an exception: ArgumentError: wrong number of arguments (given 2, expected 1).
Does anyone have an idea on how I can fix this, or how I can alternate the class definition, to get around this issue? Thank you
First off: if you disagree with a particular rule in a linter, then turn it off. In particular, this rule is in the "Style" category, so it is not a correctness or security issue, it is a matter of style.
Secondly, boolean parameters are a code smell, since they are often Flag Parameters. A method with a flag parameter will generally do two different things depending on the value of the boolean argument, because … why else would it have the flag parameter?
However, a method that does two different things should probably be two methods.
Or, in this particular case, since it is an object initializer method specifically, that hints at the fact that there should be two classes.
Okay, with that out of the way, the nice thing about Rubocop is that it generally tells you how to fix whatever it is complaining about. In this case, it suggests using a keyword parameter. That doesn't fix the problem that the method is likely still doing two different things, but at least, it gives a name to that difference, so you can see it at the call site.
So, what Rubocop is suggest is to change the positional parameter into a keyword parameter, something like this:
def initialize(version, default: false)
Now, obviously, when you change the parameter list at the definition site, you also need to change every argument list at every call site. So, if you have a call like this (remember that #initialize gets called by ::new):
ApiVersion.new('1.2.3', true)
You need to replace it with
ApiVersion.new('1.2.3', default: true)

Testing function contains an API request

I'm trying to test my rails application which using Stripe APIs, So I started with models, I'm using Rspec, The model which i want to test is called bank_account.rb inside it there is a function called (create_bank_account) with argument (bank_token) its pseudocode is something like this:
def create_bank_account(bank_token)
# make a Stripe request and save it in local variable
# save needed data in my bank_account table in my DB
end
when i started to test this function, I found that there is an API call inside it, Which is not good, I need my test not to depend on Internet, So after searching I found 'StripeMock` gem, It is useful and i started to use it with Rspec, but I found my self writing a test like this:
it 'with valid bank_token` do
# create a double for bank_account
# using StripeMock to get a faked response for creating
# new bank_account
# expect the doube to receive create_bank_account
# function and response with saving the data inside the DB
end
but after writing this I noticed that I didn't actually run create_bank_account function i faked it, So my questions are:
1- How can i test function that includes API request but run the function it self not faking it?
2- I read a lot about when we use doubles and stubs and what i understood is when a function is not completed, but if the functions is already implemented should i use doubles to avoid something like functions that call APIs?
First and foremost:
Do not create a double for bank_account.
Do not mock/stub bank_account.create_bank_account.
If you do either of these things, in a test that is supposed to be testing behaviour of BankAccount#create_bank_account, then your test is worthless.
(To prove this point, try writing broken code in the method. Your tests should obviously fail. But if you're mocking the method, everything will remain passing!!)
One way or another, you should only be mocking the stripe request, i.e. the behaviour at the boundary between your application and the internet.
I cannot provide a working code sample without a little more information, but broadly speaking you could refactor your code from this:
def create_bank_account(bank_token)
# make a Stripe request and save it in local variable
# save needed data in my bank_account table in my DB
end
To this:
def create_bank_account(bank_token)
stripe_request = make_stripe_request(bank_token)
# save needed data in my bank_account table in my DB
end
private
def make_stripe_request(bank_token)
# ...
end
...And then in your test, you can use StripeMock to only fake the response of BankAccount#make_stripe_request.
If the code is not so easy to refactor(?!), then stubbing the Stripe library directly like this might not be practical. An alternative approach you can always take is use a library like webmock to simply intercept all HTTP calls.

How to analyze quickly the code definition from a given code

Let's say I'm in a really huge project and am curious how this line works:
authorize! :read_pipeline_schedule, user_project
authorize! is supposed to be method name. Is it a private function in the class, or DSL provided from a parent block, or including, or declared in a gem? Or maybe none of them?
I was using grep to find internal code and Google for external code such as gems, however I guess it's useful if I can see the call stack of the method. puts caller is printing from the place where I am although it can not be used for analyzing the above case.
I'd like to know the best practice.
You can grep your code and installed gems and, if name is unique enough, you'll quickly locate its definition and associated usages. However, if a name is a common one, like call, then this approach is useless.
Ruby is a dynamic language and, as such, is a nightmare for static analysis. You may guess where this comes from, but you just can't know for sure.
The only way to know is runtime introspection. So, in your example, put a breakpoint right before that line
binding.pry
authorize! :read_pipeline_schedule, user_project
You'll drop out to pry shell. Now you can do
show-source authorize!
And see where exactly this method is defined.
The easiest way is to ask the method itself:
method_object = method(:authorize!)
method_object.owner
# => returns module in which the method resides
method_object.source_location
# => returns file name and line number of source code where the method is defined
Note that source_location may return nil for methods which have no Ruby source code, e.g. dynamically generated methods or methods implemented in the interpreter internals (C functions in YARV, Java methods in JRuby, etc.)
I like caller(0), caller_locations, and the ever-incredible show-source

changing the value of a global variable in rails

I'm prototyping an app and want to have a global variable that I can hit an endpoint that toggles the global variable $current_status. I have:
def toggle_status
$current_status=false if $current_status.nil?
$current_status=!$current_status
r={}
r[:current_status]=$current_status
render json:r.to_json
end
and in application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
$current_status
end
but hitting /toggle_status always returns false. Why isn't assigning a bool to what it isn't changing this value? I'm aware something like this should be in db but just prototyping
edit 1
I just created this in lib/
class Jt
#cur
def self.cur
#cur
end
def self.cur=val
#cur=val
end
end
and updated the controller to:
def toggle_status
Jt.cur=!Jt.cur
r={}
r[:current_status]=Jt.cur
render json:r.to_json
end
Your toggle code doesn't actually toggle anything. It appears you expect this line to "toggle" the contents of the $current_status variable.
$current_status!=$current_status
However, the != operator doesn't assign anything but it is a comparison operator. In your case, it returns always false based on your query whether $current_status is equal to not $current_status.
What you want to use instead is probably
$current_status = !$current_status
As for your software design, global variables are generally frowned upon in Ruby (and Rails) as are all other kinds of globally mutable state. Use proper classes and objects instead to encapsulate your state and behaviour into more manageable structures. Using global variables, you will shoot yourself in the foot some day and you will have a very hard time to find out what is actually happening. You should try to avoid this :)
You can't use a global variable in this way in such app and there are several reasons. I'll give you just one: depending on the webserver you use, the server may start different processes to handle the incoming web requests, and global variables can't be shared between these processes.
And in fact, it's not even a good idea to use a global variable at all.
If you want to persist a state, use a proper storage. Depending on how long the value should be persisted and who should be able to access it, you have plenty of choices:
database
file system
memory
cookie
Your first snipper does not work because != is a comparison operator, not assignment
Second may not work due to code reloading (Jt class instance is not guaranteed to be the same for other request unless cache_classes is on, but in development you usually always want it off, because otherwise any code changes require server restart to take effect), simple way to have a non-reloaded class - put it in a initializer
For prototyping you also may try thread-local storage for this task: Thread.current[:foo] = 1234

Defining a custom state machine

I am attempting to make it so that people can define arbitrary workflows in classes. The code for this is probably too long for Stack Overflow and so I've got a gist for it.
If you run the code, the first couple of tests will work, but when it attempts to transition to the payment state, it checks for the payment_required? method on the complete wrong object. I want it to be checking for it on the current Order instance, but instead it (seemingly) is looking for that method on the state machine anonymous class.
How do I get it to call the method correctly on the Order instance?
The problem is at the definition of the anonymous state machine, around line 42:
order.class.transitions.each { |attrs| transition(attrs) }
This means the transition guards are evaluated in the anonymous state machine's context rather than in the context of the Order class.
One solution would be to translate the transition guards. Replace the above line with this to pass your test suite:
order.class.transitions.each do |attrs|
if attrs[:if].is_a? Symbol
if_method = attrs[:if]
attrs[:if] = lambda { order.send( if_method ) }
end
transition(attrs)
end
You will need to support all of the types of transition guards for a full solution. Would recommend looking at StateMachine::EvalHelpers for the complete set.
Correction:
As others have mentioned, you will also need to fix your test suite:
go_to :payment, :if => :payment_required? # Line 107
order.stub :payment_required? => true # Line 142
I've fixed this up by moving the state machine definition to the Spree::Order class. You can see the work in this pull request.

Resources