klarrimore-routengn 0.22 → 0.23

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,80 @@
1
+ # Takes a hash of string and file parameters and returns a string of text
2
+ # formatted to be sent as a multipart form post.
3
+ #
4
+ # Author:: Cody Brimhall <mailto:cbrimhall@ucdavis.edu>
5
+ # Created:: 22 Feb 2008
6
+
7
+ require 'rubygems'
8
+ require 'mime/types'
9
+ require 'cgi'
10
+
11
+
12
+ module Multipart
13
+ VERSION = "1.0.0" unless const_defined?(:VERSION)
14
+
15
+ # Formats a given hash as a multipart form post
16
+ # If a hash value responds to :string or :read messages, then it is
17
+ # interpreted as a file and processed accordingly; otherwise, it is assumed
18
+ # to be a string
19
+ class Post
20
+ # We have to pretend like we're a web browser...
21
+ USERAGENT = "Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en-us) AppleWebKit/523.10.6 (KHTML, like Gecko) Version/3.0.4 Safari/523.10.6" unless const_defined?(:USERAGENT)
22
+ BOUNDARY = "0123456789ABLEWASIEREISAWELBA9876543210" unless const_defined?(:BOUNDARY)
23
+ CONTENT_TYPE = "multipart/form-data; boundary=#{ BOUNDARY }" unless const_defined?(:CONTENT_TYPE)
24
+ HEADER = { "Content-Type" => CONTENT_TYPE, "User-Agent" => USERAGENT } unless const_defined?(:HEADER)
25
+
26
+ def self.prepare_query(params)
27
+ fp = []
28
+
29
+ params.each do |k, v|
30
+ # Are we trying to make a file parameter?
31
+ if v.respond_to?(:path) and v.respond_to?(:read) then
32
+ fp.push(FileParam.new(k, v.path, v.read))
33
+ # We must be trying to make a regular parameter
34
+ else
35
+ fp.push(StringParam.new(k, v))
36
+ end
37
+ end
38
+
39
+ # Assemble the request body using the special multipart format
40
+ query = fp.collect {|p| "--" + BOUNDARY + "\r\n" + p.to_multipart }.join("") + "--" + BOUNDARY + "--"
41
+ return query, HEADER
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # Formats a basic string key/value pair for inclusion with a multipart post
48
+ class StringParam
49
+ attr_accessor :k, :v
50
+
51
+ def initialize(k, v)
52
+ @k = k
53
+ @v = v
54
+ end
55
+
56
+ def to_multipart
57
+ return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"\r\n\r\n#{v}\r\n"
58
+ end
59
+ end
60
+
61
+ # Formats the contents of a file or string for inclusion with a multipart
62
+ # form post
63
+ class FileParam
64
+ attr_accessor :k, :filename, :content
65
+
66
+ def initialize(k, filename, content)
67
+ @k = k
68
+ @filename = filename
69
+ @content = content
70
+ end
71
+
72
+ def to_multipart
73
+ # If we can tell the possible mime-type from the filename, use the
74
+ # first in the list; otherwise, use "application/octet-stream"
75
+ mime_type = MIME::Types.type_for(filename)[0] || MIME::Types["application/octet-stream"][0]
76
+ return "Content-Disposition: form-data; name=\"#{CGI::escape(k)}\"; filename=\"#{ filename }\"\r\n" +
77
+ "Content-Type: #{ mime_type.simplified }\r\n\r\n#{ content }\r\n"
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,108 @@
1
+ require 'rubygems'
2
+ gem 'oauth'
3
+ require 'oauth/consumer'
4
+ require 'json'
5
+ require 'http/multipart'
6
+ require 'routengn/mapper'
7
+ require 'routengn/uploader'
8
+ require 'active_support'
9
+
10
+ module RouteNGN
11
+ Dir.glob(File.dirname(__FILE__) + "/routengn/models/*.rb").each do |model|
12
+ require File.expand_path(model)
13
+ end
14
+
15
+ class Connection
16
+ attr_accessor :access_token
17
+
18
+ def initialize(site, key, secret)
19
+ @consumer = OAuth::Consumer.new key, secret, {
20
+ :signature_method => 'HMAC-SHA1',
21
+ :site => site
22
+ }
23
+
24
+ @request_token = @consumer.get_request_token
25
+ @consumer.request(:get, @request_token.authorize_url)
26
+
27
+ @access_token = @request_token.get_access_token
28
+ @access_token.consumer.http.read_timeout = 5000
29
+ end
30
+ end
31
+
32
+ class << self
33
+ def connect!(site, key, secret)
34
+ @connection = Connection.new site, key, secret
35
+ end
36
+
37
+ def connection
38
+ @connection
39
+ end
40
+
41
+ [:get, :delete].each do |http_method|
42
+ define_method(http_method) do |*args|
43
+ uri, params = args
44
+ params ||= {}
45
+ url = params.inject("#{uri}?") { |_, (k, v)| _ << "#{k}=#{v}" }
46
+ raw = @connection.access_token.send http_method, url
47
+ Response.new raw
48
+ end
49
+ end
50
+
51
+ [:post, :put].each do |http_method|
52
+ define_method(http_method) do |*args|
53
+ uri, params = args
54
+ params ||= {}
55
+ params = params.inject({}) do |_, (k, v)|
56
+ _[k.to_s] = v.to_s
57
+ _
58
+ end
59
+ check_connection!
60
+ raw = @connection.access_token.send http_method, uri, params
61
+ Response.new raw
62
+ end
63
+ end
64
+
65
+ private
66
+ def check_connection!
67
+ raise ConnectionException.new unless @connection
68
+ raise OAuthException.new unless @connection.access_token
69
+ end
70
+ end
71
+
72
+ # TODO: validation errors are now stored in failure message's data... we should figure out what to do with it
73
+ class Response
74
+ SUCCESS_RANGE = (200..399)
75
+
76
+ attr_reader :data, :status
77
+
78
+ def initialize(raw)
79
+ @status = raw.code.to_i
80
+ # TODO: if it's not a success code we probably shouldn't bother parsing the JSON
81
+ @data = JSON.parse raw.body
82
+ end
83
+
84
+ def [](key)
85
+ @data[key]
86
+ end
87
+
88
+ def success?
89
+ SUCCESS_RANGE.include? @status
90
+ end
91
+
92
+ def failure?
93
+ !success?
94
+ end
95
+ end
96
+
97
+ class ConnectionException < Exception
98
+ def initialize(msg = "No connection to RouteNGN established.")
99
+ super
100
+ end
101
+ end
102
+
103
+ class OAuthException < Exception
104
+ def initialize(msg = "Invalid RouteNGN OAuth credentials.")
105
+ super
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,18 @@
1
+ HISTFILE = "~/.routengn.irb.hist"
2
+ MAXHISTSIZE = 1000
3
+
4
+ if defined? Readline::HISTORY
5
+ histfile = File.expand_path(HISTFILE)
6
+ if File.exists?(histfile)
7
+ lines = IO.readlines(histfile).collect { |line| line.chomp }
8
+ Readline::HISTORY.push(*lines)
9
+ end
10
+
11
+ Kernel.at_exit do
12
+ lines = Readline::HISTORY.to_a.reverse.uniq.reverse
13
+ lines = lines[-MAXHISTSIZE, MAXHISTSIZE] if lines.nitems > MAXHISTSIZE
14
+ File.open(histfile, File::WRONLY|File::CREAT|File::TRUNC) do |f|
15
+ lines.each { |line| f.puts line }
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,152 @@
1
+ module RouteNGN
2
+ module Mapper
3
+ def self.included(model)
4
+ model.class_eval do
5
+ extend ClassMethods
6
+ include InstanceMethods
7
+ end
8
+ end
9
+ end
10
+
11
+ #TODO perhaps we can add some of the goodies provided through relationships like '<<'
12
+ module InstanceMethods
13
+ attr_accessor :saved
14
+
15
+ def attributes
16
+ self.class.fields.inject({}) do |_, field|
17
+ _[field] = send field
18
+ _
19
+ end
20
+ end
21
+
22
+ def attr_params
23
+ self.attributes.inject({}) do |params, (k, v)|
24
+ params["#{self.class.name.downcase}[#{k}]"] = v
25
+ params
26
+ end
27
+ end
28
+
29
+ def new?
30
+ !(@saved ||= false)
31
+ end
32
+
33
+ def save
34
+ response = if new?
35
+ RouteNGN.post self.class.base_url, attr_params
36
+ else
37
+ RouteNGN.put self.class.base_url, attr_params
38
+ end
39
+
40
+ return false if !response.success?
41
+
42
+ @saved = true
43
+
44
+ # populates fields with data that was sent back
45
+ self.class.fields.each do |field|
46
+ element = response['data'].first
47
+ self.send :"#{field}=", element[field.to_s] if element[field.to_s]
48
+ end
49
+
50
+ true
51
+ end
52
+
53
+ def destroy
54
+ response = RouteNGN.delete self.class.base_url, :id => primary
55
+ response.success?
56
+ end
57
+ end # InstanceMethods
58
+
59
+ module ClassMethods
60
+ attr_reader :fields
61
+ attr_reader :primary_field
62
+
63
+ def new(opts = {})
64
+ instance = super() # don't want implicit args
65
+ @fields.each do |field|
66
+ field = field.to_s
67
+ next unless opts.has_key? field
68
+ instance.send :"#{field}=", opts[field]
69
+ end
70
+ raise PrimaryFieldException.new unless instance.respond_to? :primary
71
+ instance
72
+ end
73
+
74
+ def new_from_saved(opts = {})
75
+ instance = new opts
76
+ instance.saved = true
77
+ instance
78
+ end
79
+
80
+ def base_url
81
+ "/#{name.downcase.pluralize}"
82
+ end
83
+
84
+ def field(name, opts = {})
85
+ @fields ||= []
86
+ return if @fields.include? name
87
+ @fields << name
88
+ define_method(name) { instance_variable_get :"@#{name}" }
89
+ define_method(:"#{name}=") { |val| instance_variable_set :"@#{name}", val }
90
+ if opts[:primary] # TODO prevent multiple primaries
91
+ alias_method :primary, name
92
+ @primary_field = name
93
+ end
94
+ end
95
+
96
+ def belongs_to(klass, opts = {}) # This isn't really a klass (in the traditional sense) but a symbol rather... so we need to_s
97
+ attr = opts[:column] ? opts[:column] : :"#{klass}_id"
98
+ field attr
99
+ define_method(klass) { klass.to_s.camelize.constantize.first :id => send(attr) }
100
+ end
101
+
102
+ def has_one(klass, opts = {}) # This isn't really a klass (in the traditional sense) but a symbol rather... so we need to_s
103
+ attr = opts[:column] ? opts[:column] : :"#{name}_id"
104
+ define_method(klass) { klass.to_s.camelize.constantize.first attr => primary }
105
+ end
106
+
107
+ def has_many(klass, opts = {}) # This isn't really a klass (in the traditional sense) but a symbol rather... so we need to_s
108
+ attr = opts[:column] ? opts[:column] : :"#{name}_id"
109
+ define_method(klass.to_s.pluralize.to_sym) { klass.to_s.singularize.camelize.constantize.all attr => primary }
110
+ end
111
+
112
+ def delete(id)
113
+ response = RouteNGN.delete base_url, :id => id
114
+ response.success?
115
+ end
116
+
117
+ def find(*args)
118
+ case args.first
119
+ when :all
120
+ all(*args[1..-1])
121
+ else
122
+ first(*args[1..-1])
123
+ end
124
+ end
125
+
126
+ def all(opts = {})
127
+ result = []
128
+
129
+ response = RouteNGN.get base_url, opts
130
+
131
+ data = response['data']
132
+
133
+ return [new_from_saved(data)] unless data.is_a? Array
134
+
135
+ data.each do |d|
136
+ result << new_from_saved(d)
137
+ end
138
+
139
+ result
140
+ end
141
+
142
+ def first(opts = {})
143
+ all(opts).first # optimize/simplify?
144
+ end
145
+ end # ClassMethods
146
+
147
+ class PrimaryFieldException < Exception
148
+ def initialize(msg = "Model does not specify a primary field.")
149
+ super
150
+ end
151
+ end
152
+ end # RouteNGN
@@ -0,0 +1,8 @@
1
+ class Carrier
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ field :name
6
+
7
+ has_many :groups
8
+ end
@@ -0,0 +1,8 @@
1
+ class Dialcode
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ field :digits
6
+
7
+ belongs_to :locale
8
+ end
@@ -0,0 +1,8 @@
1
+ class Endpoint
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ field :ip
6
+
7
+ belongs_to :group, :column => :epgroup_id
8
+ end
@@ -0,0 +1,31 @@
1
+ class Group
2
+ include RouteNGN::Mapper
3
+
4
+ #TODO: fixure out a fix for abnormal foreign key 'epgroup_id'
5
+
6
+ field :id, :primary => true
7
+ field :name
8
+ field :direction
9
+ field :margin
10
+ field :prefix
11
+ field :strip_digits
12
+ field :rn_prefix
13
+ field :add_digits
14
+ field :add_field
15
+ field :add_type
16
+ field :blocked_region_id
17
+ field :route_on_type_id
18
+ field :remove_rn
19
+ field :trunkgroup
20
+ field :active_begin_day
21
+ field :active_end_day
22
+ field :active_begin_time
23
+ field :active_end_time
24
+
25
+ belongs_to :instance
26
+ belongs_to :carrier
27
+
28
+ has_many :endpoints, :column => :epgroup_id
29
+ has_many :rates
30
+
31
+ end
@@ -0,0 +1,9 @@
1
+ class Instance
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ field :name
6
+
7
+ belongs_to :type
8
+
9
+ end
@@ -0,0 +1,11 @@
1
+ class Locale
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ field :name
6
+
7
+ belongs_to :region
8
+
9
+ has_many :dialcodes
10
+ has_many :rates
11
+ end
@@ -0,0 +1,11 @@
1
+ class Rate
2
+ include RouteNGN::Mapper
3
+ include RouteNGN::Uploader
4
+
5
+ field :id, :primary => true
6
+ field :rate
7
+
8
+ belongs_to :type
9
+ belongs_to :locale
10
+ belongs_to :group
11
+ end
@@ -0,0 +1,15 @@
1
+ class Record
2
+ include RouteNGN::Mapper
3
+
4
+ field :dialcode, :primary => true
5
+ field :rn
6
+ field :locale_id
7
+ field :sent
8
+ field :received
9
+ field :detail
10
+ field :server
11
+ field :response_time
12
+ field :created_at
13
+ field :updated_at
14
+
15
+ end
@@ -0,0 +1,9 @@
1
+ class Region
2
+ include RouteNGN::Mapper
3
+ include RouteNGN::Uploader
4
+
5
+ field :id, :primary => true
6
+ field :name
7
+
8
+ has_many :locales
9
+ end
@@ -0,0 +1,5 @@
1
+ class Route
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ end
@@ -0,0 +1,7 @@
1
+ class Type
2
+ include RouteNGN::Mapper
3
+
4
+ field :id, :primary => true
5
+ field :name
6
+
7
+ end
@@ -0,0 +1,16 @@
1
+ module RouteNGN
2
+ module Uploader
3
+ def self.included(model)
4
+ model.class_eval do
5
+ extend ClassMethods
6
+ end
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def upload
12
+ # STUB
13
+ puts "data, headers = Multipart::Post.prepare_query(\"title\" => 'title', \"uploaded_data\" => File.new(path))"
14
+ end
15
+ end
16
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: klarrimore-routengn
3
3
  version: !ruby/object:Gem::Version
4
- version: "0.22"
4
+ version: "0.23"
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keith Larrimore
@@ -43,6 +43,25 @@ extra_rdoc_files: []
43
43
  files:
44
44
  - README.rdoc
45
45
  - lib/
46
+ - lib/routengn.rb
47
+ - lib/http
48
+ - lib/http/multipart.rb
49
+ - lib/routengn
50
+ - lib/routengn/uploader.rb
51
+ - lib/routengn/console.rb
52
+ - lib/routengn/models
53
+ - lib/routengn/models/route.rb
54
+ - lib/routengn/models/locale.rb
55
+ - lib/routengn/models/endpoint.rb
56
+ - lib/routengn/models/carrier.rb
57
+ - lib/routengn/models/region.rb
58
+ - lib/routengn/models/rate.rb
59
+ - lib/routengn/models/dialcode.rb
60
+ - lib/routengn/models/instance.rb
61
+ - lib/routengn/models/record.rb
62
+ - lib/routengn/models/group.rb
63
+ - lib/routengn/models/type.rb
64
+ - lib/routengn/mapper.rb
46
65
  has_rdoc: false
47
66
  homepage: http://github.com/mojombo/grit
48
67
  licenses: