cloud_spokes_api_model 0.1.0
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.
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +38 -0
- data/app/config/config.yml +16 -0
- data/app/models/active_model/model.rb +99 -0
- data/app/models/cloud_spokes/model/base_api.rb +198 -0
- data/app/models/cloud_spokes/model/category.rb +12 -0
- data/app/models/cloud_spokes/model/member.rb +32 -0
- data/lib/cloud_spokes_api_model/engine.rb +5 -0
- data/lib/cloud_spokes_api_model/railtie.rb +15 -0
- data/lib/cloud_spokes_api_model/version.rb +3 -0
- data/lib/cloud_spokes_api_model.rb +7 -0
- data/lib/tasks/cloud_spokes_api_model_tasks.rake +4 -0
- data/test/cloud_spokes_api_model_test.rb +7 -0
- data/test/dummy/README.rdoc +261 -0
- data/test/dummy/Rakefile +7 -0
- data/test/dummy/app/assets/javascripts/application.js +15 -0
- data/test/dummy/app/assets/stylesheets/application.css +13 -0
- data/test/dummy/app/controllers/application_controller.rb +3 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/config/application.rb +59 -0
- data/test/dummy/config/boot.rb +10 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +67 -0
- data/test/dummy/config/environments/test.rb +37 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/inflections.rb +15 -0
- data/test/dummy/config/initializers/mime_types.rb +5 -0
- data/test/dummy/config/initializers/secret_token.rb +7 -0
- data/test/dummy/config/initializers/session_store.rb +8 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +5 -0
- data/test/dummy/config/routes.rb +58 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/public/404.html +26 -0
- data/test/dummy/public/422.html +26 -0
- data/test/dummy/public/500.html +25 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/script/rails +6 -0
- data/test/test_helper.rb +15 -0
- 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
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,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
|