Permalink
Browse files

Extracted module

  • Loading branch information...
1 parent 714dc09 commit a480d18d4614320cca4dcb73543884225c33a46c @logandk committed Jul 8, 2010
Showing with 310 additions and 0 deletions.
  1. +3 −0 .gitignore
  2. +20 −0 LICENSE
  3. +71 −0 README.md
  4. +27 −0 Rakefile
  5. +1 −0 VERSION
  6. +58 −0 lib/mongoid_denormalize.rb
  7. +13 −0 spec/models/comment.rb
  8. +14 −0 spec/models/post.rb
  9. +14 −0 spec/models/user.rb
  10. +73 −0 spec/mongoid_denormalize_spec.rb
  11. +16 −0 spec/spec_helper.rb
View
@@ -0,0 +1,3 @@
+.bundle
+.DS_Store
+spec/database.yml
View
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Logan Raarup
+
+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.
View
@@ -0,0 +1,71 @@
+Mongoid::Denormalize
+====================
+
+Helper module for denormalizing association attributes in Mongoid models. Why denormalize? Read *[A Note on Denormalization](http://www.mongodb.org/display/DOCS/MongoDB+Data+Modeling+and+Rails#MongoDBDataModelingandRails-ANoteonDenormalization)*.
+
+
+Installation
+------------
+
+Add the gem to your Bundler `Gemfile`:
+
+ gem 'mongoid_denormalize'
+
+Or install with RubyGems:
+
+ $ gem install mongoid_denormalize
+
+
+Usage
+-----
+
+In your model:
+
+ # Include the helper method
+ include Mongoid::Denormalize
+
+ # Define your denormalized fields
+ denormalize :title, :from => :post
+ denormalize :name, :avatar, :from => :user
+
+
+
+Example
+-------
+
+ def User
+ include Mongoid::Document
+ include Mongoid::Denormalize
+
+ references_many :posts
+
+ field :name
+ field :avatar
+ end
+
+ def Post
+ include Mongoid::Document
+ include Mongoid::Denormalize
+
+ referenced_in :user
+
+ field :title
+ denormalize :name, :avatar, :from => :user
+ end
+
+ >> user = User.create(:name => "John Doe", :avatar => "http://url/to/avatar.png")
+ >> post = Post.create(:title => "Blog post", :user => user)
+ >> post.user_name
+ "John Doe"
+ >> post.user_avatar
+ "http://url/to/avatar.png"
+ >> user.update_attributes(:name => "Bill")
+ >> post.save
+ >> post.user_name
+ "Bill"
+
+
+Credits
+-------
+
+Copyright (c) 2010 Logan Raarup, released under the MIT license.
View
@@ -0,0 +1,27 @@
+require 'rubygems'
+require 'rake'
+require 'spec/rake/spectask'
+require 'jeweler'
+
+spec_files = Rake::FileList["spec/**/*_spec.rb"]
+
+desc "Run specs"
+Spec::Rake::SpecTask.new do |t|
+ t.spec_files = spec_files
+ t.spec_opts = ["-c"]
+end
+
+task :default => :spec
+
+Jeweler::Tasks.new do |gemspec|
+ gemspec.name = "mongoid_denormalize"
+ gemspec.summary = "Mongoid denormalization helper."
+ gemspec.description = "Helper module for denormalizing association attributes in Mongoid models."
+ gemspec.add_runtime_dependency("mongoid", ["~>2.0.0.beta9"])
+ gemspec.files = Dir.glob("lib/**/*") + %w(LICENSE README.md)
+ gemspec.require_path = 'lib'
+ gemspec.email = "logan@logan.dk"
+ gemspec.homepage = "http://github.com/logandk/mongoid_denormalize"
+ gemspec.authors = ["Logan Raarup"]
+end
+Jeweler::GemcutterTasks.new
View
@@ -0,0 +1 @@
+0.0.1
View
@@ -0,0 +1,58 @@
+# = Mongoid::Denormalize
+#
+# Helper module for denormalizing association attributes in Mongoid models.
+#
+# TODO: Add support for multiple denormalize definitions
+module Mongoid::Denormalize
+ extend ActiveSupport::Concern
+
+ included do
+ cattr_accessor :denormalize_definitions
+ end
+
+ module ClassMethods
+ # Set a field or a number of fields to denormalize. Specify the associated object using the :from option.
+ #
+ # def Post
+ # include Mongoid::Document
+ # include Mongoid::Denormalize
+ #
+ # referenced_in :user
+ #
+ # denormalize :name, :avatar, :from => :user
+ # end
+ def denormalize(*fields, &block)
+ options = fields.pop
+
+ (self.denormalize_definitions ||= []) << { :fields => fields, :options => options, :block => block}
+
+ # Define schema
+ fields.each do |name|
+ denormalized_name = if block_given?
+ name
+ else
+ options[:to] ? options[:to] : "#{options[:from]}_#{name}"
+ end
+
+ field denormalized_name, :type => options[:type]
+ end
+
+ before_validation :denormalize_fields
+ end
+ end
+
+ private
+ def denormalize_fields
+ self.denormalize_definitions.each do |definition|
+ definition[:fields].each do |name|
+ if definition[:block]
+ value = (definition[:fields].length > 1 ? definition[:block].call(self, name) : definition[:block].call(self))
+ self.send("#{name}=", value)
+ else
+ attribute_name = (definition[:options][:to] ? definition[:options][:to] : "#{definition[:options][:from]}_#{name}")
+ self.send("#{attribute_name}=", self.send(definition[:options][:from]).try(name))
+ end
+ end
+ end
+ end
+end
View
@@ -0,0 +1,13 @@
+class Comment
+ include Mongoid::Document
+ include Mongoid::Denormalize
+
+ field :body
+
+ referenced_in :post
+ referenced_in :user
+
+ denormalize :name, :from => :user
+ denormalize :email, :from => :user, :to => :from_email
+ denormalize :created_at, :type => Time, :from => :post
+end
View
@@ -0,0 +1,14 @@
+class Post
+ include Mongoid::Document
+ include Mongoid::Denormalize
+
+ field :title
+ field :body
+ field :created_at, :type => Time
+
+ referenced_in :user
+ references_many :comments
+
+ denormalize :name, :email, :from => :user
+ denormalize(:comment_count, :type => Integer) { |post| post.comments.count }
+end
View
@@ -0,0 +1,14 @@
+class User
+ include Mongoid::Document
+ include Mongoid::Denormalize
+
+ field :name
+ field :email
+
+ references_many :posts
+ references_many :comments
+
+ denormalize :post_titles, :post_dates, :type => Array do |user, field|
+ field == :post_titles ? user.posts.collect(&:title) : user.posts.collect(&:created_at).collect { |t| t + 300 }
+ end
+end
@@ -0,0 +1,73 @@
+require "spec_helper"
+
+describe Mongoid::Denormalize do
+ before(:all) do
+ Mongoid.master.collections.each do |c|
+ c.drop rescue nil
+ end
+
+ @user = User.create!(:name => "John Doe", :email => "john@doe.com")
+ @post = Post.create!(:title => "Blog post", :body => "Lorem ipsum...", :created_at => Time.parse("Jan 1 2010 12:00"), :user => @user)
+ @comment = Comment.create!(:body => "This is the comment", :post => @post, :user => @user)
+ end
+
+ context "denormalize associated object" do
+ it "should define multiple fields for association" do
+ @post.fields.should have_key "user_name"
+ @post.fields.should have_key "user_email"
+ end
+
+ it "should override the name of the denormalized field" do
+ @comment.fields.should have_key "from_email"
+ end
+
+ it "should default to string field type for associated fields" do
+ @post.fields["user_name"].type.should eql String
+ end
+
+ it "should allow setting the field type for associated fields" do
+ @comment.fields["post_created_at"].type.should eql Time
+ end
+
+ it "should allow multiple declarations for the same association" do
+ @comment.fields.should have_key "user_name"
+ @comment.fields.should have_key "from_email"
+ end
+
+ it "should denormalize fields without specified type" do
+ @comment.user_name.should eql @user.name
+ @comment.from_email.should eql @user.email
+ @post.user_name.should eql @user.name
+ @post.user_email.should eql @user.email
+ end
+
+ it "should denormalize fields with specified type" do
+ @comment.post_created_at.should eql @post.created_at
+ end
+ end
+
+ context "denormalization with block" do
+ it "should accept block for denormalization" do
+ @post.fields.should have_key "comment_count"
+ end
+
+ it "should accept multiple fields for block" do
+ @user.fields.should have_key "post_titles"
+ @user.fields.should have_key "post_dates"
+ end
+
+ it "should allow setting the field type" do
+ @user.fields["post_titles"].type.should eql Array
+ @post.fields["comment_count"].type.should eql Integer
+ end
+
+ it "should denormalize fields using block" do
+ @post.save!
+ @post.comment_count.should eql 1
+
+ @user.save!
+ @user.post_titles.should eql ["Blog post"]
+ @user.post_dates.should eql [Time.parse("Jan 1 2010 12:00") + 300]
+ end
+ end
+end
View
@@ -0,0 +1,16 @@
+require 'rubygems'
+require 'spec'
+require 'mongoid'
+require 'yaml'
+
+Mongoid.configure do |config|
+ settings = YAML.load(File.read(File.join(File.dirname(__FILE__), "database.yml")))
+
+ db = Mongo::Connection.new(settings['host'], settings['port']).db(settings['database'])
+ db.authenticate(settings['username'], settings['password'])
+
+ config.master = db
+end
+
+require File.expand_path("../../lib/mongoid_denormalize", __FILE__)
+Dir["#{File.dirname(__FILE__)}/models/*.rb"].each { |f| require f }

0 comments on commit a480d18

Please sign in to comment.