I'm having an issue about thread secure variables. I have a controller method which sends sms to given numbers. But if users makes request at the same time variables are being overwritten.
I know RoR s not thread-secure and i have to make it but i couldnt do it with live response. If i would take all data from user and do it in a background job, it would be easier.
For example lets say first user tries to send sms to x number with the content a and the second user tries to send sms y number with the content b. If they make the requests at the exactly same moment x number getting two sms with content a and b.
def create
success = false
message = nil
status = 422
if params[:receivers].present? && params[:title].present? && params[:content].present?
if params[:is_future_sms].present? && params[:is_future_sms].to_s == 'true' && !params[:send_date].present?
render json: {success: false, message: 'Insufficient Parameter'}
else
sms = #account.sms_objects.new(sms_object_params)
sms.sms_title_id = set_sms_title_id
sms.receivers = sms.receivers.to_s
receivers = NumberOperations.sanitize_receivers_with_hash(sms.receivers)
if receivers.count > 0
total_count = sms.credit(sms.content)
sms_balance = sms.balance(sms.content)
receivers.map{|r| r[:balance] = sms_balance}
sms_balance = receivers.count * total_count
if #account.can_afford_sms?(sms_balance)
if sms.save
SendSmsJob.perform_later(sms.id, receivers)
success = true
message = 'Messages created successfully'
status = 201
else
success = false
message = sms.errors.full_messages.to_sentence
status = 422
end
else
success = false
message = 'Insufficient Credit'
status = 422
end
else
success = false
message = 'No valid number'
status = 422
end
end
else
success = false
message = 'Insufficient Parameter'
status = 422
end
render json: { success: success, message: message }, status: status
end
I guess i can solve the problem with mutex and Thread.new but when i use it it doesnt give the response to user.
def create
th = Thread.new do
# all code here
end
th.join
end
this works well but doesnt response at the end.
Yeyyy! I found the solution.
I've changed my server from puma to unicorn. And it works properly now.
Related
I have implemented user provisioning/deprovisioning with SCIM like so :
users_controller.rb
class Scim::UsersController < Scim::ScimController
before_action :set_scim_provider
def index
startIndex = params[:startIndex].to_i
startIndex = 1 if startIndex < 1# if the user send a startIndex < 1, it is bad data, we don't take it.
itemsPerPage = params[:count].to_i
if itemsPerPage < 1 || itemsPerPage > #scim_provider.max_results
itemsPerPage = #scim_provider.default_number_of_results
end
scim_users = #scim_provider.identity_provider.communaute_accesses.from_scim
if params["filter"]
parser = Scim::QueryFilter::Parser.new
rpn_array = parser.parse(params["filter"])
tree = parser.tree
if tree.length == 3 and tree[0]== 'eq' and tree[1] == 'userName'
userName = tree[2]
scim_users = scim_users.where(provider_identifier: userName.delete('"'))
else
fail 'e'
end
end
paginated_users = scim_users.order(:created_at).offset(startIndex - 1).limit(itemsPerPage)
r = {
"schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
"totalResults": scim_users.size,
"Resources": paginated_users.map { |ca| #scim_provider.representation_for_user(ca) },
"startIndex": startIndex,
"itemsPerPage": itemsPerPage
}
render_json_result(r, 200)
end
def create
if #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #body_params['userName'])
render_409_conflict("uniqueness")
else
ca = #scim_provider.identity_provider.communaute_accesses.find_by(provider_identifier: #body_params['userName'], communaute_id: #scim_provider.identity_provider.communaute.id)
if ca.nil?
ca = #scim_provider.identity_provider.communaute_accesses.create(provider_identifier: #body_params['userName'], communaute_id: #scim_provider.identity_provider.communaute.id)
end
ca.update_last_raw_value("scim", #body_string)
ca.extract_values_from_scim
ca.queue_send
end
render_json_result(#scim_provider.representation_for_user(ca), 201)
end
def show
user = #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #body_params['userName'])
if user
render_json_result(#scim_provider.representation_for_user(user), 200)
else
render_404_not_found(params[:id])
end
end
def update
ca = #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #body_params['userName'])
uc = UserCommunaute.find_by(provider_identifier: #body_params['userName'])
ca.update_last_raw_value("scim", #body_string)
ca.extract_values_from_scim
unless ca.nil?
if ca.pending?
ca.update_last_raw_value("scim", #body_string)
ca.update(active: false)
if ca.active == false
fail "Unable to delete this user because of activeness" if ca.active == true
ca.destroy!
end
render_json_result(#scim_provider.representation_for_communaute_access_patch(ca), 200)
end
end
unless uc.nil?
uc.update(active: #body_params['active'])
if uc.active == false
uc.user.communaute_accesses.from_scim.destroy_all
uc.user.user_communautes.from_scim.destroy_all
render_json_result(#scim_provider.representation_for_user_communaute_patch(uc), 200)
end
end
end
end
Explanations:
When updating a user, SCIM sends a PATCH request like this:
{"schemas"=>["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations"=>[{"op"=>"Replace", "path"=>"active", "value"=>"False"}]} (#body_params in the code)
Which is what i am expecting. But, for a while, i was receiving the userName also in the body response during the PATCH operation.
This is how I fetch the correct user in my DB.
Actual result:
I don't receive the userName anymore when SCIM hits my update action.
Expected results:
Being able to receive information about the user during the PATCH operation to fetch the userName and find the right user in my database.
I have tried almost everything. When SCIM hits the index action, which it does everytime before going anywhere else, it does return me a userName et everything ends up as a 200 OK.
Then, when passing through update, it sends me nothing.
What I have tried last is to isolate the userName as an instance variable in the index action to fetch it after in the update like so:
# index
...
if params["filter"]
parser = Scim::QueryFilter::Parser.new
rpn_array = parser.parse(params["filter"])
tree = parser.tree
if tree.length == 3 and tree[0]== 'eq' and tree[1] == 'userName'
#user_name = tree[2]
scim_users = scim_users.where(provider_identifier: #user_name.delete('"'))
else
fail 'e'
end
end
...
# update
def update
ca = #scim_provider.identity_provider.communaute_accesses.from_scim.find_by(provider_identifier: #user_name)
uc = UserCommunaute.find_by(provider_identifier: #user_name)
ca.update_last_raw_value("scim", #body_string)
ca.extract_values_from_scim
...
But, #user_name in update seems to disappear as its value is nil.
I am deprovisioning from Azure Active Directory and Okta in a production environment.
Mapping is ok in both platforms.
Provisioning is working like a charm.
Please refer to https://developer.okta.com/docs/reference/scim/scim-20/#update-a-specific-user-patch for PATCH /Users/{userId}. Could you not make use of the userId in the url to identify the user ?
I have a loop that creates refunds for any purchase under an order.
An order can have multiple payments on it (if user upgrades shipping, adds items to cart after initial payment and resubmits, etc.) This isn't conventonial for ecommerce but it's needed in this case.
I have this loop:
orders_payments = Payment.where()
orders_payments.each do |btp|
transaction = gateway.transaction.find("#{btp.transaction_token}")
if transaction.status == "submitted_for_settlement"
void = gateway.transaction.void("#{btp.transaction_token}")
if void.success?
format.html { redirect_to user_orders_path, notice: 'Order refund was successfully refunded.' }
else
format.html { redirect user_orders_path, notice: 'Order refund unsuccessful.' }
end
elsif transaction.status == "settled"
refund = gateway.transaction.refund("#{btp.transaction_token}")
if refund.success?
format.html { redirect_to user_orders_path, notice: 'Order refund was successfully refunded.' }
else
format.html { redirect user_orders_path, notice: 'Order refund unsuccessful.' }
end
end
end
Of course, this doesn't work due to the redirects. But aside from not creating a redirect... and it's important I have a redirect with a message of the status... How can I check the status of the voids or refunds AFTER the loop is created.
I would like to be able to check which ones worked and which ones didn't work.
Any ideas on how I can do this?
Rather than do a single loop, you can use multiple loops to split up your data into categories.
The first step in your iteration is to retrieve the transaction object. This can also be done with map:
transactions = orders_payments.map do |btp|
gateway.transaction.find("#{btp.transaction_token}")
end
The next step is the if statement that splits the data into 2 groups according to their status. You can do use select to construct these 2 lists:
submitted = transactions.select do |transaction|
transaction.status == "submitted_for_settlement"
end
settled = transactions.select do |transaction|
transaction.status == "settled"
end
The next step is to process both of the lists and track which ones succeeded or failed. It sounds like at the end of this you want a "successful" and "failure" list for each of the transaction groups, leaving you with 4 lists. partition is a good method for this use-case:
successful_submitted, failed_submitted = submitted.partition do |transaction|
gateway.transaction.void("#{btp.transaction_token}").success?
end
successful_settled, failed_settled = settled.partition do |transaction|
gateway.transaction.refund("#{btp.transaction_token}").success?
end
You can use each_with_object to store the statuses. Here I'm storing them in a Hash by transaction_token.
Note that I'm using find_each rather than each to avoid pulling all the records into memory at once.
transaction_statuses = orders_payments.find_each.each_with_object({}) do |btp,status|
transaction = gateway.transaction.find("#{btp.transaction_token}")
status[btp.transaction_token] = case
when transaction.status == "submitted_for_settlement"
{ voided: gateway.transaction.void("#{btp.transaction_token}") }
when transaction.status == "settled"
{ refunded: gateway.transaction.refund("#{btp.transaction_token}") }
end
end
And then do something later with them.
transaction_statuses.each do |token,status|
case
when status[:voided]
case
when status[:voided].success?
puts "Order #{token} was successfully voided."
else
puts "Order #{token} void failed."
end
when status[:refunded]
case
when status[:refunded].success?
puts "Order #{token} was successfully refunded."
else
puts "Order #{token} refund failed."
end
end
end
If you are coming here because you are having the same or similar issue, before reading this, I suggest reading max pleaner's and Schwern's answers on this question.
The following works but may be a very beginner way I doing what I wanted to do.
I create a blank array, and then store the messages in it with flash.keep and then redirect after the loop completes with all messages, whether successful or declined refunds.
orders_payments = Payment.where()
messages = []
orders_payments.each do |btp|
transaction = gateway.transaction.find("#{btp.transaction_token}")
if transaction.status == "submitted_for_settlement"
void = gateway.transaction.void("#{btp.transaction_token}")
if void.success?
messages << "Refund Successful message"
else
messages << "Refund unSuccessful message"
end
elsif transaction.status == "settled"
refund = gateway.transaction.refund("#{btp.transaction_token}")
if refund.success?
messages << "Refund successful message"
else
messages << "Refund unSuccessful message"
end
end
end
flash.keep[:notice] = messages.join if messages.any?
respond_to do |format|
format.html { redirect_to route }
end
...
I seem to be having trouble with both RPush and Houston.
Here's more or less what my controller looks like...
def create
if authenticate_user
post = Post.find_by_id(params[:post_id])
if post
comment = post.comments.new(comment_params.merge(user_id: params[:user_id]))
Comment.transaction do
if (comment.save)
apns_file = File.join(Rails.root, "APNSCert.pem")
app = Rpush::Apns::App.new
app.name = "My App"
app.certificate = File.read(apns_file)
app.environment = "sandbox" # APNs environment.
app.password = ENV["apns_certificate_password"] #figaro
app.connections = 1
app.save!
#SEND Push notification to the user who made the original post
post_user = User.find(post.user_id)
comment_user = User.find(comment.user_id)
if post_user && comment_user
rememeber_tokens_for_user = RememberToken.where("user_id = ?", post_user.id)
if rememeber_tokens_for_user.size > 0
rememeber_tokens_for_user.each do |remember_token|
# Create a notification that alerts a message to the user, plays a sound, and sets the badge on the app
alert = comment_user.name + ": " + comment.comment_text
if alert.length > 50
alert = alert[0..46] + "..."
end
n = Rpush::Apns::Notification.new
n.app = Rpush::Apns::App.find_by_name("My App")
n.device_token = remember_token.device_token
n.alert = alert
n.data = { foo: "bar" }
n.save!
It appears my server on heroku is getting an error on the app.save! line, ActiveRecord::RecordInvalid (Validation failed: Name has already been taken):.
I'm no more than an intermediate in rails, so any help would be appreciated. Am I suppose to put the 'app' variable section of code into a separate class that is somehow only called once or something, similar to a singleton in objective-c? It apparently doesn't like when it's accessed by a different user POSTing to this resource, which is when I get this error.
Should I take a stab at Grocer, since I can't seem to get Houston or RPush working?
You definitely do not need to do the APN setup more than once per app. That's exactly why you're having the problem.
Insert a check to create an app only when there is not one already created:
if !Rpush::Gcm::App.find_by_name("My App")
app.name = "My App"
app.certificate = File.read(apns_file)
app.environment = "sandbox" # APNs environment.
app.password = ENV["apns_certificate_password"] #figaro
app.connections = 1
app.save!
end
I have an API with the following requiements;
Your application must respond to each IPN message with HTTP header status code 200, whether or not you intend to do anything with it.
This is the API processing code i have tried;
def success
transaction =Transaction.find_by_merchant_reference(params[:external_ref])
if transaction && transaction.status != "Completed"
date_time = params[:date_time]
amount = params[:amount]
narrative = params[:narrative]
network_ref = params[:network_ref]
external_ref = params[:external_ref]
msisdn = params[:msisdn]
signature = params[:signature]
if verfiy_signiture(date_time,amount,narrative,network_ref,external_ref,msisdn) == signature
user = User.find(transaction.user_id)
user.balance = (user.balance+transaction.amount*0.975)
user.save()
transaction.status ="Completed"
TopUp.account_topup(user.name, user.email, user.currency, transaction.amount, transaction.merchant_reference).deliver
flash[:notice] = "##################################3"
user_home_path(#current_user)
end
end
render :status => 200
end
However this show errors regarding to missing template yet this is an instant payment notification.
Use:
render nothing: true, status: 200
This tells rails not to render a template
I have recently been trying to upgrade my app form Rails 2.3.8 to newly-releases Rails 3.
After going through fixing some Rails 3 RubyAMF doesn't seem to work:
>>>>>>>> RubyAMF >>>>>>>>> #<RubyAMF::Actions::PrepareAction:0x1649924> took: 0.00017 secs
The action '#<ActionDispatch::Request:0x15c0cf0>' could not be found for DaysController
/Users/tammam56/.rvm/gems/ruby-1.9.2-p0/gems/actionpack-3.0.0/lib/abstract_controller/base.rb:114:in `process'
/Users/tammam56/.rvm/gems/ruby-1.9.2-p0/gems/actionpack-3.0.0/lib/abstract_controller/rendering.rb:40:in `process'
It doesn't seem to be able to find the proper controller. Might have to do with new changes in Rails 3 Router. Do you know how to go about finding the root cause of the problem and/or trying to fix it?
I'm pasting code from RubyAMF where this is happening (Exception happens at the line: #service.process(req, res)):
#invoke the service call
def invoke
begin
# RequestStore.available_services[#amfbody.service_class_name] ||=
#service = #amfbody.service_class_name.constantize.new #handle on service
rescue Exception => e
puts e.message
puts e.backtrace
raise RUBYAMFException.new(RUBYAMFException.UNDEFINED_OBJECT_REFERENCE_ERROR, "There was an error loading the service class #{#amfbody.service_class_name}")
end
if #service.private_methods.include?(#amfbody.service_method_name.to_sym)
raise RUBYAMFExc
eption.new(RUBYAMFException.METHOD_ACCESS_ERROR, "The method {#{#amfbody.service_method_name}} in class {#{#amfbody.service_class_file_path}} is declared as private, it must be defined as public to access it.")
elsif !#service.public_methods.include?(#amfbody.service_method_name.to_sym)
raise RUBYAMFException.new(RUBYAMFException.METHOD_UNDEFINED_METHOD_ERROR, "The method {#{#amfbody.service_method_name}} in class {#{#amfbody.service_class_file_path}} is not declared.")
end
#clone the request and response and alter it for the target controller/method
req = RequestStore.rails_request.clone
res = RequestStore.rails_response.clone
#change the request controller/action targets and tell the service to process. THIS IS THE VOODOO. SWEET!
controller = #amfbody.service_class_name.gsub("Controller","").underscore
action = #amfbody.service_method_name
req.parameters['controller'] = req.request_parameters['controller'] = req.path_parameters['controller'] = controller
req.parameters['action'] = req.request_parameters['action'] = req.path_parameters['action'] = action
req.env['PATH_INFO'] = req.env['REQUEST_PATH'] = req.env['REQUEST_URI'] = "#{controller}/#{action}"
req.env['HTTP_ACCEPT'] = 'application/x-amf,' + req.env['HTTP_ACCEPT'].to_s
#set conditional helper
#service.is_amf = true
#service.is_rubyamf = true
#process the request
rubyamf_params = #service.rubyamf_params = {}
if #amfbody.value && !#amfbody.value.empty?
#amfbody.value.each_with_index do |item,i|
rubyamf_params[i] = item
end
end
# put them by default into the parameter hash if they opt for it
rubyamf_params.each{|k,v| req.parameters[k] = v} if ParameterMappings.always_add_to_params
begin
#One last update of the parameters hash, this will map custom mappings to the hash, and will override any conflicting from above
ParameterMappings.update_request_parameters(#amfbody.service_class_name, #amfbody.service_method_name, req.parameters, rubyamf_params, #amfbody.value)
rescue Exception => e
raise RUBYAMFException.new(RUBYAMFException.PARAMETER_MAPPING_ERROR, "There was an error with your parameter mappings: {#{e.message}}")
end
#service.process(req, res)
#unset conditional helper
#service.is_amf = false
#service.is_rubyamf = false
#service.rubyamf_params = rubyamf_params # add the rubyamf_args into the controller to be accessed
result = RequestStore.render_amf_results
#handle FaultObjects
if result.class.to_s == 'FaultObject' #catch returned FaultObjects - use this check so we don't have to include the fault object module
e = RUBYAMFException.new(result['code'], result['message'])
e.payload = result['payload']
raise e
end
#amf3
#amfbody.results = result
if #amfbody.special_handling == 'RemotingMessage'
#wrapper = generate_acknowledge_object(#amfbody.get_meta('messageId'), #amfbody.get_meta('clientId'))
#wrapper["body"] = result
#amfbody.results = #wrapper
end
#amfbody.success! #set the success response uri flag (/onResult)
end
The best suggestion is to try rails3-amf. It currently is severely lacking in features in comparison to RubyAMF, but it does work and I'm adding new features as soon as they are requested or I have time.