Permalink
Browse files

First commit

  • Loading branch information...
0 parents commit a87bc3b977668fa6dcbdb7e74b413df7d917a7f7 @maximeg committed Jun 14, 2012
@@ -0,0 +1,2 @@
+log
+
1 .rvmrc
@@ -0,0 +1 @@
+rvm use --create 1.9.3@mongoid_max_denormalize
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+gemspec
+
@@ -0,0 +1,60 @@
+PATH
+ remote: .
+ specs:
+ mongoid_max_denormalize (0.0.1)
+ activesupport (~> 3.1)
+ mongoid (>= 3.0.0.rc)
+
+GEM
+ remote: http://rubygems.org/
+ specs:
+ activemodel (3.2.5)
+ activesupport (= 3.2.5)
+ builder (~> 3.0.0)
+ activesupport (3.2.5)
+ i18n (~> 0.6)
+ multi_json (~> 1.0)
+ builder (3.0.0)
+ diff-lcs (1.1.3)
+ ffi (1.0.11)
+ guard (1.1.1)
+ listen (>= 0.4.2)
+ thor (>= 0.14.6)
+ guard-rspec (1.0.1)
+ guard (>= 1.1)
+ i18n (0.6.0)
+ listen (0.4.4)
+ rb-fchange (~> 0.0.5)
+ rb-fsevent (~> 0.9.1)
+ rb-inotify (~> 0.8.8)
+ mongoid (3.0.0.rc)
+ activemodel (~> 3.1)
+ moped (~> 1.0.0.rc)
+ origin (~> 1.0.0.rc)
+ tzinfo (~> 0.3.22)
+ moped (1.0.0.rc)
+ multi_json (1.3.6)
+ origin (1.0.0)
+ rb-fchange (0.0.5)
+ ffi
+ rb-fsevent (0.9.1)
+ rb-inotify (0.8.8)
+ ffi (>= 0.5.0)
+ rspec (2.10.0)
+ rspec-core (~> 2.10.0)
+ rspec-expectations (~> 2.10.0)
+ rspec-mocks (~> 2.10.0)
+ rspec-core (2.10.1)
+ rspec-expectations (2.10.0)
+ diff-lcs (~> 1.1.3)
+ rspec-mocks (2.10.1)
+ thor (0.15.2)
+ tzinfo (0.3.33)
+
+PLATFORMS
+ ruby
+
+DEPENDENCIES
+ guard-rspec
+ mongoid_max_denormalize!
+ rspec (~> 2.9)
@@ -0,0 +1,10 @@
+guard(
+ 'rspec',
+ :all_after_pass => false,
+ :cli => "--drb --fail-fast --tty --format documentation --colour") do
+
+ watch(%r{^spec/.+_spec\.rb$})
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb spec/cases" }
+ watch('spec/spec_helper.rb') { "spec" }
+end
+
21 LICENSE
@@ -0,0 +1,21 @@
+Copyright (c) Maxime Garcia
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
157 README.md
@@ -0,0 +1,157 @@
+# Mongoid::Max::Denormalize
+
+`Mongoid::Max::Denormalize` is a denormalization extension for Mongoid.
+
+It was designed for a minimum number of queries to the database.
+
+For now, support only Mongoid 3.
+
+* Propagate only when needed
+* Take advantage of atomic operations on multi documents of MongoDB
+
+
+
+## Installation
+
+Add the gem to your Gemfile:
+
+ gem 'mongoid_max_denormalize'
+
+Or install with RubyGems:
+
+ $ gem install mongoid_max_denormalize
+
+
+
+## Usage
+
+### Basic usage
+
+Add `include Mongoid::Max::Denormalize` in your model and also:
+
+ denormalize relation, field_1, field_2 ... field_n, options
+
+
+### One to Many
+
+Supported fields: normal Mongoid fields, and methods.
+
+Supported options: none.
+
+Example :
+
+ class Post
+ include Mongoid::Document
+ field :title
+ def slug
+ title.try(:parameterize)
+ end
+ has_many :comments
+ end
+
+ class Comment
+ include Mongoid::Document
+ include Mongoid::Max::Denormalize
+ belons_to :post
+ denormalize :post, :title, :slug
+ end
+
+ @post = Post.create(:title => "Mush from the Wimp")
+ @comment = @post.comments.create
+ @comment.post_title #=> "Mush from the Wimp"
+ @comment.post_slug #=> "mush-from-the-wimp"
+ #
+ @post.update_attributes(:title => "All Must Share The Burden")
+ @comment.reload # to reload the comment from the DB
+ @comment.post_title #=> "All Must Share The Burden"
+ @comment.post_slug #=> "all-must-share-the-burden"
+
+**Tips :** In your views, do not use `@comment.post` but `@comment.post_id` or `@comment.post_id?`
+to avoid a query that checks/retrieve for the post. We want to avoid it, don't we ?
+
+Exemple : Check your logs, you'll see queries for the post :
+
+ # app/views/comments/_comment.html.erb
+ <div class="comment">
+ <% if @comment.post %>
+ <%= link_to @comment.post_title, @comment.post %>
+ <% end %>
+ </div>
+
+This is better :
+
+ # app/views/comments/_comment.html.erb
+ <div class="comment">
+ <% if @comment.post_id? %>
+ <%= link_to @comment.post_title, post_path(@comment.post_id, :slug => @comment.post_slug) %>
+ <% end %>
+ </div>
+
+
+### Many to One
+
+Supported fields: **only** normal Mongoid fields (no methods)
+
+Supported options:
+
+* `:count => true` : to keep a count !
+
+
+Example :
+
+ class Post
+ include Mongoid::Document
+ include Mongoid::Max::Denormalize
+ has_many :comments
+ denormalize :comments, :rating, :stuff, :count => true
+ end
+
+ class Comment
+ include Mongoid::Document
+ belons_to :post
+ field :rating
+ field :stuff
+ end
+
+ @post = Post.create(:title => "J'accuse !")
+ @comment = @post.comments.create(:rating => 5, :stuff => "A")
+ @comment = @post.comments.create(:rating => 3, :stuff => "B")
+ @post.reload
+ @post.comments_count #=> 2
+ @post.comments_rating #=> [5, 3]
+ @post.comments_stuff #=> ["A", "B"]
+
+You can see that each denormalized field in stored in a separate array. This is wanted.
+An option `:group` will come to allow :
+
+ @post.comments_fields #=> [{:rating => 5, :stuff => "A"},{:rating => 5, :stuff => "B"}]
+
+
+### Many to One
+
+To come...
+
+
+
+## Contributing
+
+Contributions and bug reports are welcome.
+
+Clone the repository and run `bundle install` to setup the development environment.
+
+Provide a case spec according to your changes/needs, taking example on existing ones (in `spec/cases`).
+
+To run the specs:
+
+ bundle exec rspec
+
+
+
+## Credits
+
+* Maxime Garcia [emaxime.com](http://emaxime.com) [@maximegarcia](http://twitter.com/maximegarcia)
+
+
+[License](https://github.com/maximeg/mongoid_max_denormalize/blob/master/LICENSE)
+\- [Report a bug](https://github.com/maximeg/mongoid_max_denormalize/issues).
+
@@ -0,0 +1,52 @@
+# encoding: utf-8
+module Mongoid
+ module Max
+ module Denormalize
+ extend ActiveSupport::Concern
+
+ included do
+
+ end
+
+ module ClassMethods
+
+ def denormalize(relation, *fields)
+ options = fields.extract_options!
+
+ #TODO make all real fields if fields.empty?
+
+# puts "#relation : #{relation.inspect}"
+# puts "#fields : #{fields.inspect}"
+# puts "#options : #{options.inspect}"
+
+ meta = self.relations[relation.to_s]
+ raise ConfigError.new("Unknown relation :#{relation}", self) if meta.nil?
+# puts "# meta : #{meta.inspect}"
+
+ inverse_meta = meta.klass.relations[meta.inverse.to_s]
+ raise ConfigError.new("Unknown inverse relation for :#{relation}", self) if inverse_meta.nil?
+# puts "#inverse_meta : #{inverse_meta.inspect}"
+
+ methods = []
+ fields.each do |field|
+ unless meta.klass.instance_methods.include? field
+ raise ConfigError.new("Unknown field or method :#{field} in :#{relation}", self)
+ end
+ end
+
+ if meta.relation == Mongoid::Relations::Referenced::In && inverse_meta.relation == Mongoid::Relations::Referenced::Many
+ OneToMany.new(self, meta, inverse_meta, fields, options).attach
+ elsif meta.relation == Mongoid::Relations::Referenced::Many && inverse_meta.relation == Mongoid::Relations::Referenced::In
+ ManyToOne.new(self, meta, inverse_meta, fields, options).attach
+ else
+ raise ConfigError.new("Relation not supported :#{relation}", self)
+ end
+
+ end
+
+ end
+
+ end
+ end
+end
+
@@ -0,0 +1,52 @@
+# encoding: utf-8
+module Mongoid
+ module Max
+ module Denormalize
+
+ class Base
+
+ attr_accessor :klass, :meta, :inverse_meta, :fields, :options
+
+ def initialize(klass, meta, inverse_meta, fields, options)
+ @klass = klass
+ @meta = meta
+ @inverse_meta = inverse_meta
+ @fields = fields
+ @options = options
+ end
+
+
+ def relation
+ @meta.name
+ end
+ def inverse_relation
+ @meta.inverse
+ end
+ def inverse_klass
+ @meta.klass
+ end
+
+
+ def fields_methods
+ @fields_methods ||= fields.select do |field|
+ meta.klass.fields[field.to_s].nil?
+ end
+ end
+ def fields_only
+ @fields_only ||= fields - fields_methods
+ end
+
+ class << self
+
+ def array_code_for(fields)
+ fields.map { |field| ":#{field}" }.join(", ")
+ end
+
+ end
+
+ end
+
+ end
+ end
+end
+
@@ -0,0 +1,15 @@
+# encoding: utf-8
+module Mongoid
+ module Max
+ module Denormalize
+ class ConfigError < ::StandardError
+
+ def initialize(summary, klass)
+ super("[#{klass}.denormalize] #{summary}")
+ end
+
+ end
+ end
+ end
+end
+
Oops, something went wrong.

0 comments on commit a87bc3b

Please sign in to comment.