How to manage API requests in multiusers app in Rails? - ruby-on-rails

In my app when user add object it execute one API request to third party site. For single user is ok to send the request immediately. But API calls are limited by 3 per second. So, when qtt of users will increase it'll be a trouble. I suggested that ActiveJob + Sidekiq will do the work, but how to get results when job is done?
My code for one user:
controller.rb
def create
wall = get_request(code_constructor({params for constructor },[]))
end
module CommonMods
def code_constructor(meth_name, meth_params, code)
param_str = []
meth_params.each_pair do |name, value|
param_str << '"' + name.to_s + '":'+ value.to_s
end
param_str = param_str.join(',')
code << meth_name + '({' + param_str + '})'
end
def get_request(code)
i = 0
result = []
while i < code.size
part_code = code.slice(i, i + 25)
if part_code.size >1
part_code = part_code.join(',')
else
part_code = part_code[0]
end
url='https://api.site.com/method/'
uri = URI.parse(url)
parameters = {'code' => "return [#{part_code}];"}
response = Net::HTTP.post_form(uri, parameters)
result = result + JSON.parse(response.body)['response']
i += 25
end
result
end
end
How to execute get_request method in queue with code param and proceed with received response in the controller? Or maybe it have different way to make it?

Related

How to call a class method in \lib from model in rails?

I'm trying to generate stats for a character created by a form. The user inputs the name, race, class, alignment, and whether or not the stats will be generated randomly, or prioritized (values being assigned from highest to lowest). The form works flawlessly, as I can see the output in a view.
What I am now trying to do is call a method from a class in /lib in the model that will generate the stats; however, I keep getting the following error (I can't post pictures):
NoMethodError in CharactersController#create
undefined method `[]' for nil:NilClass
Extracted source (around line #14):
12 before_save do
13 generate_stats
14 self.strength = #character_stats[:strength]
15 self.dexterity = #character_stats[:dexterity]
16 self.constitution = #character_stats[:constitution]
17 self.intelligence = #character_stats[:intelligence]
Here is a copy of some of my code:
In controllers\characters_controller.rb
class CharactersController < ApplicationController
def create
#character = Character.new(character_info_params)
#character.name = params[:character][:name].capitalize
#character.alignment = "#{params[:character][:alignment_lr]} #{params[:character][:alignment_ud]}"
if #character.save
redirect_to #character
else
render 'new'
end
end
private
def character_info_params
params.require(:character).permit(:name, :race, :class_, :alignment)
end
end
In models\character.rb
class Character < ActiveRecord::Base
require 'random_stats_generator'
attr_accessor :rand_stat_gen
def generate_stats
if #rand_stat_gen == true
#character_stats_inst = RandomStatGenerator.new
#character_stats = #character_stats_inst.generate
end
end
before_save do
generate_stats
self.strength = #character_stats[:strength]
self.dexterity = #character_stats[:dexterity]
self.constitution = #character_stats[:constitution]
self.intelligence = #character_stats[:intelligence]
self.wisdom = #character_stats[:wisdom]
self.charisma = #character_stats[:charisma]
end
#validation passed this point
end
In initializers\stat_builders.rb
require "./lib/random_stat_generator.rb"
In lib/random_stat_generator.rb
class RandomStatGenerator
def initialize
#strength = :strength
#dexterity = :dexterity
#constitution = :constitution
#intelligence = :intelligence
#wisdom = :wisdom
#charisma = :charisma
#character_stats = HashWithIndifferentAccess.new()
end
def self.generate
roll_stats
end
def roll(stat)
#roll_value_1 = (1 + (rand(6)))
#roll_value_2 = (1 + (rand(6)))
#roll_value_3 = (1 + (rand(6)))
#roll_value_4 = (1 + (rand(6)))
#roll_array = [#roll_value_1,#roll_value_2,#roll_value_3,#roll_value_4]
#roll_array = #roll_array.sort_by {|x| x }
#roll_array = #roll_array.reverse
stat = #roll_array[0] + #roll_array[1] + #roll_array[2]
end
def roll_stats
#strength = roll(#strength)
#dexterity = roll(#dexterity)
#constitution = roll(#constitution)
#intelligence = roll(#intelligence)
#wisdom = roll(#wisdom)
#charisma = roll(#charisma)
#character_stats[:strength] = #strength
#character_stats[:dexterity] = #dexterity
#character_stats[:constitution] = #constitution
#character_stats[:intelligence] = #intelligence
#character_stats[:wisdom] = #wisdom
#character_stats[:charisma] = #charisma
return #character_stats
end
end
To me, it looks like the method isn't returning anything, or isn't being called at all.
I've tried a lot of solutions that I've come across online, none of them working. There may be some things that don't really make sense that are left over from these solutions. I'm only just starting with rails, so I'm still trying to get used to everything.
Thanks a lot for your help.
Ruby has really powerful functions for manipulating both hashes and arrays.
Typing out duplicate assignments like:
self.strength = #character_stats[:strength]
self.dexterity = #character_stats[:dexterity]
self.constitution = #character_stats[:constitution]
Is pretty dull. So instead we can simply rewrite the methods to pass hashes around.
class RandomStatGenerator
# This is just a constant containing all the stats we want to generate.
STATS = [:strength, :dexterity, :constitution, :intelligence, :wisdom, :charisma]
# Create a hash with random roll values for each stat
def self.roll_stats
# This is kind of scary looking but actually just creates an
# hash from an array of keys
Hash[STATS.map {|k| [k, self.roll ] } ]
end
private
def self.roll
# Create an array with 4 elements (nil)
ary = Array.new(4)
# We then replace the nil value with a random value 1-6
ary = ary.map do
(1 + (rand(6)))
end
# sort it and drop the lowest roll. return the sum of all rolls.
ary.sort.drop(1).sum
# a ruby ninja writes it like this
Array.new(4).map { 1 + rand(6) }.sort.drop(1).sum
end
end
Output:
irb(main):032:0> RandomStatGenerator.roll_stats
=> {:strength=>14, :dexterity=>14, :constitution=>14, :intelligence=>13, :wisdom=>10, :charisma=>9}
But if you don't intend to actually create instances of a class, than you should use a module instead.
Rails models can either be created with a hash or you can replace its values with a hash:
Character.new(RandomStatGenerator.roll_stats)
#character.assign_attributes(RandomStatGenerator.roll_stats)
So we can use this in Character#generate_stats:
def generate_stats
assign_attributes(RandomStatGenerator.roll_stats)
end
You should use ActiveModel callbacks with extreme prejudice. It is often quite a challenge to regulate where in your application and when in the model lifetime. Since before_save runs after validations means that any validations like validates_presence_of :constitution will fail.
In your case it might be better to simply do it in the controller or use:
before_validation :generate_stats, if: -> { new_record? && #rand_stat_gen }
I would like to suggest the following organisation fo your library
# Use a module at top level
module RandomStatGenerator
STATS = [:strength, :dexterity, :constitution, :intelligence, :wisdom, :charisma]
# Use a class Stats if you need to but I don't see why...
class Stats
def initialize
RandomStatGenerator::STATS.each do |stat|
# Below line will do #stat = :stat
instance_variable_set("##{stat.to_s}", stat)
#character_stats = HashWithIndifferentAccess.new()
end
def roll_stats
#character_stats = RandomStatGenerator.roll_stats
end
end
module_function
# below lines will be considered as module functions
# => call RandomStatGenerator.function_name
def roll
roll_value_1 = (1 + (rand(6)))
roll_value_2 = (1 + (rand(6)))
roll_value_3 = (1 + (rand(6)))
roll_value_4 = (1 + (rand(6)))
roll_array = [roll_value_1,roll_value_2,roll_value_3,roll_value_4]
roll_array = roll_array.sort_by {|x| x }
roll_array = roll_array.reverse
roll_array[0] + roll_array[1] + roll_array[2]
end
def roll_stats
character_stats = {}
STATS.each do |stat|
character_stats[stat] = RandomStatGenerator.roll
end
return character_stats
end
end
Then in your character.rb
def generate_stats
#character_stats = RandomStatGenerator.roll_stats
end

Gem Resque Error - Undefined "method perform" after Overriding it form the super class

First of all Thanks for you all for helping programmers like me with your valuable inputs in solving day to day issues.
This is my first question in stack overflow as I am experiencing this problems from almost one week.
WE are building a crawler which crawls the specific websites and extract the contents from it, we are using mechanize to acheive this , as it was taking loads of time we decided to run the crawling process as a background task using resque with redis gem , but while sending the process to background I am experiencing the error as the title saying,
my code in lib/parsers/home.rb
require 'resque'
require File.dirname(__FILE__)+"/../index"
class Home < Index
Resque.enqueue(Index , :page )
def self.perform(page)
super (page)
search_form = page.form_with :name=>"frmAgent"
resuts_page = search_form.submit
total_entries = resuts_page.parser.xpath('//*[#id="PagingTable"]/tr[2]/td[2]').text
if total_entries =~ /(\d+)\s*$/
total_entries = $1
else
total_entries = "unknown"
end
start_res_idx = 1
while true
puts "Found #{total_entries} entries"
detail_links = resuts_page.parser.xpath('//*[#id="MainTable"]/tr/td/a')
detail_links.each do |d_link|
if d_link.attribute("class")
next
else
data_page = #agent.get d_link.attribute("href")
fields = get_fields_from_page data_page
save_result_page page.uri.to_s, fields
#break
end
end
site_done
rescue Exception => e
puts "error: #{e}"
end
end
and the superclass in lib/index.rb is
require 'resque'
require 'mechanize'
require 'mechanize/form'
class Index
#queue = :Index_queue
def initialize(site)
#site = site
#agent = Mechanize.new
#agent.user_agent = Mechanize::AGENT_ALIASES['Windows Mozilla']
#agent.follow_meta_refresh = true
#rows_parsed = 0
#rows_total = 0
rescue Exception => e
log "Unable to login: #{e.message}"
end
def run
log "Parsing..."
url = "unknown"
if #site.url
url = #site.url
log "Opening #{url} as a data page"
#page = #agent.get(url)
#perform method should be override in subclasses
#data = self.perform(#page)
else
#some sites do not have "datapage" URL
#for example after login you're already on your very own datapage
#this is to be addressed in 'perform' method of subclass
#data = self.perform(nil)
end
rescue Exception=>e
puts "Failed to parse URL '#{url}', exception=>"+e.message
set_site_status("error "+e.message)
end
#overriding method
def self.perform(page)
end
def save_result_page(url, result_params)
result = Result.find_by_sql(["select * from results where site_id = ? AND ref_code = ?", #site.id, utf8(result_params[:ref_code])]).first
if result.nil?
result_params[:site_id] = #site.id
result_params[:time_crawled] = DateTime.now().strftime "%Y-%m-%d %H:%M:%S"
result_params[:link] = url
result = Result.create result_params
else
result.result_fields.each do |f|
f.delete
end
result.link = url
result.time_crawled = DateTime.now().strftime "%Y-%m-%d %H:%M:%S"
result.html = result_params[:html]
fields = []
result_params[:result_fields_attributes].each do |f|
fields.push ResultField.new(f)
end
result.result_fields = fields
result.save
end
#rows_parsed +=1
msg = "Saved #{#rows_parsed}"
msg +=" of #{#rows_total}" if #rows_total.to_i > 0
log msg
return result
end
end
What's Wrong with this code?
Thanks

Ruby, forming API request without implicitly stating each parameter

I'm trying to make a request to a web service (fwix), and in my rails app I've created the following initializer, which works... sorta, I have two problems however:
For some reason the values of the parameters need to have +'s as the spaces, is this a standard thing that I can accomplish with ruby? Additionally is this a standard way to form a url? I thought that spaces were %20.
In my code how can I take any of the options sent in and just use them instead of having to state each one like query_items << "api_key=#{options[:api_key]}" if options[:api_key]
The following is my code, the trouble area I'm having are the lines starting with query_items for each parameter in the last method, any ideas would be awesome!
require 'httparty'
module Fwix
class API
include HTTParty
class JSONParser < HTTParty::Parser
def json
JSON.parse(body)
end
end
parser JSONParser
base_uri "http://geoapi.fwix.com"
def self.query(options = {})
begin
query_url = query_url(options)
puts "querying: #{base_uri}#{query_url}"
response = get( query_url )
rescue
raise "Connection to Fwix API failed" if response.nil?
end
end
def self.query_url(input_options = {})
#defaults ||= {
:api_key => "my_api_key",
}
options = #defaults.merge(input_options)
query_url = "/content.json?"
query_items = []
query_items << "api_key=#{options[:api_key]}" if options[:api_key]
query_items << "province=#{options[:province]}" if options[:province]
query_items << "city=#{options[:city]}" if options[:city]
query_items << "address=#{options[:address]}" if options[:address]
query_url += query_items.join('&')
query_url
end
end
end
For 1)
You API provider is expecting '+' because the API is expecting in a CGI formatted string instead of URL formatted string.
require 'cgi'
my_query = "hel lo"
CGI.escape(my_query)
this should give you
"hel+lo"
as you expect
for Question 2) I would do something like
query_items = options.keys.collect { |key| "#{key.to_s}=#{options[key]}" }
def self.query_url(input_options = {})
options = {
:api_key => "my_api_key",
}.merge(input_options)
query_url = "/content.json?"
query_items = []
options.each { |k, v| query_items << "#{k}=#{v.gsub(/\s/, '+')}" }
query_url += query_items.join('&')
end
I'm a developer at Fwix and wanted to help you with your url escaping issue. However, escaping with %20 works for me:
wget 'http://geoapi.fwix.com/content.xml?api_key=mark&province=ca&city=san%20francisco&query=gavin%20newsom'
I was hoping you could provide me with the specific request you're making that you're unable to escape with %20.

RubyAmf and Rails 3

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.

How can I identify and process all URLs in a text string?

I would like to enumerate all the URLs in a text string, for example:
text = "fasòls http://george.it sdafsda"
For each URL found, I want to invoke a function method(...) that transforms the string.
Right now I'm using a method like this:
msg = ""
for i in text.split
if (i =~ URI::regexp).nil?
msg += " " + i
else
msg+= " " + method(i)
end
end
text = msg
This works, but it's slow for long strings. How can I speed this up?
I think "gsub" is your friend here:
class UrlParser
attr_accessor :text, :url_counter, :urls
def initialize(text)
#text = parse(text)
end
private
def parse(text)
#counter = 0
#urls = []
text.gsub(%r{(\A|\s+)(http://[^\s]+)}) do
#urls << $2
"#{$1}#{replace_url($2)}"
end
end
def replace_url(url)
#counter += 1
"[#{#counter}]"
end
end
parsed_url = UrlParser.new("one http://x.com/url two")
puts parsed_url.text
puts parsed_url.urls
If you really need extra fast parsing of long strings, you should build a ruby C extension with ragel.

Resources