dm-is-remixable 0.9.7

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/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
+