cloud_spokes_api_model 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.rdoc +3 -0
  3. data/Rakefile +38 -0
  4. data/app/config/config.yml +16 -0
  5. data/app/models/active_model/model.rb +99 -0
  6. data/app/models/cloud_spokes/model/base_api.rb +198 -0
  7. data/app/models/cloud_spokes/model/category.rb +12 -0
  8. data/app/models/cloud_spokes/model/member.rb +32 -0
  9. data/lib/cloud_spokes_api_model/engine.rb +5 -0
  10. data/lib/cloud_spokes_api_model/railtie.rb +15 -0
  11. data/lib/cloud_spokes_api_model/version.rb +3 -0
  12. data/lib/cloud_spokes_api_model.rb +7 -0
  13. data/lib/tasks/cloud_spokes_api_model_tasks.rake +4 -0
  14. data/test/cloud_spokes_api_model_test.rb +7 -0
  15. data/test/dummy/README.rdoc +261 -0
  16. data/test/dummy/Rakefile +7 -0
  17. data/test/dummy/app/assets/javascripts/application.js +15 -0
  18. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  19. data/test/dummy/app/controllers/application_controller.rb +3 -0
  20. data/test/dummy/app/helpers/application_helper.rb +2 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/config/application.rb +59 -0
  23. data/test/dummy/config/boot.rb +10 -0
  24. data/test/dummy/config/database.yml +25 -0
  25. data/test/dummy/config/environment.rb +5 -0
  26. data/test/dummy/config/environments/development.rb +37 -0
  27. data/test/dummy/config/environments/production.rb +67 -0
  28. data/test/dummy/config/environments/test.rb +37 -0
  29. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  30. data/test/dummy/config/initializers/inflections.rb +15 -0
  31. data/test/dummy/config/initializers/mime_types.rb +5 -0
  32. data/test/dummy/config/initializers/secret_token.rb +7 -0
  33. data/test/dummy/config/initializers/session_store.rb +8 -0
  34. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  35. data/test/dummy/config/locales/en.yml +5 -0
  36. data/test/dummy/config/routes.rb +58 -0
  37. data/test/dummy/config.ru +4 -0
  38. data/test/dummy/public/404.html +26 -0
  39. data/test/dummy/public/422.html +26 -0
  40. data/test/dummy/public/500.html +25 -0
  41. data/test/dummy/public/favicon.ico +0 -0
  42. data/test/dummy/script/rails +6 -0
  43. data/test/test_helper.rb +15 -0
  44. metadata +204 -0
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2012 YOURNAME
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,3 @@
1
+ = CloudSpokesApiModel
2
+
3
+ This project rocks and uses MIT-LICENSE.
data/Rakefile ADDED
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'CloudSpokesApiModel'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,16 @@
1
+ defaults: &defaults
2
+ :cs_api:
3
+ :challenges: 'http://cs-api-sandbox.herokuapp.com/v1/challenges'
4
+ :categories: 'http://cs-api-sandbox.herokuapp.com/v1/categories'
5
+ :members: 'http://cs-api-sandbox.herokuapp.com/v1/members'
6
+ :key: ''
7
+ :expiry: 5
8
+
9
+ development:
10
+ <<: *defaults
11
+
12
+ test:
13
+ <<: *defaults
14
+
15
+ production:
16
+ <<: *defaults
@@ -0,0 +1,99 @@
1
+ # shamelessly stolen from https://github.com/rails/rails/tree/master/activemodel :P
2
+
3
+ module ActiveModel
4
+
5
+ # == Active \Model Basic \Model
6
+ #
7
+ # Includes the required interface for an object to interact with
8
+ # <tt>ActionPack</tt>, using different <tt>ActiveModel</tt> modules.
9
+ # It includes model name introspections, conversions, translations and
10
+ # validations. Besides that, it allows you to initialize the object with a
11
+ # hash of attributes, pretty much like <tt>ActiveRecord</tt> does.
12
+ #
13
+ # A minimal implementation could be:
14
+ #
15
+ # class Person
16
+ # include ActiveModel::Model
17
+ # attr_accessor :name, :age
18
+ # end
19
+ #
20
+ # person = Person.new(name: 'bob', age: '18')
21
+ # person.name # => 'bob'
22
+ # person.age # => 18
23
+ #
24
+ # Note that, by default, <tt>ActiveModel::Model</tt> implements <tt>persisted?</tt>
25
+ # to return +false+, which is the most common case. You may want to override
26
+ # it in your class to simulate a different scenario:
27
+ #
28
+ # class Person
29
+ # include ActiveModel::Model
30
+ # attr_accessor :id, :name
31
+ #
32
+ # def persisted?
33
+ # self.id == 1
34
+ # end
35
+ # end
36
+ #
37
+ # person = Person.new(id: 1, name: 'bob')
38
+ # person.persisted? # => true
39
+ #
40
+ # Also, if for some reason you need to run code on <tt>initialize</tt>, make
41
+ # sure you call +super+ if you want the attributes hash initialization to
42
+ # happen.
43
+ #
44
+ # class Person
45
+ # include ActiveModel::Model
46
+ # attr_accessor :id, :name, :omg
47
+ #
48
+ # def initialize(attributes={})
49
+ # super
50
+ # @omg ||= true
51
+ # end
52
+ # end
53
+ #
54
+ # person = Person.new(id: 1, name: 'bob')
55
+ # person.omg # => true
56
+ #
57
+ # For more detailed information on other functionalities available, please
58
+ # refer to the specific modules included in <tt>ActiveModel::Model</tt>
59
+ # (see below).
60
+ module Model
61
+ def self.included(base) #:nodoc:
62
+ base.class_eval do
63
+ extend ActiveModel::Naming
64
+ extend ActiveModel::Translation
65
+ include ActiveModel::Validations
66
+ include ActiveModel::Conversion
67
+ end
68
+ end
69
+
70
+ # Initializes a new model with the given +params+.
71
+ #
72
+ # class Person
73
+ # include ActiveModel::Model
74
+ # attr_accessor :name, :age
75
+ # end
76
+ #
77
+ # person = Person.new(name: 'bob', age: '18')
78
+ # person.name # => "bob"
79
+ # person.age # => 18
80
+ def initialize(params={})
81
+ params.each do |attr, value|
82
+ self.public_send("#{attr}=", value)
83
+ end if params
84
+ end
85
+
86
+ # Indicates if the model is persisted. Default is +false+.
87
+ #
88
+ # class Person
89
+ # include ActiveModel::Model
90
+ # attr_accessor :id, :name
91
+ # end
92
+ #
93
+ # person = Person.new(id: 1, name: 'bob')
94
+ # person.persisted? # => false
95
+ def persisted?
96
+ false
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,198 @@
1
+ class CloudSpokes::Model::BaseApi
2
+ include ActiveModel::Model
3
+
4
+ ENDPOINT_EXPIRY = ENV['CS_API_EXPIRY'].to_i || CloudSpokes::APP_CONFIG[:expiry]
5
+
6
+ cattr_accessor :access_token
7
+
8
+ # Implements the has_many relationship
9
+ # Passing :parent as an option allows modification of the calling class
10
+ # This is used mostly for has and belongs to many relationships, where
11
+ # a model collection will have a different endpoint
12
+ # Case in point: Members and Challenges
13
+ def self.has_many(entity, options={})
14
+ # add in this relationship to the column_names table
15
+ @column_names << entity.to_sym
16
+ rel_column_names << entity.to_sym
17
+ parent = options[:parent]
18
+
19
+ # dynamically create a method on this instance that will reference the collection
20
+ define_method("#{entity.to_sym}=") do |accessor_value|
21
+ instance_variable_set("@#{entity.to_sym}", accessor_value)
22
+ end
23
+
24
+ define_method(entity.to_sym) do
25
+ klass = entity.to_s.classify.constantize
26
+ (parent || klass).raw_get([to_param, entity.to_s]).map do |e|
27
+ next if e.respond_to?(:last) # we got an array instead of a Hashie::Mash
28
+ klass.new e
29
+ end
30
+ end
31
+ end
32
+
33
+ # Overrides the attr_accesssor class method so we are able to capture and
34
+ # then save the defined fields as column_names
35
+ def self.attr_accessor(*vars)
36
+ @column_names ||= []
37
+ @column_names.concat( vars )
38
+ super
39
+ end
40
+
41
+ # Returns the previously defined attr_accessor fields
42
+ def self.column_names
43
+ @column_names
44
+ end
45
+
46
+ def self.rel_column_names
47
+ @rel_column_names ||= []
48
+ end
49
+
50
+ # Returns the api_endpoint. Note that you need to implement this method
51
+ # in the child object
52
+ def self.api_endpoint
53
+ raise 'Please implement ::api_endpoint in the child object'
54
+ end
55
+
56
+ # Returns all the records from the CloudSpokes API
57
+ def self.all
58
+ request(:get, '', {}).map {|item| new item}
59
+ end
60
+
61
+ # Returns the first record
62
+ # TODO: when the API supports it, simply request for the first record.
63
+ # There should be no need to request for all the records first.
64
+ def self.first
65
+ all.first
66
+ end
67
+
68
+ # Finds an entity
69
+ def self.find(entity)
70
+ Rails.logger.info "========== running find for #{entity} for #{self.name}"
71
+ Kernel.const_get(self.name).new(raw_get entity)
72
+ end
73
+
74
+ # Wrap initialize with a sanitation clause
75
+ def initialize(params={})
76
+ Rails.logger.info "=====calling init with #{params}"
77
+ @raw_data = params.dup
78
+ params.delete_if {|k, v| !self.class.column_names.include? k.to_sym}
79
+ super(params)
80
+ end
81
+
82
+ # Returns the raw data that created this object
83
+ def raw_data
84
+ @raw_data
85
+ end
86
+
87
+ # Returns if this record has the id attribute set (used by url_for for routing)
88
+ def persisted?
89
+ !!id
90
+ end
91
+
92
+ def save
93
+ new_record? ? create : update
94
+ end
95
+
96
+ def update_attributes(attrs={})
97
+ attrs.each do |attr, value|
98
+ self.public_send("#{attr}=", value)
99
+ end
100
+ save
101
+ end
102
+
103
+ def new_record?
104
+ id.blank?
105
+ end
106
+
107
+ def self.create(attrs)
108
+ obj = new(attrs)
109
+ obj.save
110
+ obj
111
+ end
112
+
113
+ def self.api_request_headers
114
+ {
115
+ 'oauth_token' => access_token,
116
+ 'Authorization' => 'Token token="'+(ENV['CS_API_KEY']|| CloudSpokes::APP_CONFIG[:cs_api][:key])+'"',
117
+ 'Content-Type' => 'application/json'
118
+ }
119
+ end
120
+
121
+ # Convenience method to request an entity from the CloudSpokes RESTful source
122
+ # Accepts an array or a string
123
+ # If given an array, will join the elements with '/'
124
+ # If given a string, will use the argument as is
125
+ def self.raw_get(entities = [])
126
+ endpoint = endpoint_from_entities(entities)
127
+ Rails.logger.info "=====$$$$$ CALLING RAW GET #{entities} for #{endpoint}"
128
+ #Rails.cache.fetch("#{endpoint}", expires_in: ENDPOINT_EXPIRY.minutes) do
129
+ get_response(RestClient.get(endpoint, api_request_headers))
130
+ #end
131
+ end
132
+
133
+ # Sanitized response to only the attributes we've defined
134
+ def self.get(entity = '')
135
+ raw_get(entity).delete_if {|k, v| !column_names.include? k.to_sym}
136
+ end
137
+
138
+ def self.endpoint_from_entities(entities = [])
139
+ entities = entities.respond_to?(:join) ? entities.join("/") : entities.to_s
140
+ entities.present? ? "#{api_endpoint}/#{entities}" : api_endpoint
141
+ end
142
+
143
+ def self.get_response(data)
144
+ Hashie::Mash.new(JSON.parse(data)).response
145
+ end
146
+
147
+ def self.request(method, entities, data)
148
+ Rails.logger.info "===== access_token: #{access_token}"
149
+ Rails.logger.info "===== method: #{method}"
150
+ endpoint = endpoint_from_entities(entities)
151
+ Rails.logger.info "===== endpoint: #{endpoint}"
152
+ if method.to_sym == :get
153
+ endpoint += "?#{data.to_param}"
154
+ resp = RestClient.send method, endpoint, api_request_headers
155
+ else
156
+ data = data.to_json unless data.is_a?(String)
157
+ resp = RestClient.send method, endpoint, data, api_request_headers
158
+ end
159
+ get_response(resp)
160
+ end
161
+
162
+ def self.post(entities, data)
163
+ request :post, entities, data
164
+ end
165
+
166
+ def self.put(entities, data)
167
+ request :put, entities, data
168
+ end
169
+
170
+ private
171
+
172
+ def save_data
173
+ columns = self.class.column_names - self.class.rel_column_names - [:id]
174
+ columns.inject({}) do |ret, column|
175
+ val = self.public_send(column)
176
+ ret[column] = val if val.present?
177
+ ret
178
+ end
179
+ end
180
+
181
+ # define update_endpoint to subclass if you want to use another url for update
182
+ def update_endpoint
183
+ id
184
+ end
185
+
186
+ # define create_endpoint to subclass if you want to use another url for create
187
+ def create_endpoint
188
+ ""
189
+ end
190
+
191
+ def update
192
+ self.class.put update_endpoint, save_data
193
+ end
194
+
195
+ def create
196
+ self.class.post create_endpoint, save_data
197
+ end
198
+ end
@@ -0,0 +1,12 @@
1
+ class CloudSpokes::Model::Category < CloudSpokes::Model::BaseApi
2
+ attr_accessor :id, :attributes,
3
+ :name, :color
4
+
5
+ def self.api_endpoint
6
+ CloudSpokes::APP_CONFIG[:cs_api][:categories]
7
+ end
8
+
9
+ def self.names
10
+ all.map {|category| category.name}
11
+ end
12
+ end
@@ -0,0 +1,32 @@
1
+ class CloudSpokes::Model::Member < CloudSpokes::Model::BaseApi
2
+ attr_accessor :id, :name, :profile_pic, :attributes,
3
+ :challenges_entered, :active_challenges, :time_zone,
4
+ :total_1st_place, :total_2nd_place, :total_3st_place,
5
+ :total_wins, :total_public_money, :total_points, :valid_submissions,
6
+ :summary_bio, :payments
7
+
8
+ has_many :recommendations
9
+ has_many :challenges, parent: CloudSpokes::Model::Member
10
+
11
+ def self.api_endpoint
12
+ CloudSpokes::APP_CONFIG[:cs_api][:members]
13
+ end
14
+
15
+ # Used for resourceful routes (instead of id)
16
+ def to_param
17
+ name
18
+ end
19
+
20
+ # has_many :payments
21
+ # Note that the json does not expose this method
22
+ # TODO (this requires authentication)
23
+ def payments
24
+ 'nil'
25
+ end
26
+
27
+ def self.search(keyword)
28
+ request(:get, "search", {:keyword => keyword})
29
+ .map {|member| CloudSpokes::Model::Member.new member}
30
+ end
31
+
32
+ end
@@ -0,0 +1,5 @@
1
+ module CloudSpokes
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace CloudSpokes
4
+ end
5
+ end
@@ -0,0 +1,15 @@
1
+ module CloudSpokes
2
+ class Railtie < Rails::Railtie
3
+
4
+ initializer "cloud_spokes.configure_rails_initialization" do
5
+ CloudSpokes::APP_CONFIG =
6
+ begin
7
+ YAML.load_file("#{Rails.root}/config/config.yml")[Rails.env]
8
+ rescue Errno::ENOENT => e
9
+ gem_config = $LOAD_PATH.find {|dir| dir =~ /app\/config/}
10
+ YAML.load_file("#{gem_config}/config.yml")[Rails.env]
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,3 @@
1
+ module CloudSpokes
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "cloud_spokes_api_model/engine"
2
+ require 'cloud_spokes_api_model/railtie' if defined?(Rails)
3
+ require 'hashie'
4
+ require 'rest_client'
5
+
6
+ module CloudSpokes
7
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :cloud_spokes_api_model do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class CloudSpokesApiModelTest < ActiveSupport::TestCase
4
+ test "truth" do
5
+ assert_kind_of Module, CloudSpokesApiModel
6
+ end
7
+ end