congo 0.1.1

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