cartodb-rb-client 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. data/.gitignore +5 -0
  2. data/.rvmrc +2 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE +28 -0
  5. data/README.markdown +324 -0
  6. data/Rakefile +10 -0
  7. data/cartodb-rb-client.gemspec +34 -0
  8. data/lib/cartodb-rb-client/cartodb/client/authorization.rb +58 -0
  9. data/lib/cartodb-rb-client/cartodb/client/cache.rb +14 -0
  10. data/lib/cartodb-rb-client/cartodb/client/connection/base.rb +44 -0
  11. data/lib/cartodb-rb-client/cartodb/client/connection/cartodb.rb +231 -0
  12. data/lib/cartodb-rb-client/cartodb/client/connection/postgres.rb +255 -0
  13. data/lib/cartodb-rb-client/cartodb/client/connection.rb +4 -0
  14. data/lib/cartodb-rb-client/cartodb/client/error.rb +68 -0
  15. data/lib/cartodb-rb-client/cartodb/client/utils.rb +20 -0
  16. data/lib/cartodb-rb-client/cartodb/client.rb +4 -0
  17. data/lib/cartodb-rb-client/cartodb/helpers/sql_helper.rb +30 -0
  18. data/lib/cartodb-rb-client/cartodb/helpers.rb +1 -0
  19. data/lib/cartodb-rb-client/cartodb/init.rb +30 -0
  20. data/lib/cartodb-rb-client/cartodb/libs/object.rb +15 -0
  21. data/lib/cartodb-rb-client/cartodb/libs/string.rb +116 -0
  22. data/lib/cartodb-rb-client/cartodb/libs.rb +2 -0
  23. data/lib/cartodb-rb-client/cartodb/model/base.rb +20 -0
  24. data/lib/cartodb-rb-client/cartodb/model/constants.rb +30 -0
  25. data/lib/cartodb-rb-client/cartodb/model/defaults.rb +15 -0
  26. data/lib/cartodb-rb-client/cartodb/model/geo.rb +73 -0
  27. data/lib/cartodb-rb-client/cartodb/model/getters.rb +79 -0
  28. data/lib/cartodb-rb-client/cartodb/model/persistence.rb +69 -0
  29. data/lib/cartodb-rb-client/cartodb/model/query.rb +66 -0
  30. data/lib/cartodb-rb-client/cartodb/model/schema.rb +111 -0
  31. data/lib/cartodb-rb-client/cartodb/model/scope.rb +163 -0
  32. data/lib/cartodb-rb-client/cartodb/model/setters.rb +42 -0
  33. data/lib/cartodb-rb-client/cartodb/model.rb +11 -0
  34. data/lib/cartodb-rb-client/cartodb/types/metadata.rb +89 -0
  35. data/lib/cartodb-rb-client/cartodb/types/pg_result.rb +17 -0
  36. data/lib/cartodb-rb-client/cartodb/types.rb +2 -0
  37. data/lib/cartodb-rb-client/cartodb.rb +6 -0
  38. data/lib/cartodb-rb-client/install_utils.rb +19 -0
  39. data/lib/cartodb-rb-client/version.rb +7 -0
  40. data/lib/cartodb-rb-client.rb +17 -0
  41. data/run_tests.sh +6 -0
  42. data/spec/client_spec.rb +278 -0
  43. data/spec/model/data_spec.rb +130 -0
  44. data/spec/model/metadata_spec.rb +116 -0
  45. data/spec/model/scopes_spec.rb +171 -0
  46. data/spec/spec_helper.rb +28 -0
  47. data/spec/support/cartodb_config.yml +15 -0
  48. data/spec/support/cartodb_factories.rb +33 -0
  49. data/spec/support/cartodb_helpers.rb +14 -0
  50. data/spec/support/cartodb_models.rb +22 -0
  51. data/spec/support/database.yml +5 -0
  52. data/spec/support/shp/cereal.dbf +0 -0
  53. data/spec/support/shp/cereal.shp +0 -0
  54. data/spec/support/shp/cereal.shx +0 -0
  55. data/spec/support/shp/cereal.zip +0 -0
  56. data/spec/support/shp/parcelas.dbf +0 -0
  57. data/spec/support/shp/parcelas.shp +0 -0
  58. data/spec/support/shp/parcelas.shx +0 -0
  59. data/spec/support/shp/parcelas.zip +0 -0
  60. data/spec/support/shp/zonas.dbf +0 -0
  61. data/spec/support/shp/zonas.shp +0 -0
  62. data/spec/support/shp/zonas.shx +0 -0
  63. data/spec/support/shp/zonas.zip +0 -0
  64. data/spec/support/whs_features.csv +33425 -0
  65. metadata +311 -0
@@ -0,0 +1,116 @@
1
+ # coding: UTF-8
2
+
3
+ class String
4
+ def self.random(length=10)
5
+ ('a'..'z').sort_by {rand}[0,length].join
6
+ end
7
+
8
+ def normalize
9
+ str = self.downcase
10
+ return '' if str.blank?
11
+ n = str.force_encoding("UTF-8")
12
+ n.gsub!(/[àáâãäåāă]/, 'a')
13
+ n.gsub!(/æ/, 'ae')
14
+ n.gsub!(/[ďđ]/, 'd')
15
+ n.gsub!(/[çćčĉċ]/, 'c')
16
+ n.gsub!(/[èéêëēęěĕė]/, 'e')
17
+ n.gsub!(/ƒ/, 'f')
18
+ n.gsub!(/[ĝğġģ]/, 'g')
19
+ n.gsub!(/[ĥħ]/, 'h')
20
+ n.gsub!(/[ììíîïīĩĭ]/, 'i')
21
+ n.gsub!(/[įıijĵ]/, 'j')
22
+ n.gsub!(/[ķĸ]/, 'k')
23
+ n.gsub!(/[łľĺļŀ]/, 'l')
24
+ n.gsub!(/[ñńňņʼnŋ]/, 'n')
25
+ n.gsub!(/[òóôõöøōőŏŏ]/, 'o')
26
+ n.gsub!(/œ/, 'oe')
27
+ n.gsub!(/ą/, 'q')
28
+ n.gsub!(/[ŕřŗ]/, 'r')
29
+ n.gsub!(/[śšşŝș]/, 's')
30
+ n.gsub!(/[ťţŧț]/, 't')
31
+ n.gsub!(/[ùúûüūůűŭũų]/, 'u')
32
+ n.gsub!(/ŵ/, 'w')
33
+ n.gsub!(/[ýÿŷ]/, 'y')
34
+ n.gsub!(/[žżź]/, 'z')
35
+ n.gsub!(/[ÀÁÂÃÄÅĀĂ]/i, 'A')
36
+ n.gsub!(/Æ/i, 'AE')
37
+ n.gsub!(/[ĎĐ]/i, 'D')
38
+ n.gsub!(/[ÇĆČĈĊ]/i, 'C')
39
+ n.gsub!(/[ÈÉÊËĒĘĚĔĖ]/i, 'E')
40
+ n.gsub!(/Ƒ/i, 'F')
41
+ n.gsub!(/[ĜĞĠĢ]/i, 'G')
42
+ n.gsub!(/[ĤĦ]/i, 'H')
43
+ n.gsub!(/[ÌÌÍÎÏĪĨĬ]/i, 'I')
44
+ n.gsub!(/[IJĴ]/i, 'J')
45
+ n.gsub!(/[Ķĸ]/i, 'J')
46
+ n.gsub!(/[ŁĽĹĻĿ]/i, 'L')
47
+ n.gsub!(/[ÑŃŇŅʼnŊ]/i, 'M')
48
+ n.gsub!(/[ÒÓÔÕÖØŌŐŎŎ]/i, 'N')
49
+ n.gsub!(/Œ/i, 'OE')
50
+ n.gsub!(/Ą/i, 'Q')
51
+ n.gsub!(/[ŔŘŖ]/i, 'R')
52
+ n.gsub!(/[ŚŠŞŜȘ]/i, 'S')
53
+ n.gsub!(/[ŤŢŦȚ]/i, 'T')
54
+ n.gsub!(/[ÙÚÛÜŪŮŰŬŨŲ]/i, 'U')
55
+ n.gsub!(/Ŵ/i, 'W')
56
+ n.gsub!(/[ÝŸŶ]/i, 'Y')
57
+ n.gsub!(/[ŽŻŹ]/i, 'Z')
58
+ n
59
+ end
60
+
61
+ def sanitize
62
+ return if self.blank?
63
+ self.gsub(/<[^>]+>/m,'').normalize.downcase.gsub(/&.+?;/,'-').
64
+ gsub(/[^a-z0-9 _-]/,'-').strip.gsub(/\s+/,'-').gsub(/-+/,'-').
65
+ gsub(/-/,' ').strip.gsub(/ /,'-').gsub(/-/,'_')
66
+ end
67
+
68
+ def strip_tags
69
+ self.gsub(/<[^>]+>/m,'').strip
70
+ end
71
+
72
+ def convert_to_db_type
73
+ if CartoDB::TYPES.keys.include?(self.downcase)
74
+ if self.downcase == "number"
75
+ "double precision"
76
+ else
77
+ CartoDB::TYPES[self.downcase].first
78
+ end
79
+ else
80
+ self.downcase
81
+ end
82
+ end
83
+
84
+ # {"integer"=>:number, "real"=>:number, "varchar"=>:string, "text"=>:string, "timestamp"=>:date, "boolean"=>:boolean}
85
+ def convert_to_cartodb_type
86
+ inverse_types = CartoDB::TYPES.invert.inject({}){ |h, e| e.first.each{ |k| h[k] = e.last }; h}
87
+ if inverse_types.keys.include?(self.downcase)
88
+ inverse_types[self.downcase]
89
+ else
90
+ inverse_types.keys.select{ |t| !t.is_a?(String) }.each do |re|
91
+ if self.downcase.match(re)
92
+ return inverse_types[re]
93
+ end
94
+ end
95
+ self.downcase
96
+ end
97
+ end
98
+
99
+ def sanitize_sql
100
+ self.gsub(/\\/, '\&\&').gsub(/'/, "''")
101
+ end
102
+
103
+ def host
104
+ self.split('/')[2]
105
+ end
106
+
107
+ def sanitize_column_name
108
+ temporal_name = self.sanitize
109
+ if temporal_name !~ /^[a-zA-Z_]/ || CartoDB::POSTGRESQL_RESERVED_WORDS.include?(self.upcase)
110
+ return '_' + temporal_name
111
+ else
112
+ temporal_name
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,2 @@
1
+ require 'cartodb-rb-client/cartodb/libs/string'
2
+ require 'cartodb-rb-client/cartodb/libs/object'
@@ -0,0 +1,20 @@
1
+ module CartoDB
2
+ module Model
3
+ class Base
4
+ include CartoDB::Model::Getters
5
+ include CartoDB::Model::Setters
6
+ include CartoDB::Model::Geo
7
+ include CartoDB::Model::Schema
8
+ include CartoDB::Model::Persistence
9
+ include CartoDB::Model::Query
10
+ include RGeo::Feature
11
+
12
+ def initialize(attributes = {})
13
+ self.class.cartodb_table = nil
14
+ self.class.send(:field, :the_geom, :type => Point)
15
+ self.attributes = attributes
16
+ self.class.send(:update_cartodb_schema) unless schema_synchronized?
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,30 @@
1
+ module CartoDB
2
+ module Model
3
+ module Constants
4
+ include RGeo::Feature
5
+
6
+ CARTODB_TYPES = {
7
+ String => 'text',
8
+ Integer => 'numeric',
9
+ Numeric => 'numeric',
10
+ Date => 'date',
11
+ DateTime => 'date',
12
+ TrueClass => 'boolean',
13
+ FalseClass => 'boolean',
14
+ Point => 'geometry'
15
+ }.freeze
16
+
17
+ INVALID_COLUMNS = [
18
+ :cartodb_id,
19
+ :id
20
+ ].freeze
21
+
22
+ GEOMETRY_COLUMN = 'the_geom'.freeze
23
+
24
+ RGEO_FACTORY = ::RGeo::Geographic.simple_mercator_factory(:srid => 4326)
25
+
26
+ DEFAULT_ROWS_PER_PAGE = 10.freeze
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,15 @@
1
+ module CartoDB
2
+ module Model
3
+ module Defaults
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,73 @@
1
+ module CartoDB
2
+ module Model
3
+ module Geo
4
+ include CartoDB::Model::Constants
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+ include CartoDB::Model::Constants
12
+
13
+ def setup_geometry_column(geometry_column)
14
+ return if geometry_column[:geometry_type].nil?
15
+
16
+ geometry_name = geometry_column[:name].to_sym
17
+
18
+ self.send :define_method, :the_geom do
19
+ self.attributes[geometry_name]
20
+ end
21
+
22
+ self.send :define_method, :the_geom= do |the_geom|
23
+ self.attributes[geometry_name] = the_geom
24
+ end
25
+
26
+ case geometry_column[:geometry_type].upcase
27
+ when 'POINT'
28
+ setup_point_geometry
29
+ end
30
+ end
31
+ private :setup_geometry_column
32
+
33
+ def setup_point_geometry
34
+ self.send :define_method, :latitude do
35
+ self.the_geom ? self.the_geom.y : nil
36
+ end
37
+ self.send :define_method, :longitude do
38
+ self.the_geom ? self.the_geom.x : nil
39
+ end
40
+
41
+ self.send :define_method, :latitude= do |latitude|
42
+ @latitude = latitude
43
+ if @latitude && @longitude
44
+ self.the_geom = RGEO_FACTORY.point(@longitude, @latitude)
45
+ end
46
+ end
47
+ self.send :define_method, :longitude= do |longitude|
48
+ @longitude = longitude
49
+ if @latitude && @longitude
50
+ self.the_geom = RGEO_FACTORY.point(@longitude, @latitude)
51
+ end
52
+ end
53
+
54
+ end
55
+ private :setup_point_geometry
56
+
57
+ end
58
+
59
+ def prepare_geo_attributes(attributes)
60
+ return if attributes.nil?
61
+ longitude = attributes.delete(:longitude)
62
+ latitude = attributes.delete(:latitude)
63
+ if latitude && longitude
64
+ attributes[:the_geom] = RGEO_FACTORY.point(longitude, latitude)
65
+ end
66
+
67
+ attributes
68
+ end
69
+ private :prepare_geo_attributes
70
+
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,79 @@
1
+ module CartoDB
2
+ module Model
3
+ module Getters
4
+ attr_reader :table, :attributes
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def connection
13
+ CartoDB::Connection
14
+ end
15
+
16
+ def table_name
17
+ @table_name ||= self.name.tableize
18
+ end
19
+
20
+ def cartodb_table
21
+ @cartodb_table = begin
22
+ connection.table table_name
23
+ rescue CartoDB::Client::Error
24
+ nil
25
+ end
26
+ end
27
+
28
+ def columns
29
+ update_cartodb_schema unless schema_synchronized?
30
+ @columns
31
+ end
32
+
33
+ def data_columns
34
+ columns.reject{|c| %w(cartodb_id created_at updated_at).include?(c[:name])}.compact
35
+ end
36
+
37
+ def model_columns
38
+ @model_columns || []
39
+ end
40
+ private :model_columns
41
+
42
+ end
43
+
44
+ def connection
45
+ self.class.connection
46
+ end
47
+
48
+ def table_name
49
+ self.class.table_name
50
+ end
51
+
52
+ def cartodb_table
53
+ self.class.cartodb_table
54
+ end
55
+
56
+ # def method_missing(name, *args, &block)
57
+ # if args.empty? && block.nil? && column_names.include?(name.to_s)
58
+ # attributes[name]
59
+ # else
60
+ # super
61
+ # end
62
+ # end
63
+
64
+ def columns
65
+ self.class.columns
66
+ end
67
+
68
+ def attributes
69
+ @attributes ||= {}
70
+ end
71
+
72
+ def column_names
73
+ columns.map{|column| column[:name]}
74
+ end
75
+ private :column_names
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,69 @@
1
+ module CartoDB
2
+ module Model
3
+ module Persistence
4
+ include CartoDB::Model::Constants
5
+
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ end
9
+
10
+ module ClassMethods
11
+
12
+ def create(attributes = {})
13
+ model = self.new attributes
14
+ model.save
15
+ model
16
+ end
17
+
18
+ end
19
+
20
+ def save
21
+ if new_record?
22
+ create_row
23
+ else
24
+ update_row
25
+ end
26
+ end
27
+
28
+ def destroy
29
+ unless new_record?
30
+ delete_row
31
+ end
32
+ end
33
+
34
+ def new_record?
35
+ cartodb_id.nil? || cartodb_id <= 0
36
+ end
37
+
38
+ def create_row
39
+ inserted_record = connection.insert_row table_name, attributes_for_insert
40
+ self.cartodb_id = inserted_record.id
41
+ end
42
+ private :create_row
43
+
44
+ def update_row
45
+ connection.update_row table_name, cartodb_id, attributes_for_update
46
+ end
47
+ private :update_row
48
+
49
+ def delete_row
50
+ connection.delete_row table_name, cartodb_id
51
+ end
52
+ private :delete_row
53
+
54
+ def attributes_for_insert
55
+ # only the columns defined in the model are allowed to be inserted
56
+ row = attributes.symbolize_keys.reject{|key,value| INVALID_COLUMNS.include?(key) || !column_names.include?(key.to_s) }
57
+ row
58
+ end
59
+ private :attributes_for_insert
60
+
61
+ def attributes_for_update
62
+ row = attributes.reject{|key,value| !column_names.include?(key.to_s) }
63
+ row
64
+ end
65
+ private :attributes_for_update
66
+
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,66 @@
1
+ module CartoDB
2
+ module Model
3
+ module Query
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+
11
+ def select(*fields)
12
+ scope = Scope.new(self)
13
+ scope.select(fields)
14
+ end
15
+
16
+ def all
17
+ scope = Scope.new(self)
18
+ scope.all
19
+ end
20
+
21
+ def where(attributes = nil, *rest)
22
+ scope = Scope.new(self)
23
+ scope.where(attributes, rest)
24
+ end
25
+
26
+ def find(id)
27
+ where(id)
28
+ end
29
+
30
+ def count
31
+ begin
32
+ results = connection.query "SELECT COUNT(CARTODB_ID) FROM #{table_name}"
33
+ results.rows.first[:count].try(:to_i)
34
+ rescue Exception => e
35
+ 0
36
+ end
37
+ end
38
+
39
+ def page(page_number)
40
+ scope = Scope.new(self)
41
+ scope.page(page_number)
42
+ end
43
+
44
+ def per_page(ammount)
45
+ scope = Scope.new(self)
46
+ scope.page(page_number)
47
+ end
48
+
49
+ def order(order_clause)
50
+ scope = Scope.new(self)
51
+ scope.order(order_clause)
52
+ end
53
+
54
+ end
55
+
56
+ def count
57
+ self.class.count
58
+ end
59
+
60
+ def count=(ammount)
61
+ self.class.count= ammount
62
+ end
63
+
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,111 @@
1
+ module CartoDB
2
+ module Model
3
+ module Schema
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ include CartoDB::Model::Constants
11
+
12
+ def schema_synchronized?
13
+ cartodb_table && @columns_synchronized
14
+ end
15
+
16
+ def cartodb_table_exists?
17
+ begin
18
+ cartodb_table && cartodb_table.name.eql?(table_name)
19
+ rescue CartoDB::Client::Error => e
20
+ e.status_code != 404
21
+ end
22
+ end
23
+
24
+ def field(name, options = {:type => String})
25
+ @columns_synchronized = false
26
+ @model_columns ||= []
27
+ column = {
28
+ :name => name.to_s,
29
+ :type => CARTODB_TYPES[options[:type]] || options[:type]
30
+ }
31
+ column[:geometry_type] = options[:type].name.split('::').last.downcase if column[:type].eql?('geometry')
32
+ return if model_columns.include?(column)
33
+
34
+ model_columns << column
35
+ update_cartodb_schema
36
+ end
37
+ private :field
38
+
39
+ def update_cartodb_schema
40
+ table = nil
41
+ if cartodb_table_exists?
42
+ table = cartodb_table
43
+ else
44
+ table = connection.create_table table_name, model_columns
45
+ end
46
+
47
+ read_metadata table
48
+ create_missing_columns
49
+ create_column_accessors
50
+ @columns_synchronized = true
51
+ end
52
+ private :update_cartodb_schema
53
+
54
+ def read_metadata(table)
55
+ extract_columns table
56
+ end
57
+ private :read_metadata
58
+
59
+ def extract_columns(table)
60
+ @columns = table.schema.map{|c| c[0].eql?('the_geom') ? {:name => c[0], :type => c[1], :geometry_type => c[3]} : {:name => c[0], :type => c[1]}}
61
+ end
62
+ private :extract_columns
63
+
64
+ def create_missing_columns
65
+ table_column_names = @columns.map{|c| c[:name]}
66
+ missing_columns = model_columns.reject{|c| table_column_names.include?(c[:name])}
67
+ return unless missing_columns && missing_columns.any?
68
+
69
+ missing_columns.each do |column|
70
+ connection.add_column table_name, column[:name], column[:type]
71
+ end
72
+
73
+ self.cartodb_table = nil
74
+ read_metadata self.cartodb_table
75
+ end
76
+ private :create_missing_columns
77
+
78
+ def create_column_accessors
79
+ @columns.each do |c|
80
+ column_name = c[:name]
81
+ column_type = c[:type]
82
+ setup_geometry_column(c) and next if column_name.eql?(GEOMETRY_COLUMN) || column_type.eql?('geometry')
83
+
84
+ # unless self.methods.include?(column_name)
85
+ self.send :define_method, column_name do
86
+ self.attributes[column_name.to_sym]
87
+ end
88
+ # end
89
+
90
+ # unless self.methods.include?("#{column_name}=")
91
+ self.send :define_method, "#{column_name}=" do |value|
92
+ self.attributes[column_name.to_sym] = value
93
+ end
94
+ # end
95
+ end
96
+ end
97
+ private :create_column_accessors
98
+
99
+ end
100
+
101
+ def schema_synchronized?
102
+ self.class.schema_synchronized? && cartodb_table_exists?
103
+ end
104
+
105
+ def cartodb_table_exists?
106
+ self.class.cartodb_table_exists?
107
+ end
108
+
109
+ end
110
+ end
111
+ end