congo 0.1.1

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.
@@ -0,0 +1,23 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
22
+
23
+ tmp/
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Rodrigo Alvarez
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.
@@ -0,0 +1,17 @@
1
+ = congo
2
+
3
+ Description goes here.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2009 Rodrigo Alvarez. See LICENSE for details.
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "congo"
8
+ gem.summary = %Q{Library to define flexible schemas for mongodb documents. }
9
+ gem.email = ["papipo@gmail.com", "didier@nocoffee.fr"]
10
+ gem.homepage = "http://github.com/Papipo/congo"
11
+ gem.authors = ["Rodrigo Alvarez", "Didier Lafforgue"]
12
+ gem.add_development_dependency "rspec", ">= 1.2.9"
13
+ gem.add_development_dependency "mongo_mapper", ">= 0.7.2"
14
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
15
+ end
16
+ Jeweler::GemcutterTasks.new
17
+ rescue LoadError
18
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
19
+ end
20
+
21
+ require 'spec/rake/spectask'
22
+
23
+ Spec::Rake::SpecTask.new(:rcov) do |spec|
24
+ spec.libs << 'lib' << 'spec'
25
+ spec.pattern = 'spec/**/*_spec.rb'
26
+ spec.rcov = true
27
+ end
28
+
29
+ Spec::Rake::SpecTask.new('spec:unit') do |spec|
30
+ spec.libs << 'lib' << 'spec'
31
+ spec.spec_files = FileList['spec/unit/**/*_spec.rb']
32
+ end
33
+
34
+ Spec::Rake::SpecTask.new('spec:functionals') do |spec|
35
+ spec.libs << 'lib' << 'spec'
36
+ spec.spec_files = FileList['spec/functional/**/*_spec.rb']
37
+ end
38
+
39
+ task :spec => [:check_dependencies, 'spec:unit', 'spec:functionals']
40
+
41
+ task :default => :spec
42
+
43
+ require 'rake/rdoctask'
44
+ Rake::RDocTask.new do |rdoc|
45
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
46
+
47
+ rdoc.rdoc_dir = 'rdoc'
48
+ rdoc.title = "congo #{version}"
49
+ rdoc.rdoc_files.include('README*')
50
+ rdoc.rdoc_files.include('lib/**/*.rb')
51
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1,18 @@
1
+ en:
2
+ congo:
3
+ date:
4
+ formats:
5
+ default: "%m/%d/%Y"
6
+
7
+ errors:
8
+ messages:
9
+ invalid_keys: "are not valid"
10
+ invalid: "is invalid"
11
+ empty: "can't be empty"
12
+ taken: "has already been taken"
13
+ exclusion: "is reserved"
14
+ inclusion: "is not included in the list"
15
+ confirmation: "doesn't match confirmation"
16
+ accepted: "must be accepted"
17
+ wrong_length: "is the wrong length"
18
+ not_a_number: "is not a number"
@@ -0,0 +1,18 @@
1
+ fr:
2
+ congo:
3
+ date:
4
+ formats:
5
+ default: "%d/%m/%Y"
6
+
7
+ errors:
8
+ messages:
9
+ invalid_keys: "ne sont pas valides"
10
+ invalid: "n'est pas valide"
11
+ empty: "doit être rempli(e)"
12
+ taken: "n'est pas disponible"
13
+ exclusion: "n'est pas disponible"
14
+ inclusion: "n'est pas inclus(e) dans la liste"
15
+ confirmation: "ne concorde pas avec la confirmation"
16
+ accepted: "doit être accepté(e)"
17
+ wrong_length: "ne fait pas la bonne longueur"
18
+ not_a_number: "n'est pas un nombre"
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ $:.unshift "#{File.dirname(__FILE__)}/lib"
2
+ require 'congo'
@@ -0,0 +1,16 @@
1
+ $:.unshift File.expand_path(File.dirname(__FILE__))
2
+
3
+ require 'congo/support'
4
+ require 'congo/migration'
5
+ require 'congo/validation'
6
+ require 'congo/list'
7
+ require 'congo/grip/attachment'
8
+ require 'congo/grip/has_attachment'
9
+ require 'congo/metadata/key'
10
+ require 'congo/metadata/association'
11
+ require 'congo/metadata/validation'
12
+ require 'congo/content_type'
13
+ require 'congo/types'
14
+ require 'congo/scoper'
15
+ require 'congo/proxy_scoper'
16
+
@@ -0,0 +1,107 @@
1
+ module Congo
2
+ class ContentType
3
+ include MongoMapper::Document
4
+ include Validation
5
+ include Migration
6
+
7
+ ## keys
8
+ key :embedded, Boolean, :default => false
9
+ key :name, String
10
+ key :collection_name, String
11
+ key :slug, String
12
+ key :description, String
13
+ key :scope_type, String
14
+ key :scope_id, ObjectId
15
+
16
+ ## associations
17
+ belongs_to :scope, :polymorphic => true
18
+ many :metadata_keys, :class_name => 'Congo::Metadata::Key', :dependent => :destroy
19
+ many :metadata_validations, :class_name => 'Congo::Metadata::Validation', :dependent => :destroy
20
+ many :metadata_associations, :class_name => 'Congo::Metadata::Association', :dependent => :destroy
21
+
22
+ ## callbacks
23
+ before_validation :make_names_clean
24
+ before_destroy :destroy_contents
25
+
26
+ # TODO: add callback to check if validations are still valid (for instance, a dropped key should not still have its validations)
27
+
28
+ ## methods
29
+
30
+ def to_const
31
+ klass = Class.new
32
+ if self.embedded?
33
+ klass.send(:include, MongoMapper::EmbeddedDocument)
34
+ else
35
+ klass.send(:include, MongoMapper::Document)
36
+ klass.timestamps!
37
+ set_collection_name(klass)
38
+ apply_scope(klass)
39
+ end
40
+
41
+ klass.class_eval <<-EOV
42
+ def content_type
43
+ @content_type ||= self.class.content_type
44
+ end
45
+
46
+ def self.content_type
47
+ Congo::ContentType.find('#{self._id}')
48
+ end
49
+ EOV
50
+
51
+ apply_metadata(klass)
52
+ apply_migration(klass)
53
+ klass
54
+ end
55
+
56
+ private
57
+
58
+ def set_collection_name(klass)
59
+ klass.set_collection_name self.send(:mongodb_collection_name)
60
+ end
61
+
62
+ def mongodb_collection_name
63
+ "#{scope_type}_#{scope_id}_#{name.tableize}" # maybe just name.tableize is enough
64
+ end
65
+
66
+ def apply_scope(klass)
67
+ foreign_key = scope_type.underscore.gsub('/', '_').foreign_key
68
+ association_name = scope_type.demodulize.underscore
69
+
70
+ klass.key foreign_key, ObjectId
71
+ klass.belongs_to association_name, :class_name => scope_type, :foreign_key => foreign_key
72
+ klass.validates_presence_of association_name
73
+ end
74
+
75
+ def apply_metadata(klass)
76
+ %w[metadata_keys metadata_associations metadata_validations].each do |association|
77
+ self.send(association).each { |meta| meta.apply(klass, scope) }
78
+ end
79
+ end
80
+
81
+ def destroy_contents
82
+ MongoMapper.database.collection(self.send(:mongodb_collection_name)).drop
83
+ end
84
+
85
+ def make_names_clean
86
+ if self.collection_name
87
+ self.collection_name = self.collection_name.gsub(/[\s\W]+/, ' ').strip
88
+ self.name = self.collection_name if self.name.blank?
89
+ end
90
+
91
+ self.name = self.name.strip.classify.gsub(/\s+/, '_').camelize if self.name
92
+ self.slug = slugify_name(self.collection_name) if self.collection_name
93
+ end
94
+
95
+ def slugify_name(name)
96
+ # replace accented chars with ther ascii equivalents
97
+ s = ActiveSupport::Inflector.transliterate(name).to_s
98
+ # Remove leading or trailing space
99
+ s.strip!
100
+ # Remove leading or trailing slash
101
+ s.gsub! /(^[\/]+)|([\/]+$)/, ''
102
+ # Turn unwanted chars into the seperator
103
+ s.gsub!(/[^a-zA-Z0-9\-_\+\/]+/i, '_')
104
+ s.downcase
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,22 @@
1
+ module Congo
2
+ module Grip
3
+ class Attachment
4
+
5
+ attr_reader :name, :path, :content_type, :size, :body
6
+
7
+ def initialize(attrs = {})
8
+ @name = attrs[:name]
9
+ @path = attrs[:path]
10
+ @content_type = attrs[:content_type]
11
+ @size = attrs[:size]
12
+ @body = attrs[:body]
13
+ end
14
+
15
+ def to_s
16
+ self.body
17
+ end
18
+
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,99 @@
1
+ # Got from http://github.com/jnunemaker/grip/blob/master/lib/grip.rb
2
+ # Wait for jnunemaker to include it in the next release of Mongomapper
3
+ # Note:
4
+ # we patched it to reflect the collection name within the path of the file.
5
+
6
+ require 'mongo/gridfs'
7
+ require 'mime/types'
8
+ require 'tempfile'
9
+
10
+ # if thumbnailable?
11
+ # tmp = Tempfile.new("thumb_#{filename}")
12
+ # MojoMagick::resize(uploaded_file.path, tmp.path, {:width => 50, :height => 40, :scale => '>'})
13
+ # self.thumbnail = tmp.read
14
+ # end
15
+
16
+ # open : db, name, mode, options (:root, :metadata, :content_type)
17
+ # read : db, name, length, offset
18
+ # unlink : db, names
19
+ # list : db, root collection
20
+ #
21
+ # GridStore.open(database, 'filename', 'w') { |f|
22
+ # f.puts "Hello, world!"
23
+ # }
24
+
25
+ module Congo
26
+ module Grip
27
+ module HasAttachment
28
+ def self.included(base)
29
+ base.extend Congo::Grip::HasAttachment::ClassMethods
30
+ end
31
+
32
+ module ClassMethods
33
+ def has_grid_attachment(name, options = {})
34
+ configuration = { :path => ":class/:name/:id" }
35
+ configuration.update(options) if options.is_a?(Hash)
36
+
37
+ write_inheritable_attribute(:attachment_definitions, {}) if attachment_definitions.nil?
38
+ attachment_definitions[name] = {}
39
+
40
+ after_save :save_attachments
41
+ before_destroy :destroy_attached_files
42
+
43
+ key "#{name}_size".to_sym, Integer
44
+ key "#{name}_path".to_sym, String
45
+ key "#{name}_name".to_sym, String
46
+ key "#{name}_content_type".to_sym, String
47
+
48
+ define_method(name) do
49
+ return nil unless self.send("#{name}?".to_sym)
50
+
51
+ Congo::Grip::Attachment.new({
52
+ :name => self["#{name}_name"],
53
+ :path => self["#{name}_path"],
54
+ :size => self["#{name}_size"],
55
+ :content_type => self["#{name}_content_type"],
56
+ :body => GridFS::GridStore.read(self.class.database, self["#{name}_path"])
57
+ })
58
+ end
59
+
60
+ define_method("#{name}=") do |file|
61
+ return if file.nil?
62
+ self['_id'] = Mongo::ObjectID.new if _id.blank?
63
+ self["#{name}_size"] = file.size rescue File.size(file)
64
+ self["#{name}_name"] = file.original_filename rescue File.basename(file.path)
65
+ self["#{name}_path"] = configuration[:path].gsub(':class', self.class.to_s.underscore).gsub(':name', self.content_type.name.underscore).gsub(':id', _id.to_s)
66
+ self["#{name}_content_type"] = file.content_type rescue MIME::Types.type_for(self["#{name}_name"]).to_s
67
+ self.class.attachment_definitions[name] = file
68
+ end
69
+
70
+ define_method("#{name}?") do
71
+ !self["#{name}_name"].nil?
72
+ end
73
+ end
74
+
75
+ def attachment_definitions
76
+ read_inheritable_attribute(:attachment_definitions)
77
+ end
78
+ end
79
+
80
+ def save_attachments
81
+ self.class.attachment_definitions.each do |attachment|
82
+ name, file = attachment
83
+
84
+ if (file.is_a?(File) || file.is_a?(Tempfile))
85
+ GridFS::GridStore.open(self.class.database, self["#{name}_path"], 'w', :content_type => self["#{name}_content_type"]) do |f|
86
+ f.write(file.read)
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ def destroy_attached_files
93
+ self.class.attachment_definitions.each do |name, attachment|
94
+ GridFS::GridStore.unlink(self.class.database, self["#{name}_path"])
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,64 @@
1
+ module Congo
2
+ module List
3
+
4
+ def self.included(model)
5
+ model.class_eval do
6
+ include InstanceMethods
7
+
8
+ def to_const_with_list
9
+ klass = to_const_without_list
10
+ apply_list(klass)
11
+ klass
12
+ end
13
+
14
+ alias_method_chain :to_const, :list
15
+ end
16
+ end
17
+
18
+ module InstanceMethods
19
+
20
+ private
21
+
22
+ def apply_list(klass)
23
+ klass.key :_position, Integer, :default => 0
24
+
25
+ klass.class_eval do
26
+
27
+ include Congo::List::ContentInstanceMethods
28
+
29
+ before_destroy :remove_from_list
30
+ before_create :add_to_list_bottom
31
+
32
+ def self.reorder_all(ids)
33
+ return false if ids.nil? or ids.empty?
34
+ updates = {}
35
+ ids.each_with_index do |id, index|
36
+ updates[id] = { :_position => index + 1 }
37
+ end
38
+ self.update(updates)
39
+ end
40
+ end
41
+ end
42
+
43
+ end
44
+
45
+ module ContentInstanceMethods
46
+
47
+ private
48
+
49
+ def add_to_list_bottom
50
+ self._position = self.class.count + 1
51
+ end
52
+
53
+ def remove_from_list
54
+ updates = {}
55
+ self.class.all(:conditions => { :_position.gt => self._position.to_i }).each do |item|
56
+ updates[item._id] = { :_position => item._position - 1 }
57
+ end
58
+ self.class.update(updates) unless updates.empty?
59
+ end
60
+
61
+ end
62
+
63
+ end
64
+ end