/tests_description.txt.rb
Ruby | 270 lines | 99 code | 63 blank | 108 comment | 5 complexity | dee98695b2c6c52b0b93360d81195187 MD5 | raw file
- =begin
- COMMON NOTES:
- Application is available here for public access: https://bitbucket.org/boris_pilgun/test_for_david/overview
- It was crated with help of composer command:
- "rails new myapp -m https://raw.github.com/RailsApps/rails-composer/master/composer.rb"
- There is not any changes on client_side part, only backend for check tests from pdf.
- All 3 tests are in this app.
- task-1:
- look at
- /db/migrate/20130624133411_create_post.rb
- /db/migrate/20130624135126_add_permalink_to_post.rb
- task-2:
- look at
- /lib/second.rb
- run just this separated file to check (some tests are already there; implemented by original version of Luhn algorithm author).
- task-3:
- test process described at the end of this document together with related code.
- =end
- =begin
- 1.
- Show the code you would use for a database migration that added a field “permalink” to an existing table called “post”. The migration is to also create a permalink for every existing Post record based on the “title” field. You can use any method you want to convert title to become permalink, the only requirements being permalink must be based on title, must be constructed of only URL valid characters, and must be unique (even if another Post has the same title and converts to the same permalink).
- =end
- #20130624135126_add_permalink_to_post.rb
- class AddPermalinkToPost < ActiveRecord::Migration
- def change
- add_column :post, :permalink, :string
- add_index :post, :permalink, :unique => true
- end
- def migrate(direction)
- super
- if direction == :up
- Rails.application.eager_load! #preload all code in production to access Post model
- Post.all.each do |post|
- post.update_attribute :permalink, "#{post.id} #{post.title}".parameterize
- puts post.permalink
- end
- puts "All permalinks updated!"
- end
- end
- end
- =begin
- NOTES:
- we have to set uniqueness pemalink for each post so we need some unique key + post.title. As key we can use post.id - it is always unique. If assume that title is blank, permalink will be same as post.id, so not blank but still unique. However we have issue here for new records, because id is available only after saving new post. This is not a problem and as solution we can use "after_create :set_permalink" callback for new records which will add permalink after object was created.
- Our model will looks like below in such case:
- =end
- class Post < ActiveRecord::Base
- attr_accessible :title
- set_table_name 'post'
- validates :title, :presence => true, :length => {:minimum => 2}
- #!!!!!!!!
- # all next code should be added after running migration with permalink field:
- #!!!!!!!!
- #validates :permalink, :presence => true, :unless => Proc.new{ new_record? }
- #in additional to uniqueness validation with indexes in DB we add validation to model:
- #validates :permalink, :uniqueness => true, :allow_nil => true
- #after_create :set_permalink
- #
- #private
- #
- #def set_permalink
- # self.update_attribute :permalink, "#{self.id} #{self.title}".parameterize
- #end
- end
- #=========================================================================================
- =begin
- 2.
- Credit cards use a check digit algorithm called the Luhn algorithm. Write a function that takes one parameter, the credit card number, and returns true or false depending on whether the number is a valid number. Similarly, write a second function that takes a number of any length and calculates the Luhn check digit and returns the original number with the check digit appended on the end.
- For information on the Luhn algorithm:
- http://en.wikipedia.org/wiki/Luhn_algorithm
- =end
- #separated method for calculating Luhn digit of source number
- def calculate_luhn_digit(input_num)
- input_num = input_num.to_s.scan(/\d/).map(&:to_i).reverse
- #separate values at odd & even indexes
- even_numbers = input_num.values_at(* input_num.each_index.select{|k| k.even?})
- odd_numbers = input_num.values_at(* input_num.each_index.select{|k| k.odd?})
- #calculate sum
- result = odd_numbers.reduce{|sum, new_val| sum + new_val }
- even_numbers.each{|num| num *= 2; result += (num > 9 ? num - 9 : num)}
- #additional digit
- result = 10 - (result % 10)
- result == 10 ? 0 : result
- end
- #check if source number is valid
- def number_is_valid?(number)
- number = number.to_s
- luhn_result = calculate_luhn_digit(number[0...number.size-1])
- number[number.size-1].to_i == luhn_result
- end
- #append luhn digit to the end of source number
- def append_luhn_digit(number)
- number = number.to_s
- "#{number}#{calculate_luhn_digit(number[0..number.size-1])}".to_i
- end
- #some tests:
- puts "calculate_luhn_digit method:"
- puts calculate_luhn_digit(7999999712)
- puts calculate_luhn_digit(7992739871)
- puts "number_is_valid? method:"
- puts number_is_valid?(79999997124)
- puts number_is_valid?(79999997129)
- puts number_is_valid?(79927398712)
- puts number_is_valid?(79927398713)
- puts "append_luhn_digit? method:"
- puts append_luhn_digit(7999999712)
- puts append_luhn_digit(7992739871)
- #=========================================================================================
- =begin
- 3.
- ...here is too long description...
- =end
- #>>>"Find or create an object by a unique key and update it without introducing any extra objects..."
- #>>>"...A line_item_id is unique in the scope of a service...."
- #as said above we have unique key :line_item_id in our payment model. However just for testing I'll comment out this validation (if my solution is correct then we will not have any duplicates even without validations). And now my model will looks like the below:
- class Payment < ActiveRecord::Base
- attr_accessible :balance, :line_item_id, :service_id
- belongs_to :service
- #validates :line_item_id, :uniqueness => { :scope => :service_id } #commented out for testing with parallel requests
- #same thing in CreatePayment migration for DB-level validation:
- #add_index :payments, [:line_item_id, :service_id], :unique => true
- validates :line_item_id, :service_id, :presence => true #validates for presence of required fields
- #simulates some updates in payment object with help of transaction and pessimistic DB locking
- def self.update_with_lock(payment, args)
- #using Locking::Pessimistic to prevent conflict because of update line by 2 or more threads at one time
- Payment.transaction do
- payment.lock!
- payment.balance = payment.balance.to_f + 10 # + args[:new_amount]
- payment.save!
- end
- payment
- end
-
- end
- #also we have service model:
- class Service < ActiveRecord::Base
- attr_accessible :name
- validates :name, :presence => true, :uniqueness => true
- end
- #to make requests we will use PaymentController#payment_process (controller#action);
- #SingletonPaymentWrapper instance inside of mutex sync. block has extra privilages to give us correct payment object (it will find or create record if not exists). The main idea in using singleton pattern inside mutex is to simplify getting object in single thread without any conflicts between threads which trying to access the same payment object at one time.
- class PaymentController < ApplicationController
- @@mutex = Mutex.new
- def payment_process
- payment = nil
- #syncronize access to SingletonPaymentWrapper class instance.
- @@mutex.synchronize do
- payment = SingletonPaymentWrapper.instance.find_or_create_payment(params)
- end
- if payment
- #make some updates with found/created payment object
- payment = Payment.update_with_lock(payment, params)
- render text: "#{payment.id} - #{payment.balance}"
- else
- render text: "blank"
- end
- end
- end
- #Singleton pattern implementation: SingletonPaymentWrapper class:
- require 'singleton'
- class SingletonPaymentWrapper
- include Singleton
- def initialize()
- end
- def find_or_create_payment(args)
- payment = nil
- Payment.transaction do
- payment = Payment.where(:line_item_id => args[:line_item_id], :service_id => args[:service_id]).first
- unless payment
- payment = Payment.create(:line_item_id => args[:line_item_id], :service_id => args[:service_id])
- end
- end
- payment
- end
- end
- =begin
- NOTES:
- 1. to make possible test all described in task 3 I've used puma web-server wich allow use multithreaded requests (max 16 at once) if enabled option in production.rb file:
-
- # Enable threaded mode
- config.threadsafe!
- 2. mutex with singleton class find||create and returns payment object in one thread and after this each request continue go to new queue iside of self.update_with_lock(payment, args) method where uses DB-level pessimistic locking for update found record safely.
- TEST PROCESS:
- from 8 consoles ($ rails c production) almost at one time manually started this script:
- (1..5000).map{|i| `curl \'http://localhost:3000/payment/payment_process?service_id=1&line_item_id=#{rand(100)}\'`}
- also all the time were monitored count of records in Payment model with this script from one more console:
- while(true) do
- sleep(5)
- puts Payment.count
- end
- At the end after a couple tests this solution gave me good result.
- =end