cartodb-rb-client 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
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