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