gamifier 1.0.3
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/.gitignore +17 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +60 -0
- data/Rakefile +2 -0
- data/examples/dev.rb +186 -0
- data/examples/preprod.rb +218 -0
- data/gamifier.gemspec +24 -0
- data/lib/gamifier/collection.rb +26 -0
- data/lib/gamifier/dsl/network.rb +33 -0
- data/lib/gamifier/dsl/site.rb +134 -0
- data/lib/gamifier/dsl.rb +24 -0
- data/lib/gamifier/engine.rb +84 -0
- data/lib/gamifier/errors.rb +6 -0
- data/lib/gamifier/model.rb +212 -0
- data/lib/gamifier/models/activity.rb +8 -0
- data/lib/gamifier/models/activity_definition.rb +6 -0
- data/lib/gamifier/models/group.rb +6 -0
- data/lib/gamifier/models/player.rb +20 -0
- data/lib/gamifier/models/reward.rb +6 -0
- data/lib/gamifier/models/reward_definition.rb +14 -0
- data/lib/gamifier/models/site.rb +6 -0
- data/lib/gamifier/models/track.rb +6 -0
- data/lib/gamifier/models/unit.rb +16 -0
- data/lib/gamifier/models/user.rb +6 -0
- data/lib/gamifier/version.rb +3 -0
- data/lib/gamifier.rb +76 -0
- data/spec/integration/dsl_integration_spec.rb +76 -0
- data/spec/integration/player_integration_spec.rb +41 -0
- data/spec/integration/user_integration_spec.rb +19 -0
- data/spec/spec_helper.rb +7 -0
- data/spec/spec_integration_helper.rb +51 -0
- data/spec/unit/collection_spec.rb +21 -0
- data/spec/unit/dsl/network_spec.rb +34 -0
- data/spec/unit/dsl/site_spec.rb +54 -0
- data/spec/unit/dsl_spec.rb +9 -0
- data/spec/unit/engine_spec.rb +135 -0
- data/spec/unit/gamifier_spec.rb +60 -0
- data/spec/unit/model_spec.rb +182 -0
- data/spec/unit/models/player_spec.rb +40 -0
- metadata +179 -0
@@ -0,0 +1,134 @@
|
|
1
|
+
module Gamifier
|
2
|
+
module DSL
|
3
|
+
class Site
|
4
|
+
attr_reader :behaviors
|
5
|
+
attr_reader :units
|
6
|
+
attr_reader :rewards
|
7
|
+
attr_reader :missions
|
8
|
+
attr_reader :tracks
|
9
|
+
attr_reader :source
|
10
|
+
|
11
|
+
def initialize(*args)
|
12
|
+
@source = ::Gamifier::Site.new(*args)
|
13
|
+
@behaviors = []
|
14
|
+
@units = []
|
15
|
+
@rewards = []
|
16
|
+
@missions = []
|
17
|
+
@tracks = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def engine
|
21
|
+
source.engine
|
22
|
+
end
|
23
|
+
|
24
|
+
def behavior(name, &block)
|
25
|
+
new_behavior = engine.activity_definitions.build(:name => name)
|
26
|
+
behaviors.push new_behavior
|
27
|
+
|
28
|
+
DSL.eval_with_context(new_behavior, &block)
|
29
|
+
end
|
30
|
+
|
31
|
+
def reward(name, &block)
|
32
|
+
new_reward = engine.reward_definitions.build(:name => name)
|
33
|
+
rewards.push new_reward
|
34
|
+
|
35
|
+
DSL.eval_with_context(new_reward, &block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def unit(name, &block)
|
39
|
+
new_unit = engine.units.build(:name => name)
|
40
|
+
units.push new_unit
|
41
|
+
|
42
|
+
DSL.eval_with_context(new_unit, &block)
|
43
|
+
end
|
44
|
+
|
45
|
+
def mission(name, &block)
|
46
|
+
new_mission = engine.groups.build(:name => name)
|
47
|
+
missions.push new_mission
|
48
|
+
|
49
|
+
DSL.eval_with_context(new_mission, &block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def track(name, &block)
|
53
|
+
new_track = engine.tracks.build(:label => name)
|
54
|
+
tracks.push new_track
|
55
|
+
|
56
|
+
DSL.eval_with_context(new_track, &block)
|
57
|
+
end
|
58
|
+
|
59
|
+
def save
|
60
|
+
if super
|
61
|
+
save_units!
|
62
|
+
save_behaviors!
|
63
|
+
save_rewards!
|
64
|
+
save_missions!
|
65
|
+
save_tracks!
|
66
|
+
else
|
67
|
+
raise Error, "Can't save #{self}"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def save_units!
|
72
|
+
units.each do |unit|
|
73
|
+
Gamifier.logger.info "Saving units..."
|
74
|
+
unit.site = self.url
|
75
|
+
if gunit = engine.send(unit.class.path).find_by_name(unit.name, :site => unit.site)
|
76
|
+
unit._id = gunit._id
|
77
|
+
# FIXME: once Badgeville returns the unit _id in the
|
78
|
+
# response, remove the next line:
|
79
|
+
next
|
80
|
+
end
|
81
|
+
unit.save || raise(Error, "Can't save #{unit}")
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def save_behaviors!
|
86
|
+
behaviors.each do |behavior|
|
87
|
+
Gamifier.logger.info "Saving behaviors..."
|
88
|
+
behavior.site_id = self._id
|
89
|
+
if gbehavior = engine.send(behavior.class.path).find_by_name(behavior.name, :site => self.url)
|
90
|
+
behavior._id = gbehavior._id
|
91
|
+
end
|
92
|
+
behavior.save || raise(Error, "Can't save #{behavior}")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def save_rewards!
|
97
|
+
rewards.each do |reward|
|
98
|
+
Gamifier.logger.info "Saving rewards..."
|
99
|
+
reward.site_id = self._id
|
100
|
+
if greward = engine.send(reward.class.path).find_by_name(reward.name, :site => self.url)
|
101
|
+
reward._id = greward._id
|
102
|
+
end
|
103
|
+
reward.save || raise(Error, "Can't save #{reward}")
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def save_missions!
|
108
|
+
missions.each do |mission|
|
109
|
+
Gamifier.logger.info "Saving missions..."
|
110
|
+
mission.site_id = self._id
|
111
|
+
if gmission = engine.send(mission.class.path).find_by_name(mission.name, :site => self.url)
|
112
|
+
mission._id = gmission._id
|
113
|
+
end
|
114
|
+
mission.save || raise(Error, "Can't save #{mission}")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def save_tracks!
|
119
|
+
tracks.each do |track|
|
120
|
+
Gamifier.logger.info "Saving tracks..."
|
121
|
+
track.site_id = self._id
|
122
|
+
if gtrack = engine.send(track.class.path).find_by_label(track.label, :site => self.url)
|
123
|
+
track._id = gtrack._id
|
124
|
+
end
|
125
|
+
track.save || raise(Error, "Can't save #{track}")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def method_missing(*args, &block)
|
130
|
+
source.send(*args, &block)
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
data/lib/gamifier/dsl.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'gamifier/dsl/network'
|
2
|
+
require 'gamifier/dsl/site'
|
3
|
+
|
4
|
+
module Gamifier
|
5
|
+
module DSL
|
6
|
+
class << self
|
7
|
+
def eval_with_context(new_context, &block)
|
8
|
+
new_context.extend DSL
|
9
|
+
if block
|
10
|
+
new_context.instance_eval(&block)
|
11
|
+
end
|
12
|
+
new_context
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def set(key, *args, &block)
|
17
|
+
if block
|
18
|
+
self.send("#{key}=".to_sym, block)
|
19
|
+
else
|
20
|
+
self.send("#{key}=".to_sym, *args)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'faraday'
|
2
|
+
require 'gamifier/collection'
|
3
|
+
require 'json'
|
4
|
+
require 'rack/utils'
|
5
|
+
|
6
|
+
module Gamifier
|
7
|
+
class Engine
|
8
|
+
MODELS = %w{Activity ActivityDefinition Group Player Reward RewardDefinition Site Track Unit User}
|
9
|
+
|
10
|
+
attr_accessor :connection
|
11
|
+
attr_writer :config
|
12
|
+
|
13
|
+
def initialize(opts = {})
|
14
|
+
opts.each{|k,v| config[k.to_sym] = v}
|
15
|
+
raise ArgumentError, "Please configure a :uri and :key first." unless ok?
|
16
|
+
@connection = Faraday::Connection.new(:url => uri_to) do |builder|
|
17
|
+
builder.use Faraday::Adapter::NetHttp # make http requests with Net::HTTP
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def config
|
22
|
+
@config ||= Gamifier.config.dup
|
23
|
+
end
|
24
|
+
|
25
|
+
def ok?
|
26
|
+
!config.values_at(:key, :uri).any?{|k| k.nil? || k.empty?}
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sends an HTTP request corresponding to +method+, on +path+, with
|
30
|
+
# any options given in +opts+:
|
31
|
+
# +query+:: a hash of query parameters to pass with the request
|
32
|
+
# +head+:: a hash of HTTP headers to pass with the request
|
33
|
+
# +body+:: the body to send with the request. If you pass a Ruby
|
34
|
+
# object, it will be automatically converted into
|
35
|
+
# x-www-form-urlencoded by default.
|
36
|
+
def transmit(method, path, opts = {})
|
37
|
+
res = connection.send(method) do |req|
|
38
|
+
req.url [path, "json"].join("."), (opts[:query] || {})
|
39
|
+
(opts[:head] || {}).each do |k,v|
|
40
|
+
req.headers[k] = v
|
41
|
+
end
|
42
|
+
req.headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
|
43
|
+
req.body = self.class.encode_www_form(opts[:body]) unless opts[:body].nil?
|
44
|
+
Gamifier.logger.debug "#{method.to_s.upcase} #{req.inspect}"
|
45
|
+
end
|
46
|
+
Gamifier.logger.debug "#{res.inspect}"
|
47
|
+
body = JSON.parse(res.body) rescue res.body
|
48
|
+
case res.status
|
49
|
+
when 204
|
50
|
+
true
|
51
|
+
when 200...300
|
52
|
+
body
|
53
|
+
when 404
|
54
|
+
nil
|
55
|
+
when 422
|
56
|
+
# Badgeville returns 422 when an entry already exists or is not valid
|
57
|
+
[:get, :head].include?(method) ? nil : false
|
58
|
+
when 400...500
|
59
|
+
raise HTTPClientError, body
|
60
|
+
when 500...600
|
61
|
+
raise HTTPServerError, body
|
62
|
+
else
|
63
|
+
raise HTTPError, body
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def uri_to(path = "")
|
68
|
+
URI.join(File.join(::Gamifier.config[:uri], ::Gamifier.config[:key], ""), path)
|
69
|
+
end
|
70
|
+
|
71
|
+
MODELS.each do |model|
|
72
|
+
underscored = Gamifier.underscore(model)
|
73
|
+
require "gamifier/models/#{underscored}"
|
74
|
+
klass = Gamifier.const_get(model)
|
75
|
+
define_method(klass.path.to_sym) do
|
76
|
+
Collection.new(self, klass)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.encode_www_form(enum)
|
81
|
+
Rack::Utils.build_nested_query(enum)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
|
3
|
+
module Gamifier
|
4
|
+
class Model < OpenStruct
|
5
|
+
attr_writer :engine
|
6
|
+
|
7
|
+
# The engine to use when creating or updating records. Default to
|
8
|
+
# the global Gamifier engine if not set via <tt>#engine=</tt>
|
9
|
+
# method.
|
10
|
+
def engine
|
11
|
+
@engine ||= Gamifier.engine
|
12
|
+
end
|
13
|
+
|
14
|
+
def attributes; @table.dup; end
|
15
|
+
|
16
|
+
def save
|
17
|
+
new? ? create : update
|
18
|
+
end
|
19
|
+
|
20
|
+
# Allow to update only the specific attributes given in the +opts+ Hash.
|
21
|
+
def update_attributes(opts = {})
|
22
|
+
raise Error, "Can't update an object if it has not been saved yet" if new?
|
23
|
+
update(opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def payload_for_submission(opts = {})
|
27
|
+
attr_to_keep = opts.empty? ? attributes : opts
|
28
|
+
|
29
|
+
attr_to_keep = attr_to_keep.reject{|k,v|
|
30
|
+
self.class.special_attributes.include?(k.to_sym) || (
|
31
|
+
!self.class.mutable_attributes.empty? && !self.class.mutable_attributes.include?(k.to_sym)
|
32
|
+
)
|
33
|
+
}
|
34
|
+
|
35
|
+
# Nested hashes are dumped as JSON payload.
|
36
|
+
attr_to_keep.each do |k,v|
|
37
|
+
attr_to_keep[k] = encode(k.to_sym,v) || case v
|
38
|
+
when Hash
|
39
|
+
JSON.dump(v)
|
40
|
+
# Lazyloaded attributes
|
41
|
+
when Proc
|
42
|
+
v.call
|
43
|
+
else
|
44
|
+
v.to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
h = { self.class.container => attr_to_keep }
|
49
|
+
|
50
|
+
self.class.special_attributes.each do |key|
|
51
|
+
h[key] = send(key)
|
52
|
+
end
|
53
|
+
|
54
|
+
h
|
55
|
+
end
|
56
|
+
|
57
|
+
# Overwrite in descendants to specifically encode a key/value pair.
|
58
|
+
def encode(key, value)
|
59
|
+
return nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def _id
|
63
|
+
super || attributes[:id]
|
64
|
+
end
|
65
|
+
|
66
|
+
def destroy
|
67
|
+
return true if new?
|
68
|
+
engine.transmit :delete, path
|
69
|
+
end
|
70
|
+
|
71
|
+
def replace_if_successful(response)
|
72
|
+
if response.kind_of?(Hash)
|
73
|
+
response.each{|k,v| send("#{k}=".to_sym, v)}
|
74
|
+
self
|
75
|
+
else
|
76
|
+
response
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# Returns true if the current object does not exist on the server.
|
81
|
+
def new?
|
82
|
+
self._id.nil?
|
83
|
+
end
|
84
|
+
|
85
|
+
def path
|
86
|
+
[self.class.path, self._id || "undefined"].join("/")
|
87
|
+
end
|
88
|
+
|
89
|
+
# To be included as instance methods into the Collection class used
|
90
|
+
# to instantiate and find objects. Add more in descendants if you
|
91
|
+
# want to add more methods. See Gamifier::Player for an example.
|
92
|
+
module FinderMethods
|
93
|
+
# Fetch all the records according to the given query parameters.
|
94
|
+
# If a block is given, it will automatically lazy-load all the
|
95
|
+
# pages and yield each entry, until all the items have been
|
96
|
+
# loaded, or until the user returns from the block.
|
97
|
+
def all(params = {}, &block)
|
98
|
+
params[:page] ||= 1
|
99
|
+
params[:per_page] ||= 50
|
100
|
+
res = engine.transmit(:get, path, :query => params)
|
101
|
+
if res.kind_of?(Hash)
|
102
|
+
entries = res['data'].map{|h| build(h)}
|
103
|
+
if block
|
104
|
+
# Lazy load all the pages
|
105
|
+
entries.each{|entry| yield(entry)}
|
106
|
+
params[:page] += 1
|
107
|
+
all(params, &block) unless res['paging'].empty? || res['data'].empty?
|
108
|
+
end
|
109
|
+
entries
|
110
|
+
else
|
111
|
+
res
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def find_by(key, value, params = {})
|
116
|
+
all(params) do |entry|
|
117
|
+
return entry if entry.respond_to?(key) && entry.send(key) == value.to_s
|
118
|
+
end
|
119
|
+
return nil
|
120
|
+
end
|
121
|
+
|
122
|
+
def find_by_name(name, params = {})
|
123
|
+
find_by(:name, name, params)
|
124
|
+
end
|
125
|
+
|
126
|
+
def find_by_label(label, params = {})
|
127
|
+
find_by(:label, label, params)
|
128
|
+
end
|
129
|
+
|
130
|
+
def find(key, params = {})
|
131
|
+
res = engine.transmit(:get, [path, key].join("/"), :query => params)
|
132
|
+
select_first_entry_if_any(res)
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_or_create(key, params = {})
|
136
|
+
find(key, params) || build(params).save
|
137
|
+
end
|
138
|
+
|
139
|
+
protected
|
140
|
+
# Selects the first entry out of a data array, or out of a single
|
141
|
+
# data entry.
|
142
|
+
def select_first_entry_if_any(response)
|
143
|
+
if response.kind_of?(Hash) && response.has_key?('data')
|
144
|
+
entry = [response['data']].flatten[0]
|
145
|
+
return nil if entry.nil?
|
146
|
+
build(entry)
|
147
|
+
else
|
148
|
+
response
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
module ClassMethods
|
154
|
+
def reset!
|
155
|
+
@path = nil
|
156
|
+
end
|
157
|
+
|
158
|
+
# Sets or retrieve the path to use to access the resource on the
|
159
|
+
# remote server. By default the path will be the pluralized
|
160
|
+
# container name (naive pluralization: container+s).
|
161
|
+
def path(value = nil)
|
162
|
+
if value.nil?
|
163
|
+
@path ||= container+"s"
|
164
|
+
else
|
165
|
+
@path = value
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
# Declare attributes that must be submitted outside the default
|
170
|
+
# container.
|
171
|
+
def special_attributes *values
|
172
|
+
store_or_get("@special_attributes", values)
|
173
|
+
end
|
174
|
+
|
175
|
+
# Define the attributes that will be sent when creating or
|
176
|
+
# updating a model.
|
177
|
+
def mutable_attributes *values
|
178
|
+
store_or_get("@mutable_attributes", values)
|
179
|
+
end
|
180
|
+
|
181
|
+
def store_or_get(variable, values)
|
182
|
+
if values.empty?
|
183
|
+
v = instance_variable_get(variable)
|
184
|
+
v ||= []
|
185
|
+
else
|
186
|
+
instance_variable_set(variable, values.map{|v| v.to_sym})
|
187
|
+
end
|
188
|
+
end
|
189
|
+
# Returns the container to use when submitting data. E.g. when
|
190
|
+
# submitting user attributes as user[email]=xxx, the container is
|
191
|
+
# 'user'. Basically, this is the underscored class name.
|
192
|
+
def container
|
193
|
+
Gamifier.underscore(name)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
extend ClassMethods
|
198
|
+
|
199
|
+
protected
|
200
|
+
|
201
|
+
def create
|
202
|
+
res = engine.transmit :post, self.class.path, :body => payload_for_submission
|
203
|
+
replace_if_successful(res)
|
204
|
+
end
|
205
|
+
|
206
|
+
def update(opts = {})
|
207
|
+
res = engine.transmit :put, path, :body => payload_for_submission(opts)
|
208
|
+
replace_if_successful(res)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'gamifier/model'
|
2
|
+
|
3
|
+
module Gamifier
|
4
|
+
class Player < Model
|
5
|
+
|
6
|
+
special_attributes :email, :site
|
7
|
+
mutable_attributes :first_name, :last_name, :nickname, :display_name, :picture_url, :admin
|
8
|
+
|
9
|
+
module FinderMethods
|
10
|
+
def find_by_site_and_email(site, email, params = {})
|
11
|
+
res = engine.transmit(:get, path, :query => params.merge({:site => site, :email => email}))
|
12
|
+
select_first_entry_if_any(res)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def credit(verb, metadata = {})
|
17
|
+
engine.activities.build(metadata.merge({:player_id => _id, :verb => verb})).save
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/lib/gamifier.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require "gamifier/version"
|
3
|
+
require 'gamifier/errors'
|
4
|
+
|
5
|
+
module Gamifier
|
6
|
+
class << self
|
7
|
+
attr_writer :logger
|
8
|
+
|
9
|
+
# Sets a configuration option. The keys will be symbolized.
|
10
|
+
def set(key, opts = nil)
|
11
|
+
if opts.nil?
|
12
|
+
key.each do |k,v|
|
13
|
+
config[k.to_sym] = v
|
14
|
+
end
|
15
|
+
else
|
16
|
+
config[key.to_sym] = opts
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Returns the configuration Hash.
|
21
|
+
def config
|
22
|
+
@config ||= {}
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the global Engine. If you need multiple engine with
|
26
|
+
# different API keys and/or URIs, you should create a new engine
|
27
|
+
# directly with Gamifier::Engine.new
|
28
|
+
def engine(&block)
|
29
|
+
@engine ||= Engine.new
|
30
|
+
block.call(@engine) if block
|
31
|
+
@engine
|
32
|
+
end
|
33
|
+
|
34
|
+
# Creates and returns a new DSL::Network object. The given block
|
35
|
+
# will be evaluated in the context of the network object.
|
36
|
+
#
|
37
|
+
# Usage:
|
38
|
+
#
|
39
|
+
# network = Gamifier.dsl do
|
40
|
+
# site 'my-site' do
|
41
|
+
# set :url, 'my-site.com'
|
42
|
+
# ...
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
def dsl(&block)
|
46
|
+
network = DSL::Network.new
|
47
|
+
network.instance_eval(&block)
|
48
|
+
network
|
49
|
+
end
|
50
|
+
|
51
|
+
def logger
|
52
|
+
@logger ||= begin
|
53
|
+
l = Logger.new(STDERR)
|
54
|
+
l.level = Logger::WARN
|
55
|
+
l
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def reset!
|
60
|
+
@engine = nil
|
61
|
+
config.clear
|
62
|
+
end
|
63
|
+
|
64
|
+
def underscore(camel_cased_word)
|
65
|
+
camel_cased_word.to_s.gsub(/::/, '/').
|
66
|
+
gsub("Gamifier/", '').
|
67
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
68
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
69
|
+
tr("-", "_").
|
70
|
+
downcase
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
require 'gamifier/engine'
|
76
|
+
require 'gamifier/dsl'
|