dm-is-remixable 0.9.7

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,5 @@
1
+ === 0.9.3 / 2008-07-31
2
+
3
+ * 1 major enhancement
4
+
5
+ * Pre-Release, super beta.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2008 Cory ODaniel
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/Manifest.txt ADDED
@@ -0,0 +1,24 @@
1
+ History.txt
2
+ LICENSE
3
+ Manifest.txt
4
+ README.txt
5
+ Rakefile
6
+ TODO
7
+ lib/dm-is-remixable.rb
8
+ lib/dm-is-remixable/is/remixable.rb
9
+ lib/dm-is-remixable/is/version.rb
10
+ spec/data/addressable.rb
11
+ spec/data/article.rb
12
+ spec/data/billable.rb
13
+ spec/data/bot.rb
14
+ spec/data/commentable.rb
15
+ spec/data/image.rb
16
+ spec/data/rating.rb
17
+ spec/data/tag.rb
18
+ spec/data/taggable.rb
19
+ spec/data/topic.rb
20
+ spec/data/user.rb
21
+ spec/data/viewable.rb
22
+ spec/integration/remixable_spec.rb
23
+ spec/spec.opts
24
+ spec/spec_helper.rb
data/README.txt ADDED
@@ -0,0 +1,127 @@
1
+ = dm-is-remixable
2
+
3
+ DataMapper::Is::Remixable allows you to create re-usable chunks of relational data, its kind of like multiple
4
+ inheritance for models.
5
+
6
+
7
+ For example:
8
+ #Comments are everywhere, why define them over and over?
9
+ module Comment
10
+ include DataMapper::Resource
11
+ is :remixable
12
+
13
+ property :id, Integer, :key => true, :serial => true
14
+ property :body, String
15
+ property :created_at, DateTime
16
+ end
17
+
18
+ #Lots of things can be addressable; people, buildings
19
+ module Addressable
20
+ include DataMapper::Resource
21
+
22
+ is :remixable,
23
+ :suffix => "address" #Default suffix is module name pluralized
24
+
25
+ property :id, Integer, :key => true, :serial => true
26
+
27
+ property :label, String #home, work, etc...
28
+
29
+ property :address1, String, :length => 255
30
+ property :address2, String, :length => 255
31
+
32
+ property :city, String, :length => 128
33
+ property :state, String, :length => 2
34
+ property :zip, String, :length => 5..10
35
+ end
36
+
37
+ module Vote
38
+ include DataMapper::Resource
39
+
40
+ is :remixable
41
+
42
+ property :id, Integer, :key => true, :serial => true
43
+ property :opinion, Enum.new("good","bad")
44
+
45
+ end
46
+
47
+ class Location
48
+ include DataMapper::Resource
49
+
50
+ #Location can have 1 address
51
+ remix 1, :addressables
52
+
53
+ # This does the following:
54
+ # - creates a class called LocationAddress
55
+ (default name would be LocationAddressable, but Addressable#suffix was specified)
56
+ # - duplicates the properties of Addressable within LocationAddress
57
+ # - a table called location_addresses
58
+ # - creates Location#location_addresses accessor
59
+
60
+ #... methods, properties, etc ...#
61
+ end
62
+
63
+
64
+ class User
65
+ include DataMapper::Resource
66
+
67
+ #User can have many addresses
68
+ remix n, :addressables, :as => "addresses"
69
+ # - creates a class called UserAddress
70
+ (default name would be UserAddressable, but Addressable#suffix was specified)
71
+ # - duplicates the properties of Addressable within UserAddress
72
+ # - a table called user_addresses
73
+ # - creates User#user_addresses accessor
74
+ # - creates an accessor alias User#addresses
75
+
76
+ enhance :addressables do
77
+ storage_names[:default] = "a_different_table_name"
78
+ property :label, Enum.new("work","home")
79
+
80
+ #This adds a column to user_addresses to store an address label
81
+ end
82
+
83
+ #... methods, properties, etc ...#
84
+ end
85
+
86
+ class Article
87
+ include DataMapper::Resource
88
+
89
+ remix n, :comments, :for => "User"
90
+ # - creates a class called ArticleComment
91
+ # - duplicates the properties of Comment within ArticleComment
92
+ # - a table called article_comments
93
+ # - creates Article#article_comments
94
+ # - creates User#article_comments
95
+
96
+ #... methods, properties, etc ...#
97
+
98
+ end
99
+
100
+ class Video
101
+ include DataMapper::Resource
102
+
103
+ remix n, :comments, :for => "User", :as => "comments"
104
+ # - creates a class called VideoComment
105
+ # - duplicates the properties of Comment within VideoComment
106
+ # - a table called video_comments
107
+ # - creates Video#video_comments
108
+ # - creates User#video_comments
109
+ # - create Video#comments
110
+
111
+ enhance :comments do
112
+ # VideoComment now has the method #reverse
113
+ def reverse
114
+ return self.body.reverse
115
+ end
116
+
117
+ #I like YouTubes ability for users to vote comments up and down
118
+ remix 1, :votes, :for => "User"
119
+ # - creates a class called VideoCommentVote
120
+ # - duplicates the properties of Vote within VideoCommentVote
121
+ # - a table called video_comment_votes
122
+ # - creates Video#video_comments#votes
123
+
124
+ end
125
+
126
+ #... methods, properties, etc ...#
127
+ end
data/Rakefile ADDED
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'spec'
3
+ require 'spec/rake/spectask'
4
+ require 'pathname'
5
+
6
+ ROOT = Pathname(__FILE__).dirname.expand_path
7
+ require ROOT + 'lib/dm-is-remixable/is/version'
8
+
9
+ AUTHOR = "Cory O'Daniel"
10
+ EMAIL = "dm-is-remixable [a] coryodaniel [d] com"
11
+ GEM_NAME = "dm-is-remixable"
12
+ GEM_VERSION = DataMapper::Is::Remixable::VERSION
13
+ GEM_DEPENDENCIES = [["dm-core", GEM_VERSION]]
14
+ GEM_CLEAN = ["log", "pkg"]
15
+ GEM_EXTRAS = { :has_rdoc => true, :extra_rdoc_files => %w[ README.txt LICENSE TODO ] }
16
+
17
+ PROJECT_NAME = "datamapper"
18
+ PROJECT_URL = "http://github.com/sam/dm-more/tree/master/dm-remixes"
19
+ PROJECT_DESCRIPTION = PROJECT_SUMMARY = "dm-is-remixable allow you to create reusable data functionality"
20
+
21
+ require ROOT.parent + 'tasks/hoe'
22
+
23
+ task :default => [ :spec ]
24
+
25
+ WIN32 = (RUBY_PLATFORM =~ /win32|mingw|cygwin/) rescue nil
26
+ SUDO = WIN32 ? '' : ('sudo' unless ENV['SUDOLESS'])
27
+
28
+ desc "Install #{GEM_NAME} #{GEM_VERSION}"
29
+ task :install => [ :package ] do
30
+ sh "#{SUDO} gem install --local pkg/#{GEM_NAME}-#{GEM_VERSION} --no-ri --no-rdoc --no-update-sources", :verbose => false
31
+ end
32
+
33
+ desc "Uninstall #{GEM_NAME} #{GEM_VERSION} (default ruby)"
34
+ task :uninstall => [ :clobber ] do
35
+ sh "#{SUDO} gem uninstall #{GEM_NAME} -v#{GEM_VERSION} -I -x", :verbose => false
36
+ end
37
+
38
+ desc 'Run specifications'
39
+ Spec::Rake::SpecTask.new(:spec) do |t|
40
+ t.spec_opts << '--options' << 'spec/spec.opts' if File.exists?('spec/spec.opts')
41
+ t.spec_files = Pathname.glob((ROOT + 'spec/**/*_spec.rb').to_s)
42
+
43
+ begin
44
+ t.rcov = ENV.has_key?('NO_RCOV') ? ENV['NO_RCOV'] != 'true' : true
45
+ t.rcov_opts << '--exclude' << 'spec'
46
+ t.rcov_opts << '--text-summary'
47
+ t.rcov_opts << '--sort' << 'coverage' << '--sort-reverse'
48
+ rescue Exception
49
+ # rcov not installed
50
+ end
51
+ end
data/TODO ADDED
@@ -0,0 +1,30 @@
1
+ FIXME
2
+ ====
3
+ Add ability to have a unique relationship when joining through remixable
4
+ This is stubbed at remixable.rb @~ line 223; waiting on stategic eager loading
5
+ M:M Video.remix n, :commentables, :as => "comments", :for => "User", :unique => false
6
+ M:M Article.remix n, :views, :for => "User", :unique => true
7
+ in the generated table article_views article_id and user_id would be a unique key,
8
+ only counting a users viewing of an article once
9
+
10
+ TODO
11
+ ====
12
+ - Add ability to set primary keys and indexes when remixing
13
+
14
+ - Test nested remixing User remixes Photogenic; Photogenic Remixes comments
15
+ - Test double+ remixing. User remixes Commentable; enhance Commentable remix Commentable
16
+
17
+ - Harvest Class methods (including it into Remixed Model gets the instance methods, but not class methods...)
18
+
19
+ - Squash protection;
20
+ IF ClassA => remix ModuleB, :table_name => "squashme"
21
+ AND ClassC => remix ModuleB, :table_name => "squashme" #SQUASHED THAT TABLE
22
+
23
+ - Remixable.related(*remixed_models)
24
+ Taggable.related(Article, JobPostings)
25
+
26
+
27
+ CONSIDERATIONS
28
+ ==============
29
+ - Customizing Assocations (http://datamapper.org/docs/associations.html)
30
+ - Adding Conditions to Associations (http://datamapper.org/docs/associations.html)
@@ -0,0 +1,402 @@
1
+ # reopen sam/extlib/lib/extlib/object.rb
2
+ class Object
3
+
4
+ def full_const_defined?(name)
5
+ !!full_const_get(name) rescue false
6
+ end
7
+
8
+ end
9
+
10
+ Extlib::Inflection.rule 'ess', 'esses'
11
+
12
+ module DataMapper
13
+ module Is
14
+ module Remixable
15
+
16
+ #==============================INCLUSION METHODS==============================#
17
+
18
+ # Adds remixer methods to DataMapper::Resource
19
+ def self.included(base)
20
+ base.send(:include,RemixerClassMethods)
21
+ base.send(:include,RemixerInstanceMethods)
22
+ end
23
+
24
+ # - is_remixable
25
+ # ==== Description
26
+ # Adds RemixeeClassMethods and RemixeeInstanceMethods to any model that is: remixable
27
+ # ==== Examples
28
+ # class User #Remixer
29
+ # remixes Commentable
30
+ # remixes Vote
31
+ # end
32
+ #
33
+ # module Commentable #Remixable
34
+ # include DataMapper::Resource
35
+ #
36
+ # is :remixable,
37
+ # :suffix => "comment"
38
+ # end
39
+ #
40
+ # module Vote #Remixable
41
+ # include DataMapper::Resource
42
+ #
43
+ # is :remixable
44
+ #
45
+ # ==== Notes
46
+ # These options are just available for whatever reason your Remixable Module name
47
+ # might not be what you'd like to see the table name and property accessor named.
48
+ # These are just configurable defaults, upon remixing the class_name and accessor there
49
+ # take precedence over the defaults set here
50
+ # ==== Options
51
+ # :suffix <String>
52
+ # Table suffix, defaults to YourModule.name.downcase.singular
53
+ # Yields table name of remixer_suffix; ie user_comments, user_votes
54
+ def is_remixable(options={})
55
+ extend DataMapper::Is::Remixable::RemixeeClassMethods
56
+ include DataMapper::Is::Remixable::RemixeeInstanceMethods
57
+ @is_remixable = true
58
+ # support clean suffixes for nested modules
59
+ default_suffix = Extlib::Inflection.demodulize(self.name).singular.snake_case
60
+ suffix(options.delete(:suffix) || default_suffix)
61
+ end
62
+
63
+
64
+ #==============================CLASS METHODS==============================#
65
+
66
+ # - RemixerClassMethods
67
+ # ==== Description
68
+ # Methods available to all DataMapper::Resources
69
+ module RemixerClassMethods
70
+ def self.included(base);end;
71
+
72
+ def is_remixable?
73
+ @is_remixable ||= false
74
+ end
75
+
76
+ # - remixables
77
+ # ==== Description
78
+ # Returns a hash of the remixables used by this class
79
+ # ==== Returns
80
+ # <Hash> Remixable Class Name => Remixed Class Name
81
+ def remixables
82
+ @remixables
83
+ end
84
+
85
+ # - remix
86
+ # ==== Description
87
+ # Remixes a Remixable Module
88
+ # ==== Parameters
89
+ # cardinality <~Fixnum> 1, n, x ...
90
+ # remixable <Symbol> plural of remixable; i.e. Comment => :comments
91
+ # options <Hash> options hash
92
+ # :class_name <String> Remixed Model name (Also creates a storage_name as tableize(:class_name))
93
+ # This is the class that will be created from the Remixable Module
94
+ # The storage_name can be changed via 'enhance' in the class that is remixing
95
+ # Default: self.name.downcase + "_" + remixable.suffix.pluralize
96
+ # :as <String> Alias to access associated data
97
+ # Default: tableize(:class_name)
98
+ # :for|:on <String> Class name to join to through Remixable
99
+ # This will create a M:M relationship THROUGH the remixable, rather than
100
+ # a 1:M with the remixable
101
+ # :via <String> changes the name of the second id in a unary relationship
102
+ # see example below; only used when remixing a module between the same class twice
103
+ # ie: self.class.to_s == options[:for||:on]
104
+ # :unique <Boolean> Only works with :for|:on; creates a unique composite key
105
+ # over the two table id's
106
+ # ==== Examples
107
+ # Given: User (Class), Addressable (Module)
108
+ #
109
+ # One-To-Many; Class-To-Remixable
110
+ #
111
+ # remix n, :addressables,
112
+ # :class_name => "UserAddress",
113
+ # :as => "addresses"
114
+ #
115
+ # Tables: users, user_addresses
116
+ # Classes: User, UserAddress
117
+ # User.user_addresses << UserAddress.new
118
+ # User.addresses << UserAddress.new
119
+ # --------------------------------------------
120
+ # --------------------------------------------
121
+ #
122
+ # Given: User (Class), Video (Class), Commentable (Module)
123
+ #
124
+ # Many-To-Many; Class-To-Class through RemixableIntermediate (Video allows Commentable for User)
125
+ #
126
+ # Video.remix n, :commentables
127
+ # :for => 'User' #:for & :on have same effect, just a choice of wording...
128
+ # --------------------------------------------
129
+ # --------------------------------------------
130
+ #
131
+ # Given: User (Class), User (Class), Commentable (Module)
132
+ #
133
+ # Many-To-Many Unary relationship between User & User through comments
134
+ # User.remix n, :commentables, :as => "comments", :for => 'User', :via => "commentor"
135
+ # => This would create user_id and commentor_id as the
136
+ #
137
+ def remix(cardinality, remixable, options={})
138
+ #A map for remixable names to Remixed Models
139
+ @remixables = {} if @remixables.nil?
140
+
141
+ # Allow nested modules to be remixable to better support using dm-is-remixable in gems
142
+ # Example (from my upcoming dm-is-rateable gem)
143
+ # remix n, "DataMapper::Is::Rateable::Rating", :as => :ratings
144
+ remixable_module = Object.full_const_get(Extlib::Inflection.classify(remixable))
145
+
146
+ unless remixable_module.is_remixable?
147
+ raise Exception, "#{remixable_module} is not remixable"
148
+ end
149
+
150
+ #Merge defaults/options
151
+ options = {
152
+ :as => nil,
153
+ :class_name => Extlib::Inflection.classify(self.name.snake_case + "_" + remixable_module.suffix.pluralize),
154
+ :for => nil,
155
+ :on => nil,
156
+ :unique => false,
157
+ :via => nil
158
+ }.merge(options)
159
+
160
+ #Make sure the class hasn't been remixed already
161
+ unless Object.full_const_defined?(Extlib::Inflection.classify(options[:class_name]))
162
+
163
+ #Storage name of our remixed model
164
+ options[:table_name] = Extlib::Inflection.tableize(options[:class_name])
165
+
166
+ #Other model to mix with in case of M:M through Remixable
167
+ options[:other_model] = options[:for] || options[:on]
168
+
169
+ DataMapper.logger.info "Generating Remixed Model: #{options[:class_name]}"
170
+ model = generate_remixed_model(remixable_module, options)
171
+
172
+ # map the remixable to the remixed model
173
+ # since this will be used from 'enhance api' i think it makes perfect sense to
174
+ # always refer to a remixable by its demodulized snake_cased constant name
175
+ remixable_key = Extlib::Inflection.demodulize(remixable_module.name).snake_case.to_sym
176
+ populate_remixables_mapping(model, options.merge(:remixable_key => remixable_key))
177
+
178
+ #Create relationships between Remixer and remixed class
179
+ if options[:other_model]
180
+ # M:M Class-To-Class w/ Remixable Module as intermediate table
181
+ # has n and belongs_to (or One-To-Many)
182
+ remix_many_to_many cardinality, model, options
183
+ else
184
+ # 1:M Class-To-Remixable
185
+ # has n and belongs_to (or One-To-Many)
186
+ remix_one_to_many cardinality, model, options
187
+ end
188
+
189
+ #Add accessor alias
190
+ attach_accessor(options) unless options[:as].nil?
191
+ else
192
+ DataMapper.logger.warn "#{__FILE__}:#{__LINE__} warning: already remixed constant #{options[:class_name]}"
193
+ end
194
+ end
195
+
196
+ # - enhance
197
+ # ==== Description
198
+ # Enhance a remix; allows nesting remixables, adding columns & functions to a remixed resource
199
+ # ==== Parameters
200
+ # remixable <Symbol> Name of remixable to enhance (plural or singular name of is :remixable module)
201
+ # model_class <Class, symbol, String> Name of the remixable generated Model Class.
202
+ # block <Proc> Enhancements to perform
203
+ # ==== Examples
204
+ # When you have one remixable:
205
+ #
206
+ # class Video
207
+ # include DataMapper::Resource
208
+ # remix Comment
209
+ #
210
+ # enhance :comments do
211
+ # remix n, :votes #This would result in something like YouTubes Voting comments up/down
212
+ #
213
+ # property :updated_at, DateTime
214
+ #
215
+ # def backwards; self.test.reverse; end;
216
+ # end
217
+ #
218
+ # When you remixe the same remixable modules twice:
219
+ #
220
+ # class Article
221
+ # include DataMapper::Resource
222
+ # remix n, :taggings, :for => User, :class_name => "UserArticleTagging"
223
+ # remix n, :taggings, :for => Bot, :class_name => "BotArticleTagging"
224
+ #
225
+ # enhance :taggings, "UserArticleTagging" do
226
+ # property :updated_at, DateTime
227
+ # belongs_to :user
228
+ # belongs_to :tag
229
+ # end
230
+ #
231
+ # enhance :taggings, "BotArticleTagging" do
232
+ # belongs_to :bot
233
+ # belongs_to :tag
234
+ # end
235
+
236
+ def enhance(remixable,remixable_model=nil, &block)
237
+ # always use innermost singular snake_cased constant name
238
+ remixable_name = remixable.to_s.singular.snake_case.to_sym
239
+ class_name = if remixable_model.nil?
240
+ @remixables[remixable_name].keys.first
241
+ else
242
+ Extlib::Inflection.demodulize(remixable_model.to_s).snake_case.to_sym
243
+ end
244
+
245
+ model = @remixables[remixable_name][class_name][:model] unless @remixables[remixable_name][class_name].nil?
246
+
247
+ unless model.nil?
248
+ model.class_eval &block
249
+ else
250
+ raise Exception, "#{remixable} must be remixed with :class_name option as #{remixable_model} before it can be enhanced"
251
+ end
252
+ end
253
+
254
+ private
255
+
256
+ # - attach_accessor
257
+ # ==== Description
258
+ # Creates additional alias for r/w accessor
259
+ # ==== Parameters
260
+ # options <Hash> options hash
261
+ def attach_accessor(options)
262
+ self.class_eval(<<-EOS, __FILE__, __LINE__ + 1)
263
+ alias #{options[:as].to_sym} #{options[:table_name].to_sym}
264
+ alias #{options[:as].to_sym}= #{options[:table_name].to_sym}=
265
+ EOS
266
+ end
267
+
268
+ # - populate_remixables_mapping
269
+ # ==== Description
270
+ # Populates the Hash of remixables with information about the remixable
271
+ # ==== Parameters
272
+ # remixable
273
+ # options <Hash> options hash
274
+ def populate_remixables_mapping(remixable_model, options)
275
+ key = options[:remixable_key]
276
+ accessor_name = options[:as] ? options[:as] : options[:table_name]
277
+ @remixables[key] ||= {}
278
+ model_key = Extlib::Inflection.demodulize(remixable_model.to_s).snake_case.to_sym
279
+ @remixables[key][model_key] ||= {}
280
+ @remixables[key][model_key][:reader] ||= accessor_name.to_sym
281
+ @remixables[key][model_key][:writer] ||= "#{accessor_name}=".to_sym
282
+ @remixables[key][model_key][:model] ||= remixable_model
283
+ end
284
+
285
+ # - remix_one_to_many
286
+ # ==== Description
287
+ # creates a one to many relationship Class has many of remixed model
288
+ # ==== Parameters
289
+ # cardinality <Fixnum> cardinality of relationship
290
+ # model <Class> remixed model that 'self' is relating to
291
+ # options <Hash> options hash
292
+ def remix_one_to_many(cardinality, model, options)
293
+ self.has cardinality, options[:table_name].intern
294
+ model.property Extlib::Inflection.foreign_key(self.name).intern, Integer, :nullable => false
295
+ model.belongs_to Extlib::Inflection.tableize(self.name).intern
296
+ end
297
+
298
+ # - remix_many_to_many
299
+ # ==== Description
300
+ # creates a many to many relationship between two DataMapper models THROUGH a Remixable module
301
+ # ==== Parameters
302
+ # cardinality <Fixnum> cardinality of relationship
303
+ # model <Class> remixed model that 'self' is relating through
304
+ # options <Hash> options hash
305
+ def remix_many_to_many(cardinality, model, options)
306
+ options[:other_model] = Object.full_const_get(Extlib::Inflection.classify(options[:other_model]))
307
+
308
+ #TODO if options[:unique] the two *_id's need to be a unique composite key, maybe even
309
+ # attach a validates_is_unique if the validator is included.
310
+ puts " ~ options[:unique] is not yet supported" if options[:unique]
311
+
312
+ # Is M:M between two different classes or the same class
313
+ unless self.name == options[:other_model].name
314
+ self.has cardinality, options[:table_name].intern
315
+ options[:other_model].has cardinality, options[:table_name].intern
316
+
317
+ model.belongs_to Extlib::Inflection.tableize(self.name).intern
318
+ model.belongs_to Extlib::Inflection.tableize(options[:other_model].name).intern
319
+ else
320
+ raise Exception, "options[:via] must be specified when Remixing a module between two of the same class" unless options[:via]
321
+
322
+ self.has cardinality, options[:table_name].intern
323
+ model.belongs_to Extlib::Inflection.tableize(self.name).intern
324
+ model.belongs_to options[:via].intern, :class_name => options[:other_model].name, :child_key => ["#{options[:via]}_id".intern]
325
+ end
326
+ end
327
+
328
+ # - generate_remixed_model
329
+ # ==== Description
330
+ # Generates a Remixed Model Class from a Remixable Module and options
331
+ # ==== Parameters
332
+ # remixable <Module> module that is being remixed
333
+ # options <Hash> options hash
334
+ # ==== Returns
335
+ # <Class> remixed model
336
+ def generate_remixed_model(remixable,options)
337
+ #Create Remixed Model
338
+ klass = Class.new Object do
339
+ include DataMapper::Resource
340
+ end
341
+
342
+ #Give remixed model a name and create its constant
343
+ model = Object.full_const_set(options[:class_name], klass)
344
+
345
+ #Get instance methods & validators
346
+ model.send(:include,remixable)
347
+
348
+ #port the properties over...
349
+ remixable.properties.each do |prop|
350
+ model.property(prop.name, prop.type, prop.options)
351
+ end
352
+
353
+ model
354
+ end
355
+
356
+ end # RemixerClassMethods
357
+
358
+ # - RemixeeClassMethods
359
+ # ==== Description
360
+ # Methods available to any model that is :remixable
361
+ module RemixeeClassMethods
362
+ # - suffix
363
+ # ==== Description
364
+ # modifies the storage name suffix, which is by default based on the Remixable Module name
365
+ # ==== Parameters
366
+ # suffix <String> storage name suffix to use (singular)
367
+ def suffix(sfx=nil)
368
+ @suffix = sfx unless sfx.nil?
369
+ @suffix
370
+ end
371
+
372
+ # Squash auto_migrate!
373
+ # model.auto_migrate! never gets called directly from dm-core/auto_migrations.rb
374
+ # The models are explicitly migrated down and up again.
375
+ def auto_migrate_up!(args=nil)
376
+ DataMapper.logger.warn("Skipping auto_migrate_up! for remixable module (#{self.name})")
377
+ end
378
+
379
+ def auto_migrate_down!(args=nil)
380
+ DataMapper.logger.warn("Skipping auto_migrate_down! for remixable module (#{self.name})")
381
+ end
382
+
383
+ #Squash auto_upgrade!
384
+ def auto_upgrade!(args=nil)
385
+ DataMapper.logger.warn("Skipping auto_upgrade! for remixable module (#{self.name})")
386
+ end
387
+ end # RemixeeClassMethods
388
+
389
+
390
+ #==============================INSTANCE METHODS==============================#
391
+
392
+ module RemixeeInstanceMethods
393
+ def self.included(base);end;
394
+ end # RemixeeInstanceMethods
395
+
396
+ module RemixerInstanceMethods
397
+ def self.included(base);end;
398
+ end # RemixerInstanceMethods
399
+
400
+ end # Example
401
+ end # Is
402
+ end # DataMapper
@@ -0,0 +1,7 @@
1
+ module DataMapper
2
+ module Is
3
+ module Remixable
4
+ VERSION = "0.9.7"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ # Needed to import datamapper and other gems
2
+ require 'rubygems'
3
+ require 'pathname'
4
+
5
+ # Add all external dependencies for the plugin here
6
+ gem 'dm-core', '~>0.9.7'
7
+ require 'dm-core'
8
+
9
+ # Require plugin-files
10
+ require Pathname(__FILE__).dirname.expand_path / 'dm-is-remixable' / 'is' / 'remixable.rb'
11
+
12
+ # Include the plugin in Resource
13
+ module DataMapper
14
+ module Resource
15
+ module ClassMethods
16
+ include DataMapper::Is::Remixable
17
+ end # module ClassMethods
18
+ end # module Resource
19
+ end # module DataMapper
@@ -0,0 +1,15 @@
1
+ module Addressable
2
+ include DataMapper::Resource
3
+
4
+ is :remixable,
5
+ :suffix => "address"
6
+
7
+ property :id, Integer, :key => true, :serial => true
8
+
9
+ property :address1, String, :length => 255
10
+ property :address2, String, :length => 255
11
+
12
+ property :city, String, :length => 128
13
+ property :state, String, :length => 2
14
+ property :zip, String, :length => 5..10
15
+ end
@@ -0,0 +1,51 @@
1
+ require Pathname(__FILE__).dirname / "image"
2
+ require Pathname(__FILE__).dirname / "commentable"
3
+ require Pathname(__FILE__).dirname / "viewable"
4
+ require Pathname(__FILE__).dirname / "taggable"
5
+ require Pathname(__FILE__).dirname / "user"
6
+ require Pathname(__FILE__).dirname / "bot"
7
+ require Pathname(__FILE__).dirname / "tag"
8
+
9
+ class Article
10
+ include DataMapper::Resource
11
+
12
+ property :id, Integer, :key => true, :serial => true
13
+ property :title, String
14
+ property :url, String
15
+
16
+
17
+ remix 1, :images, :as => "pics"
18
+
19
+ remix n, :viewables, :as => "views"
20
+
21
+ remix n, :commentables, :as => "comments", :for => "User"
22
+
23
+ remix n, "My::Nested::Remixable::Rating", :as => :ratings
24
+
25
+ remix n, :taggable, :as => "user_taggings", :for => "User", :class_name => "UserTagging"
26
+
27
+ remix n, :taggable, :as => "bot_taggings", :for => "Bot", :class_name => "BotTagging"
28
+
29
+ enhance :viewables do
30
+ belongs_to :user
31
+ end
32
+
33
+ enhance :taggable, "UserTagging" do
34
+ belongs_to :user
35
+ belongs_to :tag
36
+ end
37
+
38
+ enhance :taggable, "BotTagging" do
39
+ belongs_to :bot
40
+ belongs_to :tag
41
+ end
42
+
43
+ def viewed_by(usr)
44
+ art_view = ArticleView.new
45
+ art_view.ip = "127.0.0.1"
46
+ art_view.user_id = usr.id
47
+
48
+ self.views << art_view
49
+ end
50
+
51
+ end
@@ -0,0 +1,12 @@
1
+ module Billable
2
+ include DataMapper::Resource
3
+
4
+ is :remixable,
5
+ :suffix => "billing_account"
6
+
7
+ property :id, Integer, :key => true, :serial => true
8
+
9
+ property :cc_type, Enum.new("mastercard","amex","visa")
10
+ property :cc_num, String, :length => 12..20
11
+ property :expiration, Date
12
+ end
data/spec/data/bot.rb ADDED
@@ -0,0 +1,21 @@
1
+ require Pathname(__FILE__).dirname / "viewable"
2
+ require Pathname(__FILE__).dirname / "billable"
3
+ require Pathname(__FILE__).dirname / "addressable"
4
+ require Pathname(__FILE__).dirname / "rating"
5
+
6
+ class Bot
7
+ include DataMapper::Resource
8
+
9
+ property :id, Integer,
10
+ :key => true,
11
+ :serial => true
12
+
13
+ property :bot_name, String,
14
+ :nullable => false,
15
+ :length => 2..50
16
+
17
+ property :bot_version, String,
18
+ :nullable => false,
19
+ :length => 2..50
20
+
21
+ end
@@ -0,0 +1,10 @@
1
+ module Commentable
2
+ include DataMapper::Resource
3
+
4
+ is :remixable,
5
+ :suffix => "comment"
6
+
7
+ property :id, Integer, :key => true, :serial => true
8
+ property :comment, String
9
+ property :created_at, DateTime
10
+ end
@@ -0,0 +1,9 @@
1
+ module Image
2
+ include DataMapper::Resource
3
+
4
+ is :remixable
5
+
6
+ property :id, Integer, :key => true, :serial => true
7
+ property :description, String
8
+ property :path, String
9
+ end
@@ -0,0 +1,37 @@
1
+ module My
2
+ module Nested
3
+ module Remixable
4
+
5
+ module Rating
6
+
7
+ def self.included(base)
8
+ base.extend ClassMethods
9
+ end
10
+
11
+ include DataMapper::Resource
12
+
13
+ is :remixable
14
+
15
+ # properties
16
+
17
+ property :id, Integer, :serial => true
18
+
19
+ property :user_id, Integer, :nullable => false
20
+ property :rating, Integer, :nullable => false, :default => 0
21
+
22
+ module ClassMethods
23
+
24
+ # total rating for all rateable instances of this type
25
+ def total_rating
26
+ rating_sum = self.sum(:rating).to_f
27
+ rating_count = self.count.to_f
28
+ rating_count > 0 ? rating_sum / rating_count : 0
29
+ end
30
+
31
+ end
32
+
33
+ end
34
+
35
+ end
36
+ end
37
+ end
data/spec/data/tag.rb ADDED
@@ -0,0 +1,6 @@
1
+ class Tag
2
+ include DataMapper::Resource
3
+
4
+ property :id, Integer, :key => true, :serial => true
5
+ property :name, String, :unique => true, :nullable => false
6
+ end
@@ -0,0 +1,24 @@
1
+ module Taggable
2
+ def self.included(base)
3
+ base.extend Taggable::ClassMethods
4
+ end
5
+
6
+ include DataMapper::Resource
7
+
8
+ is :remixable
9
+
10
+ property :id, Integer, :key => true, :serial => true
11
+ property :tag_id, Integer
12
+
13
+
14
+
15
+
16
+ module ClassMethods
17
+
18
+ def related_tags
19
+ puts "should work"
20
+ end
21
+
22
+ end
23
+
24
+ end
@@ -0,0 +1,15 @@
1
+ require Pathname(__FILE__).dirname / "rating"
2
+
3
+ class Topic
4
+ include DataMapper::Resource
5
+
6
+ property :id, Integer, :key => true, :serial => true
7
+
8
+ property :name, String
9
+ property :description, String
10
+
11
+ remix n, My::Nested::Remixable::Rating,
12
+ :as => :ratings_for_topic,
13
+ :class_name => "Rating"
14
+
15
+ end
data/spec/data/user.rb ADDED
@@ -0,0 +1,34 @@
1
+ require Pathname(__FILE__).dirname / "viewable"
2
+ require Pathname(__FILE__).dirname / "billable"
3
+ require Pathname(__FILE__).dirname / "addressable"
4
+ require Pathname(__FILE__).dirname / "rating"
5
+
6
+ class User
7
+ include DataMapper::Resource
8
+
9
+ property :id, Integer,
10
+ :key => true,
11
+ :serial => true
12
+
13
+ property :first_name, String,
14
+ :nullable => false,
15
+ :length => 2..50
16
+
17
+ property :last_name, String,
18
+ :nullable => false,
19
+ :length => 2..50
20
+
21
+ remix n, :viewables
22
+
23
+ remix n, :billables, :class_name => "Account"
24
+
25
+ remix n, :addressables
26
+
27
+ remix n, :commentables, :as => "comments", :for => "User", :via => "commentor"
28
+
29
+ remix n, "My::Nested::Remixable::Rating"
30
+
31
+ enhance :addressables do
32
+ property :label, Enum.new('home','work')
33
+ end
34
+ end
@@ -0,0 +1,11 @@
1
+ module Viewable
2
+ include DataMapper::Resource
3
+
4
+ is :remixable,
5
+ :suffix => "view"
6
+
7
+ property :id, Integer, :key => true, :serial => true
8
+
9
+ property :created_at, DateTime
10
+ property :ip, String
11
+ end
@@ -0,0 +1,239 @@
1
+ require 'pathname'
2
+ require Pathname(__FILE__).dirname.expand_path.parent + 'spec_helper'
3
+
4
+ require "dm-types"
5
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'addressable'
6
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'billable'
7
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'commentable'
8
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'article'
9
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'image'
10
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'user'
11
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'viewable'
12
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'topic'
13
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'rating'
14
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'taggable'
15
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'bot'
16
+ require Pathname(__FILE__).dirname.expand_path.parent / 'data' / 'tag'
17
+ DataMapper.auto_migrate!
18
+
19
+ if HAS_SQLITE3 || HAS_MYSQL || HAS_POSTGRES
20
+ describe 'DataMapper::Is::Remixable' do
21
+ describe 'DataMapper::Resource' do
22
+ it "should know if it is remixable" do
23
+ User.is_remixable?.should be(false)
24
+ Image.is_remixable?.should be(true)
25
+ Article.is_remixable?.should be(false)
26
+ Commentable.is_remixable?.should be(true)
27
+ end
28
+ end
29
+
30
+ it "should only allow remixables to be remixed" do
31
+ lambda { User.remix 1, :articles }.should raise_error(Exception)
32
+ end
33
+
34
+ it "should not allow enhancements of modules that aren't remixed" do
35
+ lambda {
36
+ User.enhance Image
37
+ }.should raise_error
38
+ end
39
+
40
+ it "should provide a default suffix values for models that do 'is :remixable'" do
41
+ Image.suffix.should == "image"
42
+ end
43
+
44
+ it "should allow enhancing a model that is remixed" do
45
+ Article.enhance :images do
46
+ def self.test_enhance
47
+ true
48
+ end
49
+ end
50
+
51
+ ArticleImage.should respond_to("test_enhance")
52
+ end
53
+
54
+ it "should allow enhancing a model that was remixed from a nested module" do
55
+ Article.enhance :ratings do
56
+ def self.test_enhance
57
+ true
58
+ end
59
+ end
60
+
61
+ ArticleRating.should respond_to("test_enhance")
62
+ ArticleRating.should respond_to("total_rating")
63
+ ArticleRating.new.should respond_to("user_id")
64
+ ArticleRating.new.should respond_to("rating")
65
+ end
66
+
67
+ it "should allow enhancing the same remixable twice with different class_name attributes" do
68
+ Article.enhance :taggable, "UserTagging" do
69
+ def self.test_enhance
70
+ true
71
+ end
72
+ end
73
+
74
+ UserTagging.should respond_to("test_enhance")
75
+ UserTagging.should respond_to("related_tags")
76
+ UserTagging.new.should respond_to("user_id")
77
+ UserTagging.new.should respond_to("tag")
78
+
79
+ Article.enhance :taggable, "BotTagging" do
80
+ def self.test_enhance_2
81
+ true
82
+ end
83
+ end
84
+ BotTagging.should respond_to("test_enhance_2")
85
+ BotTagging.should respond_to("related_tags")
86
+ BotTagging.new.should respond_to("bot_id")
87
+ BotTagging.new.should respond_to("tag")
88
+ end
89
+
90
+ it "should through exception when enhancing an unknown class" do
91
+ lambda {
92
+ Article.enhance :taggable, "NonExistentClass"
93
+ }.should raise_error
94
+ end
95
+
96
+ it "should provided a map of Remixable Modules to Remixed Models names" do
97
+ User.remixables.should_not be(nil)
98
+ end
99
+
100
+ it "should store the remixed model in the map of Remixable Modules to Remixed Models" do
101
+ User.remixables[:billable][:account][:model].should == Account
102
+ # nested remixables
103
+ User.remixables[:rating][:user_rating][:model].should == UserRating
104
+ Article.remixables[:rating][:article_rating][:model].should == ArticleRating
105
+ Topic.remixables[:rating][:rating][:model].should == Rating
106
+ end
107
+
108
+ it "should store the remixee reader name in the map of Remixable Modules to Remixed Models" do
109
+ User.remixables[:billable][:account][:reader].should == :accounts
110
+ # nested remixables
111
+ User.remixables[:rating][:user_rating][:reader].should == :user_ratings
112
+ Article.remixables[:rating][:article_rating][:reader].should == :ratings
113
+ Topic.remixables[:rating][:rating][:reader].should == :ratings_for_topic
114
+ end
115
+
116
+ it "should store the remixee writer name in the map of Remixable Modules to Remixed Models" do
117
+ User.remixables[:billable][:account][:writer].should == :accounts=
118
+ # nested remixables
119
+ User.remixables[:rating][:user_rating][:writer].should == :user_ratings=
120
+ Article.remixables[:rating][:article_rating][:writer].should == :ratings=
121
+ Topic.remixables[:rating][:rating][:writer].should == :ratings_for_topic=
122
+ end
123
+
124
+ it "should allow specifying an alternate class name" do
125
+ User.remixables[:billable][:account][:model].name.should_not == "UserBillable"
126
+ User.remixables[:billable][:account][:model].name.should == "Account"
127
+ end
128
+
129
+ it "should create a storage name based on the class name" do
130
+
131
+ Article.remixables[:image][:article_image][:model].storage_names[:default].should == "article_images"
132
+ User.remixables[:billable][:account][:model].storage_names[:default].should == "accounts"
133
+ end
134
+
135
+ it "should allow creating an accessor alias" do
136
+ article = Article.new
137
+ article.should respond_to("pics")
138
+ article.should respond_to("article_images")
139
+ end
140
+
141
+ it "should copy properties from the Remixable Module to the Remixed Model" do
142
+ #Billabe => Account
143
+ account = Account.new
144
+
145
+ account.should respond_to("cc_num")
146
+ account.should respond_to("cc_type")
147
+ account.should respond_to("expiration")
148
+ end
149
+
150
+ it "should allow 1:M relationships with the Remixable Module" do
151
+ user = User.new
152
+ addy = UserAddress.new
153
+ addy2 = UserAddress.new
154
+
155
+ user.first_name = "Jack"
156
+ user.last_name = "Tester"
157
+
158
+ addy.address1 = "888 West Whatnot Ave."
159
+ addy.city = "Los Angeles"
160
+ addy.state = "CA"
161
+ addy.zip = "90230"
162
+
163
+ addy2.address1 = "325 East NoWhere Lane"
164
+ addy2.city = "Fort Myers"
165
+ addy2.state = "FL"
166
+ addy2.zip = "33971"
167
+
168
+ user.user_addresses << addy
169
+ user.user_addresses << addy2
170
+
171
+ user.user_addresses.length.should be(2)
172
+ end
173
+
174
+ it "should allow 1:1 relationships with the Remixable Module" do
175
+ article = Article.new
176
+ image1 = ArticleImage.new
177
+ image2 = ArticleImage.new
178
+
179
+ article.title = "Really important news!"
180
+ article.url = "http://example.com/index.html"
181
+
182
+ image1.description = "Shocking and horrific photo!"
183
+ image1.path = "~/pictures/shocking.jpg"
184
+
185
+ image2.description = "Other photo"
186
+ image2.path = "~/pictures/mom_naked.yipes"
187
+
188
+ begin
189
+ article.pics << image1
190
+ false
191
+ rescue Exception => e
192
+ e.class.should be(NoMethodError)
193
+ end
194
+
195
+ article.pics = image2
196
+ article.pics.path.should == image2.path
197
+ end
198
+
199
+ it "should allow M:M unary relationships through the Remixable Module" do
200
+ #User => Commentable => User
201
+ user = User.new
202
+ user.first_name = "Tester"
203
+ user2 = User.new
204
+ user2.first_name = "Testy"
205
+
206
+ comment = UserComment.new
207
+ comment.comment = "YOU SUCK!"
208
+ comment.commentor = user2
209
+ user.user_comments << comment
210
+
211
+ user2.user_comments.length.should be(0)
212
+
213
+ comment.commentor.first_name.should == "Testy"
214
+ user.user_comments.length.should be(1)
215
+ end
216
+
217
+ it "should allow M:M relationships through the Remixable Module" do
218
+ #Article => Commentable => User
219
+ user = User.new
220
+ article = Article.new
221
+
222
+ ac = ArticleComment.new
223
+
224
+ user.first_name = "Talker"
225
+ user.last_name = "OnTheInternetz"
226
+
227
+ article.url = "Http://example.com/"
228
+ article.title = "Important internet thingz, lol"
229
+
230
+ ac.comment = "This article sux!"
231
+
232
+ article.comments << ac
233
+ user.article_comments << ac
234
+
235
+ article.comments.first.should be(ac)
236
+ user.article_comments.first.should be(ac)
237
+ end
238
+ end
239
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --format specdoc
2
+ --colour
@@ -0,0 +1,28 @@
1
+ require 'rubygems'
2
+ gem 'rspec', '>=1.1.3'
3
+ require 'spec'
4
+ require 'pathname'
5
+ require Pathname(__FILE__).dirname.expand_path.parent + 'lib/dm-is-remixable'
6
+ require "ruby-debug"
7
+ def load_driver(name, default_uri)
8
+ return false if ENV['ADAPTER'] != name.to_s
9
+
10
+ lib = "do_#{name}"
11
+
12
+ begin
13
+ gem lib, '~>0.9.7'
14
+ require lib
15
+ DataMapper.setup(name, ENV["#{name.to_s.upcase}_SPEC_URI"] || default_uri)
16
+ DataMapper::Repository.adapters[:default] = DataMapper::Repository.adapters[name]
17
+ true
18
+ rescue Gem::LoadError => e
19
+ warn "Could not load #{lib}: #{e}"
20
+ false
21
+ end
22
+ end
23
+
24
+ ENV['ADAPTER'] ||= 'sqlite3'
25
+
26
+ HAS_SQLITE3 = load_driver(:sqlite3, 'sqlite3::memory:')
27
+ HAS_MYSQL = load_driver(:mysql, 'mysql://localhost/dm_core_test')
28
+ HAS_POSTGRES = load_driver(:postgres, 'postgres://postgres@localhost/dm_core_test')
metadata ADDED
@@ -0,0 +1,99 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dm-is-remixable
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.7
5
+ platform: ruby
6
+ authors:
7
+ - Cory O'Daniel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-11-18 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: dm-core
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - "="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.9.7
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: hoe
27
+ type: :development
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 1.8.2
34
+ version:
35
+ description: dm-is-remixable allow you to create reusable data functionality
36
+ email:
37
+ - dm-is-remixable [a] coryodaniel [d] com
38
+ executables: []
39
+
40
+ extensions: []
41
+
42
+ extra_rdoc_files:
43
+ - README.txt
44
+ - LICENSE
45
+ - TODO
46
+ files:
47
+ - History.txt
48
+ - LICENSE
49
+ - Manifest.txt
50
+ - README.txt
51
+ - Rakefile
52
+ - TODO
53
+ - lib/dm-is-remixable.rb
54
+ - lib/dm-is-remixable/is/remixable.rb
55
+ - lib/dm-is-remixable/is/version.rb
56
+ - spec/data/addressable.rb
57
+ - spec/data/article.rb
58
+ - spec/data/billable.rb
59
+ - spec/data/bot.rb
60
+ - spec/data/commentable.rb
61
+ - spec/data/image.rb
62
+ - spec/data/rating.rb
63
+ - spec/data/tag.rb
64
+ - spec/data/taggable.rb
65
+ - spec/data/topic.rb
66
+ - spec/data/user.rb
67
+ - spec/data/viewable.rb
68
+ - spec/integration/remixable_spec.rb
69
+ - spec/spec.opts
70
+ - spec/spec_helper.rb
71
+ has_rdoc: true
72
+ homepage: http://github.com/sam/dm-more/tree/master/dm-remixes
73
+ post_install_message:
74
+ rdoc_options:
75
+ - --main
76
+ - README.txt
77
+ require_paths:
78
+ - lib
79
+ required_ruby_version: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: "0"
84
+ version:
85
+ required_rubygems_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: "0"
90
+ version:
91
+ requirements: []
92
+
93
+ rubyforge_project: datamapper
94
+ rubygems_version: 1.3.1
95
+ signing_key:
96
+ specification_version: 2
97
+ summary: dm-is-remixable allow you to create reusable data functionality
98
+ test_files: []
99
+