custom fields has its own repo + upgrade code for Mongoid beta 16 + clean code + fix rspec tests

This commit is contained in:
dinedine 2010-08-20 17:56:15 +02:00
parent 53500bda9e
commit e25ded881b
77 changed files with 258 additions and 1992 deletions

View File

@ -6,7 +6,7 @@ gem 'rails', '3.0.0.rc'
gem 'liquid', :git => 'git://github.com/locomotivecms/liquid.git', :ref => 'a41213c77cbc81dab87d'
gem 'bson_ext', '>= 1.0.1'
gem 'mongoid', :git => 'git://github.com/durran/mongoid.git', :ref => 'e387a0d1dc74da057472'
gem 'mongoid', '2.0.0.beta.16'
gem 'mongoid_acts_as_tree', '0.1.5'
gem 'mongo_session_store', '2.0.0.pre'
gem 'warden'
@ -22,6 +22,7 @@ gem 'heroku'
gem 'httparty', '0.6.1'
gem 'RedCloth'
gem 'inherited_resources', '1.1.2'
gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git'
gem 'jeweler'
# Development environment

View File

@ -1,15 +1,3 @@
GIT
remote: git://github.com/durran/mongoid.git
revision: e387a0d
ref: e387a0d1dc74da057472
specs:
mongoid (2.0.0.beta.15)
activemodel (= 3.0.0.rc)
bson (= 1.0.4)
mongo (= 1.0.6)
tzinfo (= 0.3.22)
will_paginate (~> 3.0.pre)
GIT
remote: git://github.com/floehopper/mocha.git
revision: d1715ff
@ -17,6 +5,12 @@ GIT
mocha (0.9.8.20090918115329)
rake
GIT
remote: git://github.com/locomotivecms/custom_fields.git
revision: d393307
specs:
custom_fields (0.0.0.1)
GIT
remote: git://github.com/locomotivecms/liquid.git
revision: a41213c
@ -152,10 +146,16 @@ GEM
treetop (>= 1.4.5)
mime-types (1.16)
mimetype-fu (0.1.2)
mongo (1.0.6)
mongo (1.0.7)
bson (>= 1.0.4)
mongo_session_store (2.0.0.pre)
actionpack (~> 3.0)
mongoid (2.0.0.beta.16)
activemodel (= 3.0.0.rc)
bson (= 1.0.4)
mongo (= 1.0.7)
tzinfo (= 0.3.22)
will_paginate (~> 3.0.pre)
mongoid_acts_as_tree (0.1.5)
bson (>= 0.20.1)
mongoid (<= 2.0.0)
@ -246,6 +246,7 @@ DEPENDENCIES
cgi_multipart_eof_fix
cucumber
cucumber-rails
custom_fields!
database_cleaner
devise!
factory_girl_rails
@ -262,7 +263,7 @@ DEPENDENCIES
mimetype-fu
mocha!
mongo_session_store (= 2.0.0.pre)
mongoid!
mongoid (= 2.0.0.beta.16)
mongoid_acts_as_tree (= 0.1.5)
mongrel
pickle!

View File

@ -1,13 +0,0 @@
module Admin
class LayoutsController < BaseController
sections 'settings'
respond_to :json, :only => :update
def index
@layouts = current_site.layouts.order_by([[:name, :asc]])
end
end
end

View File

@ -1,15 +1,16 @@
module Admin
class PagePartsController < BaseController
layout nil
respond_to :json
def index
parts = current_site.layouts.find(params[:layout_id]).parts
respond_with parts.collect(&:attributes)
end
end
end
# @DEPRECATED
# module Admin
# class PagePartsController < BaseController
#
# layout nil
#
# respond_to :json
#
# def index
# parts = current_site.layouts.find(params[:layout_id]).parts
#
# respond_with parts.collect(&:attributes)
# end
#
# end
# end

View File

@ -1,58 +1,59 @@
module Models
module Extensions
module Page
module Parts
extend ActiveSupport::Concern
included do
embeds_many :parts, :class_name => 'PagePart'
before_validation do |p|
if p.parts.empty?
p.parts << PagePart.build_body_part(p.respond_to?(:body) ? p.body : nil)
end
end
end
module InstanceMethods
def parts_attributes=(attributes)
self.update_parts(attributes.values.map { |attrs| PagePart.new(attrs) })
end
def joined_parts
self.parts.enabled.map(&:template).join('')
end
protected
def update_parts(parts)
performed = []
# add / update
parts.each do |part|
if (existing = self.parts.detect { |p| p.id == part.id || p.slug == part.slug })
existing.attributes = part.attributes.delete_if { |k, v| %w{_id slug}.include?(k) }
else
self.parts << (existing = part)
end
performed << existing unless existing.disabled?
end
# disable missing parts
(self.parts.map(&:slug) - performed.map(&:slug)).each do |slug|
self.parts.detect { |p| p.slug == slug }.disabled = true
end
end
def update_parts!(new_parts)
self.update_parts(new_parts)
self.save
end
end
end
end
end
end
# @DEPRECATED
# module Models
# module Extensions
# module Page
# module Parts
#
# extend ActiveSupport::Concern
#
# included do
# embeds_many :parts, :class_name => 'PagePart'
#
# before_validation do |p|
# if p.parts.empty?
# p.parts << PagePart.build_body_part(p.respond_to?(:body) ? p.body : nil)
# end
# end
# end
#
# module InstanceMethods
#
# def parts_attributes=(attributes)
# self.update_parts(attributes.values.map { |attrs| PagePart.new(attrs) })
# end
#
# def joined_parts
# self.parts.enabled.map(&:template).join('')
# end
#
# protected
#
# def update_parts(parts)
# performed = []
#
# # add / update
# parts.each do |part|
# if (existing = self.parts.detect { |p| p.id == part.id || p.slug == part.slug })
# existing.attributes = part.attributes.delete_if { |k, v| %w{_id slug}.include?(k) }
# else
# self.parts << (existing = part)
# end
# performed << existing unless existing.disabled?
# end
#
# # disable missing parts
# (self.parts.map(&:slug) - performed.map(&:slug)).each do |slug|
# self.parts.detect { |p| p.slug == slug }.disabled = true
# end
# end
#
# def update_parts!(new_parts)
# self.update_parts(new_parts)
# self.save
# end
#
# end
# end
# end
# end
# end

View File

@ -5,15 +5,6 @@ module Models
def render(context)
self.template.render(context)
# FIXME : old code based on layout / parts
# self.template.render(context)
#
# if self.layout
# self.layout.template.render(context)
# else
# ::Liquid::Template.parse("{{ content_for_layout }}").render(context)
# end
end
end

View File

@ -24,6 +24,7 @@ module Models
# Fixme (Didier L.): Instances methods are defined before the include itself
alias :fix_position :hacked_fix_position
alias :descendants :hacked_descendants
end
module InstanceMethods
@ -42,6 +43,11 @@ module Models
self.instance_variable_set :@_will_move, true
end
def hacked_descendants
return [] if new_record?
self.class.all_in(path_field => [self._id]).order_by tree_order
end
protected
def change_parent

View File

@ -1,74 +0,0 @@
# @deprecated
class Layout < LiquidTemplate
# acts_as_tree
protected
# TODO: move that in the liquify_template module
def after_parse_template
blocks = self.find_blocks(self.template.root)
self.template.send(:instance_variable_set, :"@parent_blocks", blocks)
end
def find_blocks(node, blocks = {})
if node.respond_to?(:nodelist) && node.nodelist
node.nodelist.inject(blocks) do |b, node|
if node.is_a?(Locomotive::Liquid::Tags::Block)
b[node.name] = node
end
self.find_blocks(node, b) # FIXME: find nested blocks too
b
end
end
blocks
end
## associations ##
# references_many :pages
# embeds_many :parts, :class_name => 'PagePart'
## callbacks ##
# before_save :build_parts_from_value
# after_save :update_parts_in_pages
## validations ##
# validates_format_of :value, :with => Locomotive::Regexps::CONTENT_FOR_LAYOUT, :message => :missing_content_for_layout
## methods ##
# protected
#
# def build_parts_from_value
# if self.value_changed? || self.new_record?
# self.parts.each { |p| p.disabled = true }
#
# self.value.scan(Locomotive::Regexps::CONTENT_FOR).each do |attributes|
# slug = attributes[0].strip.downcase
# name = slug.humanize
# name = I18n.t('attributes.defaults.page_parts.name') if slug == 'layout'
#
# if part = self.parts.detect { |p| p.slug == slug }
# part.name = name if name.present?
# part.disabled = false
# else
# self.parts.build :slug => slug, :name => name || slug
# end
# end
#
# # body always first
# body = self.parts.detect { |p| p.slug == 'layout' }
# self.parts.delete(body)
# self.parts.insert(0, body)
#
# @_update_pages = true if self.value_changed?
# end
# end
#
# def update_parts_in_pages
# self.pages.each { |p| p.send(:update_parts!, self.parts) } if @_update_pages
# end
end

View File

@ -22,7 +22,6 @@ class Page
## associations ##
referenced_in :site
# referenced_in :layout
## callbacks ##
before_validation :normalize_slug

View File

@ -1,35 +1,36 @@
class PagePart
include Mongoid::Document
## fields ##
field :name
field :slug
field :value
field :disabled, :type => Boolean, :default => false
field :value
## associations ##
embedded_in :page, :inverse_of => :parts
## validations ##
validates_presence_of :name, :slug
## named scopes ##
scope :enabled, where(:disabled => false)
## methods ##
def template
"{% capture content_for_#{self.slug} %}#{self.value}{% endcapture %}"
end
def self.build_body_part(body_content = nil)
self.new({
:name => I18n.t('attributes.defaults.page_parts.name'),
:value => body_content || I18n.t('attributes.defaults.pages.other.body'),
:slug => 'layout'
})
end
end
# @DEPRECATED
# class PagePart
#
# include Mongoid::Document
#
# ## fields ##
# field :name
# field :slug
# field :value
# field :disabled, :type => Boolean, :default => false
# field :value
#
# ## associations ##
# embedded_in :page, :inverse_of => :parts
#
# ## validations ##
# validates_presence_of :name, :slug
#
# ## named scopes ##
# scope :enabled, where(:disabled => false)
#
# ## methods ##
#
# def template
# "{% capture content_for_#{self.slug} %}#{self.value}{% endcapture %}"
# end
#
# def self.build_body_part(body_content = nil)
# self.new({
# :name => I18n.t('attributes.defaults.page_parts.name'),
# :value => body_content || I18n.t('attributes.defaults.pages.other.body'),
# :slug => 'layout'
# })
# end
#
# end

View File

@ -11,11 +11,10 @@ class Site
## associations ##
references_many :pages
references_many :layouts
references_many :snippets
references_many :theme_assets
references_many :asset_collections
references_many :content_types
references_many :snippets, :dependent => :destroy
references_many :theme_assets, :dependent => :destroy
references_many :asset_collections, :dependent => :destroy
references_many :content_types, :dependent => :destroy
embeds_many :memberships
## validations ##
@ -28,11 +27,13 @@ class Site
## callbacks ##
after_create :create_default_pages!
before_save :add_subdomain_to_domains
after_destroy :destroy_in_cascade!
after_destroy :destroy_pages
## named scopes ##
scope :match_domain, lambda { |domain| { :where => { :domains => domain } } }
scope :match_domain_with_exclusion_of, lambda { |domain, site| { :where => { :domains => domain, :_id.ne => site.id } } }
scope :match_domain, lambda { |domain| { :any_in => { :domains => [*domain] } } }
scope :match_domain_with_exclusion_of, lambda { |domain, site|
{ :any_in => { :domains => [*domain] }, :where => { :_id.ne => site.id } }
}
## methods ##
@ -67,7 +68,7 @@ class Site
return if self.domains.empty?
self.domains_without_subdomain.each do |domain|
if not self.class.match_domain_with_exclusion_of(domain, self).empty?
if self.class.match_domain_with_exclusion_of(domain, self).any?
self.errors.add(:domains, :domain_taken, :value => domain)
end
@ -88,10 +89,9 @@ class Site
end
end
def destroy_in_cascade!
%w{pages layouts snippets theme_assets asset_collections content_types}.each do |association|
self.send(association).destroy_all
end
def destroy_pages
# pages is a tree so we just need to delete the root (as well as the page not found page)
self.pages.index.first.try(:destroy) && self.pages.not_found.first.try(:destroy)
end
end

View File

@ -1,13 +0,0 @@
- content_for :head do
= javascript_include_tag 'admin/plugins/codemirror/codemirror', 'admin/layouts.js'
= image_picker_include_tags
= f.inputs :name => :information do
= f.input :name
= f.inputs :name => :code do
= f.custom_input :value, :css => 'code full', :with_label => false do
%code{ :class => 'html' }
= f.text_area :value
.more
= link_to t('admin.image_picker.link'), admin_theme_assets_path, :id => 'image-picker-link'

View File

@ -1,7 +0,0 @@
%li
%strong= link_to layout.name, edit_admin_layout_path(layout)
.more
%span!= t('.updated_at')
= l layout.updated_at, :format => :short
= link_to image_tag('admin/list/icons/trash.png'), admin_layout_path(layout), :class => 'remove', :confirm => t('admin.messages.confirm'), :method => :delete

View File

@ -1,15 +0,0 @@
- title t('.title')
- content_for :submenu do
= render 'admin/shared/menu/settings'
- content_for :buttons do
= admin_button_tag :new, new_admin_layout_url, :class => 'new'
%p!= t('.help')
= semantic_form_for @layout, :url => admin_layout_url(@layout), :html => { :class => 'save-with-shortcut' } do |form|
= render 'form', :f => form
= render 'admin/shared/form_actions', :back_url => admin_layouts_url, :button_label => :update

View File

@ -1,15 +0,0 @@
- title t('.title')
- content_for :submenu do
= render 'admin/shared/menu/settings'
- content_for :buttons do
= admin_button_tag :new, new_admin_layout_url, :class => 'new'
%p!= t('.help')
- if @layouts.empty?
%p.no-items!= t('.no_items', :url => new_admin_layout_url)
- else
%ul#layouts-list.list
= render @layouts

View File

@ -1,12 +0,0 @@
- title t('.title')
- content_for :submenu do
= render 'admin/shared/menu/settings'
%p!= t('.help')
= semantic_form_for @layout, :url => admin_layouts_url do |form|
= render 'form', :f => form
= render 'admin/shared/form_actions', :back_url => admin_layouts_url, :button_label => :create

View File

@ -6,8 +6,6 @@
= f.input :title
/ = f.input :layout_id, :as => :select, :collection => current_site.layouts.all.to_a, :input_html => { :data_url => admin_layout_page_parts_url('_id_to_replace_') }
- if not @page.index? and not @page.not_found?
= f.input :parent_id, :as => :select, :collection => parent_pages_options, :include_blank => false

View File

@ -1,6 +1,5 @@
%ul
= admin_submenu_item 'site', edit_admin_current_site_url
/ = admin_submenu_item 'layouts', admin_layouts_url
= admin_submenu_item 'snippets', admin_snippets_url
= admin_submenu_item 'theme_assets', admin_theme_assets_url
= admin_submenu_item 'account', edit_admin_my_account_url

View File

@ -20,7 +20,6 @@ en:
assets: Assets
settings: Settings
pages: Pages
layouts: Layouts
snippets: Snippets
account: My account
site: Site
@ -108,22 +107,6 @@ en:
week: 1 week
month: 1 month
layouts:
index:
title: Listing layouts
help: "Layouts give the look of a page (1, 2 or many columns)."
no_items: "There are no layouts for now. Just click <a href=\"%{url}\">here</a> to create the first one."
new: new layout
new:
title: New layout
help: "Fill in the form below to create your layout."
edit:
title: Editing layout
help: "Fill in the form below to update your layout."
new: new layout
layout:
updated_at: Updated at
snippets:
index:
title: Listing snippets
@ -291,7 +274,7 @@ en:
cache_strategy: "Cache the page for better performance. The 'Simple' choice is a good compromise."
templatized: "Use the page as a template for a model you defined."
snippet:
slug: "You need to know it in order to insert the snippet inside a page or a layout"
slug: "You need to know it in order to insert the snippet inside a page"
site:
meta_keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO."
meta_description: "Meta description used within the head tag of the page. Required for SEO."

View File

@ -30,7 +30,6 @@ fr:
assets: Média
settings: Paramètres
pages: Pages
layouts: Gabarits
snippets: Snippets
account: Mon compte
site: Site
@ -108,22 +107,6 @@ fr:
week: 1 semaine
month: 1 mois
layouts:
index:
title: Liste gabarits
help: "Les gabarits permettent de définir le squelette d'une page (1, 2 ou plusieurs colonnes)."
no_items: "Il n'existe pas de gabarit. Vous pouvez commencer par créer un <a href='%{url}'>ici</a>."
new: nouveau gabarit
new:
title: Nouveau gabarit
help: "Remplissez le formulaire ci-dessous pour créer votre gabarit."
edit:
title: Edition gabarit
help: "Remplissez le formulaire ci-dessous pour mettre à jour votre gabarit."
new: nouveau gabarit
layout:
updated_at: Mis à jour le
snippets:
index:
title: Liste des snippets
@ -290,7 +273,7 @@ fr:
cache_strategy: "Cache la page pour de meilleure performance. L'option 'Simple' est le meilleur compromis."
templatized: "Utilise la page comme un template pour un modèle défini."
snippet:
slug: "Utilisé pour insérer le snippet dans une page ou un gabarit."
slug: "Utilisé pour insérer le snippet dans une page."
site:
meta_keywords: "Mots-clés utilisés à l'intérieur de la balise HEAD. Ils sont séparés par un espace. Requis pour un meilleur référencement."
meta_description: "Description utilisée à l'intérieur de la balise HEAD. Requis pour un meilleur référencement."

View File

@ -2,18 +2,17 @@ en:
date:
formats:
default: "%m/%d/%Y"
errors:
messages:
domain_taken: "%{value} is already taken"
invalid_domain: "%{value} is invalid"
missing_content_for_layout: "should contain 'content_for_layout' liquid tag"
needs_admin_account: "One admin account is required at least"
protected_page: "You can not remove index or 404 pages"
extname_changed: "New file does not have the original extension"
array_too_short: "is too small (minimum element number is %{count})"
liquid_syntax_error: "Syntax error in page parts, please check the syntax"
attributes:
defaults:
pages:
@ -27,7 +26,7 @@ en:
body: "Content goes here"
page_parts:
name: "Body"
pagination:
previous: "&laquo; Previous"
next: "Next &raquo;"

View File

@ -28,7 +28,6 @@ fr:
domain_taken: "%{value} a été déjà pris"
invalid_domain: "%{value} n'est pas valide"
missing_content_for_layout: "doit contenir le tag liquid 'content_for_layout'"
needs_admin_account: "Un minimum d'un scompte admin est requis"
protected_page: "Vous ne pouvez pas supprimer les pages index et 404"
extname_changed: "Nouveau fichier n'a pas l'extension original"
@ -53,7 +52,6 @@ fr:
attributes:
page:
title: Titre
layout_id: Gabarit
parent: Parent
slug: Raccourci
templatized: Templatisée
@ -79,9 +77,6 @@ fr:
language: Langue
new_password: "Nouveau mot de passe"
new_password_confirmation: "Confirmation nouveau mot de passe"
layout:
name: Nom
body: Code
snippet:
body: Code
slug: Raccourci

View File

@ -40,16 +40,6 @@ en:
notice: "My site was successfully updated."
alert: "My site was not updated."
layouts:
create:
notice: "Layout was successfully created."
alert: "Layout was not created."
update:
notice: "Layout was successfully updated."
alert: "Layout was not updated."
destroy:
notice: "Layout was successfully deleted."
snippets:
create:
notice: "Snippet was successfully created."

View File

@ -40,16 +40,6 @@ fr:
notice: "Mon site a été mis à jour avec succès."
alert: "Mon site n'a pas été mis à jour."
layouts:
create:
notice: "Le gabarit a été crée avec succès."
alert: "Le gabarit n'a pas été crée."
update:
notice: "Le gabarit a été mis à jour avec succès."
alert: "Le gabarit n'a pas été mis à jour."
destroy:
notice: "Le gabarit a été supprimé avec succès."
snippets:
create:
notice: "Le snippet a été crée avec succès."

View File

@ -17,9 +17,6 @@ Rails.application.routes.draw do
get :get_path, :on => :collection
end
resources :layouts do
resources :page_parts, :only => :index
end
resources :snippets
resources :sites

View File

@ -18,7 +18,7 @@ Scenario: Creating a valid page
And I fill in "Title" with "Test"
And I fill in "Slug" with "test"
And I select "Home page" from "Parent"
And I fill in "Body" with "Lorem ipsum...."
And I fill in "Layout Template" with "Lorem ipsum...."
And I press "Create"
Then I should see "Page was successfully created."
And I should have "Lorem ipsum...." in the test page layout
@ -27,7 +27,7 @@ Scenario: Updating a valid page
When I go to pages
And I follow "Home page"
And I fill in "Title" with "Home page !"
And I fill in "Body" with "My new content is here"
And I fill in "Layout Template" with "My new content is here"
And I press "Update"
Then I should see "Page was successfully updated."
And I should have "My new content is here" in the index page layout

View File

@ -27,13 +27,13 @@ end
### Common
def create_layout_samples
Factory(:layout, :site => @site, :name => 'One column', :value => %{<html>
<head>
<title>My website</title>
</head>
<body>
<div id="main">{{ content_for_layout }}</div>
</body>
</html>})
end
# def create_layout_samples
# Factory(:layout, :site => @site, :name => 'One column', :value => %{<html>
# <head>
# <title>My website</title>
# </head>
# <body>
# <div id="main">{{ content_for_layout }}</div>
# </body>
# </html>})
# end

View File

@ -17,29 +17,24 @@ Given /^a page named "([^"]*)" with the template:$/ do |page_slug, template|
@page = create_content_page(page_slug, '', template)
end
# creates a layout
Given /^a layout named "([^"]*)" with the source:$/ do |layout_name, layout_body|
@layout = Factory(:layout, :name => layout_name, :value => layout_body, :site => @site)
end
# creates a part within a page
Given /^the page named "([^"]*)" has the part "([^"]*)" with the content:$/ do |page_slug, part_slug, part_contents|
page = @site.pages.where(:slug => page_slug).first
raise "Could not find page: #{page_slug}" unless page
# find or crate page part
part = page.parts.where(:slug => part_slug).first
unless part
part = page.parts.build(:name => part_slug.titleize, :slug => part_slug)
end
# set part value
part.value = part_contents
part.should be_valid
# save page with embedded part
page.save
end
# # creates a part within a page
# Given /^the page named "([^"]*)" has the part "([^"]*)" with the content:$/ do |page_slug, part_slug, part_contents|
# page = @site.pages.where(:slug => page_slug).first
# raise "Could not find page: #{page_slug}" unless page
#
# # find or crate page part
# part = page.parts.where(:slug => part_slug).first
# unless part
# part = page.parts.build(:name => part_slug.titleize, :slug => part_slug)
# end
#
# # set part value
# part.value = part_contents
# part.should be_valid
#
# # save page with embedded part
# page.save
# end
# try to render a page by slug
When /^I view the rendered page at "([^"]*)"$/ do |path|

View File

@ -5,9 +5,5 @@ module Locomotive
DOMAIN = /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/ix
CONTENT_FOR = /\{\{\s*content_for_([a-zA-Z]{1}[a-zA-Z_0-9]*)(\s+.*)?\s*\}\}/
CONTENT_FOR_LAYOUT = /\{\{\s*content_for_layout\s*/
end
end

View File

@ -1,7 +0,0 @@
$(document).ready(function() {
$('a#image-picker-link').imagepicker({
insertFn: function(link) {
return "{{ theme_images." + link.attr('data-slug') + " }}";
}
});
});

View File

@ -17,8 +17,9 @@ $(document).ready(function() {
var newRow = lastRow.clone(true).removeClass('template').addClass('added').insertBefore(lastRow);
// should copy the value of the select box
var input_name = $('input#current_site_subdomain').attr('name').split('[')[0];
var input = newRow.find('input.label')
.attr('name', 'site[domains][]');
.attr('name', input_name + '[domains][]');
if (lastRow.find('input.label').val() == '') input.val("undefined");
// then reset the form

View File

@ -67,41 +67,41 @@ Factory.define :liquid_template do |t|
end
## Layouts ##
Factory.define :layout do |l|
l.name '1 main column + sidebar'
l.value %{<html>
<head>
<title>My website</title>
</head>
<body>
<div id="sidebar">
\{% block sidebar %\}
DEFAULT SIDEBAR CONTENT
\{% endblock %\}
</div>
<div id="main">
\{% block main %\}
DEFAULT MAIN CONTENT
\{% endblock %\}
</div>
</body>
</html>}
l.site { Site.where(:subdomain => "acme").first || Factory(:site) }
end
Factory.define :base_layout, :parent => :layout do |l|
l.name '1 main column + sidebar'
l.value %{<html>
<head>
<title>My website</title>
</head>
<body>
<div id="sidebar">\{\{ content_for_left_sidebar \}\}</div>
<div id="main">\{\{ content_for_layout | textile \}\}</div>
</body>
</html>}
end
# ## Layouts ##
# Factory.define :layout do |l|
# l.name '1 main column + sidebar'
# l.value %{<html>
# <head>
# <title>My website</title>
# </head>
# <body>
# <div id="sidebar">
# \{% block sidebar %\}
# DEFAULT SIDEBAR CONTENT
# \{% endblock %\}
# </div>
# <div id="main">
# \{% block main %\}
# DEFAULT MAIN CONTENT
# \{% endblock %\}
# </div>
# </body>
# </html>}
# l.site { Site.where(:subdomain => "acme").first || Factory(:site) }
# end
#
# Factory.define :base_layout, :parent => :layout do |l|
# l.name '1 main column + sidebar'
# l.value %{<html>
# <head>
# <title>My website</title>
# </head>
# <body>
# <div id="sidebar">\{\{ content_for_left_sidebar \}\}</div>
# <div id="main">\{\{ content_for_layout | textile \}\}</div>
# </body>
# </html>}
# end
## Snippets ##

View File

@ -7,21 +7,21 @@ describe Locomotive::Liquid::Tags::Consume do
it 'validates a basic syntax' do
markup = 'blog from "http://blog.locomotiveapp.org"'
lambda do
Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"])
Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"], {})
end.should_not raise_error
end
it 'validates more complex syntax with attributes' do
markup = 'blog from "http://www.locomotiveapp.org" username: "john", password: "easyone"'
lambda do
Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"])
Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"], {})
end.should_not raise_error
end
it 'raises an error if the syntax is incorrect' do
markup = 'blog from http://www.locomotiveapp.org'
lambda do
Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"])
Locomotive::Liquid::Tags::Consume.new('consume', markup, ["{% endconsume %}"], {})
end.should raise_error
end

View File

@ -5,14 +5,14 @@ describe Locomotive::Liquid::Tags::Paginate do
it 'should have a valid syntax' do
markup = "contents.projects by 5"
lambda do
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"])
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"], {})
end.should_not raise_error
end
it 'should raise an error if the syntax is incorrect' do
["contents.projects by a", "contents.projects", "contents.projects 5"].each do |markup|
lambda do
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"])
Locomotive::Liquid::Tags::Paginate.new('paginate', markup, ["{% endpaginate %}"], {})
end.should raise_error
end
end

View File

@ -3,7 +3,7 @@ require 'spec_helper'
describe Locomotive::Liquid::Tags::WithScope do
it 'should decode options (boolean, interger, ...)' do
scope = Locomotive::Liquid::Tags::WithScope.new('with_scope', 'active:true price:42 title:\'foo\' hidden:false', ["{% endwith_scope %}"])
scope = Locomotive::Liquid::Tags::WithScope.new('with_scope', 'active:true price:42 title:\'foo\' hidden:false', ["{% endwith_scope %}"], {})
attributes = scope.send(:decode, scope.instance_variable_get(:@attributes))
attributes['active'].should == true
attributes['price'].should == 42

View File

@ -1,64 +0,0 @@
require 'spec_helper'
describe Layout do
it 'should have a valid factory' do
Factory.build(:layout).should be_valid
end
# ## validations ##
#
# it 'should validate presence of content_for_layout in value' do
# layout = Factory.build(:layout, :value => 'without content_for_layout')
# layout.should_not be_valid
# layout.errors[:value].should == ["should contain 'content_for_layout' liquid tag"]
# end
#
# context 'dealing with page parts' do
#
# before(:each) do
# @layout = Factory.build(:layout)
# end
#
# it 'should have 2 parts' do
# @layout.send(:build_parts_from_value)
# @layout.parts.size.should == 2
#
# @layout.parts.first.name.should == 'Body'
# @layout.parts.first.slug.should == 'layout'
#
# @layout.parts.last.name.should == 'Left sidebar'
# @layout.parts.last.slug.should == 'left_sidebar'
# end
#
# it 'should not add parts to pages if layout does not change' do
# @layout.stubs(:value_changed?).returns(false)
# page = Factory.build(:page, :layout => @layout, :site => nil)
# page.expects(:update_parts!).never
# @layout.pages << page
# @layout.save
# end
#
# it 'should add parts to pages if layout changes' do
# @layout.value = @layout.value + "..."
# page = Factory.build(:page, :layout => @layout, :site => nil)
# page.expects(:update_parts!)
# @layout.pages << page
# @layout.save
# end
#
# end
#
# context 'parsing liquid template' do
#
# before(:each) do
# @layout = Factory.build(:layout)
# end
#
# it 'should not raise an error if template is empty' do
# @layout.template.should be_nil
# end
#
# end
end

View File

@ -105,120 +105,6 @@ describe Page do
end
describe 'accepts_nested_attributes_for used for parts' do
before(:each) do
@page = Factory.build(:page)
@page.parts.build(:name => 'Main content', :slug => 'layout')
@page.parts.build(:name => 'Left column', :slug => 'left_sidebar')
@page.parts.build(:name => 'Right column', :slug => 'right_sidebar')
end
it 'should add parts' do
attributes = { '0' => { 'slug' => 'footer', 'name' => 'A custom footer', 'value' => 'End of page' } }
@page.parts_attributes = attributes
@page.parts.size.should == 4
@page.parts.last.slug.should == 'footer'
@page.parts.last.disabled.should == false
end
it 'should update parts' do
attributes = { '0' => { 'slug' => 'layout', 'name' => 'A new name', 'value' => 'Hello world' } }
@page.parts_attributes = attributes
@page.parts.size.should == 3
@page.parts.first.slug.should == 'layout'
@page.parts.first.name.should == 'A new name'
@page.parts.first.value.should == 'Hello world'
end
it 'should disable parts' do
attributes = { '0' => { 'slug' => 'left_sidebar', 'disabled' => 'true' } }
@page.parts_attributes = attributes
@page.parts.size.should == 3
@page.parts.first.disabled.should == true
@page.parts[1].disabled.should == true
@page.parts[2].disabled.should == true
end
it 'should add/update/disable parts at the same time' do
@page.parts.size.should == 3
attributes = {
'0' => { 'slug' => 'layout', 'name' => 'Body', 'value' => 'Hello world' },
'1' => { 'slug' => 'left_sidebar', 'disabled' => 'true' },
'2' => { 'id' => @page.parts[2].id, 'value' => 'Content from right sidebar', 'disabled' => 'false' }
}
@page.parts_attributes = attributes
@page.parts.size.should == 3
@page.parts[0].value.should == 'Hello world'
@page.parts[1].disabled.should == true
@page.parts[2].disabled.should == false
end
it 'should update it with success (mongoid bug #71)' do
@page.save
@page = Page.first
@page.parts.size.should == 3
@page.parts_attributes = { '0' => { 'slug' => 'header', 'name' => 'A custom header', 'value' => 'Head of page' } }
@page.parts.size.should == 4
@page.save
@page = Page.first
@page.parts.size.should == 4
end
end
describe 'dealing with page parts' do # DUPLICATED ?
before(:each) do
@page = Factory.build(:page)
@parts = [
PagePart.new(:name => 'Main content', :slug => 'layout'),
PagePart.new(:name => 'Left column', :slug => 'left_sidebar'),
PagePart.new(:name => 'Right column', :slug => 'right_sidebar')
]
@page.send(:update_parts, @parts)
end
it 'should add new parts from an array of parts' do
@page.parts.size.should == 3
@page.parts.shift.name.should == 'Main content'
@page.parts.shift.name.should == 'Left column'
@page.parts.shift.name.should == 'Right column'
end
it 'should update parts' do
@parts[1].name = 'Very left column'
@page.send(:update_parts, @parts)
@page.parts.size.should == 3
@page.parts[1].name.should == 'Very left column'
@page.parts[1].slug.should == 'left_sidebar'
end
it 'should disable parts' do
@parts = [@parts.shift, @parts.pop]
@page.send(:update_parts, @parts)
@page.parts.size.should == 3
@page.parts[1].name.should == 'Left column'
@page.parts[1].disabled.should be_true
end
it 'should enable parts previously disabled' do
parts_at_first = @parts.clone
@parts = [@parts.shift, @parts.pop]
@page.send(:update_parts, @parts)
@page.send(:update_parts, parts_at_first)
@page.parts.size.should == 3
@page.parts[1].name.should == 'Left column'
@page.parts[1].disabled.should be_true
end
end
describe 'acts as tree' do
before(:each) do
@ -295,71 +181,6 @@ describe Page do
end
context 'rendering' do
before(:each) do
@page = Factory.build(:page, :site => nil)
@page.parts.build :slug => 'layout', :value => 'Hello world !'
@page.parts.build :slug => 'left_sidebar', :value => 'A sidebar...'
@page.send(:store_template)
@layout = Factory.build(:layout, :site => nil)
@layout.send(:store_template)
@context = Liquid::Context.new
end
context 'without layout' do
it 'should render the body part' do
@page.render(@context).should == 'Hello world !'
end
end
context 'with layout' do
it 'should render both the body and sidebar parts' do
@page.layout = @layout
@page.render(@context).should == %{<html>
<head>
<title>My website</title>
</head>
<body>
<div id="sidebar">A sidebar...</div>
<div id="main"><p>Hello world !</p></div>
</body>
</html>}
end
end
end
describe "creating a new page" do
context "with a body" do
before do
@site = Factory(:site, :subdomain => "somethingweird")
@page = Page.create({
:slug => "some_slug",
:title => "Page Title",
:body => "Page Body",
:published => true,
:site => @site
})
end
it "should be valid" do
@page.should be_valid
end
it "should render the passed in body attribute of the page" do
@page.render(Liquid::Context.new).should == "Page Body"
end
end
end
describe 'templatized extension' do
before(:each) do

View File

@ -9,87 +9,87 @@ describe Site do
it 'should have a valid factory' do
Factory.build(:site).should be_valid
end
## Validations ##
it 'should validate presence of name' do
site = Factory.build(:site, :name => nil)
site.should_not be_valid
site.errors[:name].should == ["can't be blank"]
end
it 'should validate presence of subdomain' do
site = Factory.build(:site, :subdomain => nil)
site.should_not be_valid
site.errors[:subdomain].should == ["can't be blank"]
end
%w{test test42}.each do |subdomain|
it "should accept subdomain like '#{subdomain}'" do
Factory.build(:site, :subdomain => subdomain).should be_valid
end
end
['-', '_test', 'test_', 't est', '42', '42test', 'foo_bar'].each do |subdomain|
it "should not accept subdomain like '#{subdomain}'" do
(site = Factory.build(:site, :subdomain => subdomain)).should_not be_valid
site.errors[:subdomain].should == ['is invalid']
end
end
it "should not use reserved keywords as subdomain" do
%w{www admin email blog webmail mail support help site sites}.each do |subdomain|
(site = Factory.build(:site, :subdomain => subdomain)).should_not be_valid
site.errors[:subdomain].should == ['is reserved']
end
end
it 'should validate uniqueness of subdomain' do
Factory(:site)
(site = Factory.build(:site)).should_not be_valid
site.errors[:subdomain].should == ["is already taken"]
end
it 'should validate uniqueness of domains' do
Factory(:site, :domains => %w{www.acme.net www.acme.com})
(site = Factory.build(:site, :domains => %w{www.acme.com})).should_not be_valid
site.errors[:domains].should == ["www.acme.com is already taken"]
(site = Factory.build(:site, :subdomain => 'foo', :domains => %w{acme.example.com})).should_not be_valid
site.errors[:domains].should == ["acme.example.com is already taken"]
end
it 'should validate format of domains' do
site = Factory.build(:site, :domains => ['barformat.superlongextension', '-foo.net'])
site.should_not be_valid
site.errors[:domains].should == ['barformat.superlongextension is invalid', '-foo.net is invalid']
end
## Named scopes ##
it 'should retrieve sites by domain' do
site_1 = Factory(:site, :domains => %w{www.acme.net})
site_2 = Factory(:site, :subdomain => 'test', :domains => %w{www.example.com})
sites = Site.match_domain('www.acme.net')
sites.size.should == 1
sites.first.should == site_1
sites = Site.match_domain('www.example.com')
sites.size.should == 1
sites.first.should == site_2
sites = Site.match_domain('test.example.com')
sites.size.should == 1
sites.first.should == site_2
sites = Site.match_domain('www.unknown.com')
sites.should be_empty
end
## Associations ##
it 'should have many accounts' do
site = Factory.build(:site)
account_1, account_2 = Factory(:account), Factory(:account, :name => 'homer', :email => 'homer@simpson.net')
@ -98,50 +98,41 @@ describe Site do
site.memberships.size.should == 2
site.accounts.should == [account_1, account_2]
end
## Methods ##
it 'should return domains without subdomain' do
site = Factory(:site, :domains => %w{www.acme.net www.acme.com})
site.domains.should == %w{www.acme.net www.acme.com acme.example.com}
site.domains_without_subdomain.should == %w{www.acme.net www.acme.com}
end
describe 'once created' do
before(:each) do
@site = Factory(:site)
end
it 'should create index and 404 pages' do
@site.pages.size.should == 2
@site.pages.first.slug.should == 'index'
@site.pages.last.slug.should == '404'
end
end
describe 'delete in cascade' do
describe 'deleting in cascade' do
before(:each) do
@site = Factory(:site)
end
it 'should destroy pages' do
@site.pages.expects(:destroy_all)
@site.destroy
it 'should also destroy pages' do
lambda {
@site.destroy
}.should change(Page, :count).by(-2)
end
it 'should destroy layouts' do
@site.layouts.expects(:destroy_all)
@site.destroy
end
it 'should destroy snippets' do
@site.snippets.expects(:destroy_all)
@site.destroy
end
end
end

View File

@ -1,3 +0,0 @@
--colour
--format nested
--backtrace

View File

@ -1,12 +0,0 @@
source 'http://gemcutter.org'
gem 'bson_ext', '>= 1.0.1'
gem 'mongo_ext'
gem 'mongoid', '2.0.0.beta.14'
gem 'activesupport', '~>3.0.0.beta'
gem 'carrierwave-rails3', :require => 'carrierwave'
group :test do
gem 'rspec', '>= 2.0.0.beta.10'
gem 'mocha', :git => 'git://github.com/floehopper/mocha.git'
end

View File

@ -1,20 +0,0 @@
Copyright (c) 2010 [name of plugin creator]
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 File

@ -1,13 +0,0 @@
CustomFields
===========
Introduction goes here.
Example
=======
Example goes here.
Copyright (c) 2010 [Didier Lafforgue], released under the MIT license

View File

@ -1,29 +0,0 @@
require "rubygems"
require "rake"
require "rake/rdoctask"
require "rspec"
require "rspec/core/rake_task"
desc 'Generate documentation for the custom_fields plugin.'
Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'CustomFields'
rdoc.options << '--line-numbers' << '--inline-source'
rdoc.rdoc_files.include('README')
rdoc.rdoc_files.include('lib/**/*.rb')
end
Rspec::Core::RakeTask.new('spec:unit') do |spec|
spec.pattern = "spec/unit/**/*_spec.rb"
# spec.pattern = "spec/unit/custom_fields_for_spec.rb"
# spec.pattern = "spec/unit/types/category_spec.rb"
end
Rspec::Core::RakeTask.new('spec:integration') do |spec|
spec.pattern = "spec/integration/**/*_spec.rb"
# spec.pattern = "spec/integration/types/category_spec.rb"
end
task :spec => ['spec:unit', 'spec:integration']
task :default => :spec

View File

@ -1,2 +0,0 @@
# Include hook code here
require File.dirname(__FILE__) + '/lib/custom_fields'

View File

@ -1,28 +0,0 @@
$:.unshift File.expand_path(File.dirname(__FILE__))
require 'active_support'
require 'carrierwave/orm/mongoid'
require 'custom_fields/extensions/mongoid/hierarchy'
require 'custom_fields/extensions/mongoid/associations/proxy'
require 'custom_fields/extensions/mongoid/associations/references_many'
require 'custom_fields/extensions/mongoid/associations/embeds_many'
require 'custom_fields/types/default'
require 'custom_fields/types/string'
require 'custom_fields/types/text'
require 'custom_fields/types/category'
require 'custom_fields/types/boolean'
require 'custom_fields/types/date'
require 'custom_fields/types/file'
require 'custom_fields/proxy_class_enabler'
require 'custom_fields/field'
require 'custom_fields/custom_fields_for'
module Mongoid
module CustomFields
extend ActiveSupport::Concern
included do
include ::CustomFields::CustomFieldsFor
end
end
end

View File

@ -1,50 +0,0 @@
module CustomFields
module CustomFieldsFor
def self.included(base)
base.extend(ClassMethods)
end
# Enhance an embedded collection by providing methods to manage custom fields
#
# class Company
# embeds_many :employees
# custom_fields_for :employees
# end
#
# class Employee
# embedded_in :company, :inverse_of => :employees
# field :name, String
# end
#
# company.employee_custom_fields.build :label => 'His/her position', :_alias => 'position', :kind => 'String'
#
# company.employees.build :name => 'Michael Scott', :position => 'Regional manager'
#
module ClassMethods
def custom_fields_for(collection_name)
singular_name = collection_name.to_s.singularize
class_eval <<-EOV
field :#{singular_name}_custom_fields_counter, :type => Integer, :default => 0
embeds_many :#{singular_name}_custom_fields, :class_name => "::CustomFields::Field"
validates_associated :#{singular_name}_custom_fields
accepts_nested_attributes_for :#{singular_name}_custom_fields, :allow_destroy => true
def ordered_#{singular_name}_custom_fields
self.#{singular_name}_custom_fields.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
end
EOV
end
end
end
end

View File

@ -1,31 +0,0 @@
# encoding: utf-8
module Mongoid #:nodoc:
module Associations #:nodoc:
class EmbedsMany < Proxy
def initialize_with_custom_fields(parent, options, target_array = nil)
if custom_fields?(parent, options.name)
options = options.clone # 2 parent instances should not share the exact same option instance
custom_fields = parent.send(:"ordered_#{custom_fields_association_name(options.name)}")
klass = options.klass.to_klass_with_custom_fields(custom_fields)
klass._parent = parent
klass.association_name = options.name
options.instance_eval <<-EOF
def klass=(klass); @klass = klass; end
def klass; @klass || class_name.constantize; end
EOF
options.klass = klass
end
initialize_without_custom_fields(parent, options, target_array)
end
alias_method_chain :initialize, :custom_fields
end
end
end

View File

@ -1,20 +0,0 @@
# encoding: utf-8
module Mongoid #:nodoc
module Associations #:nodoc
class Proxy #:nodoc
def custom_fields_association_name(association_name)
"#{association_name.to_s.singularize}_custom_fields".to_sym
end
def custom_fields?(object, association_name)
object.respond_to?(custom_fields_association_name(association_name))
end
def klass
@klass
end
end
end
end

View File

@ -1,33 +0,0 @@
# encoding: utf-8
module Mongoid #:nodoc:
module Associations #:nodoc:
# Represents an relational one-to-many association with an object in a
# separate collection or database.
class ReferencesMany < Proxy
def initialize_with_custom_fields(parent, options, target_array = nil)
if custom_fields?(parent, options.name)
options = options.clone # 2 parent instances should not share the exact same option instance
custom_fields = parent.send(:"ordered_#{custom_fields_association_name(options.name)}")
klass = options.klass.to_klass_with_custom_fields(custom_fields)
klass._parent = parent
klass.association_name = options.name
options.instance_eval <<-EOF
def klass=(klass); @klass = klass; end
def klass; @klass || class_name.constantize; end
EOF
options.klass = klass
end
initialize_without_custom_fields(parent, options, target_array)
end
alias_method_chain :initialize, :custom_fields
end
end
end

View File

@ -1,28 +0,0 @@
# encoding: utf-8
module Mongoid #:nodoc
module Hierarchy #:nodoc
module InstanceMethods
def parentize_with_custom_fields(object, association_name)
if association_name.to_s.ends_with?('_custom_fields')
self.singleton_class.associations = {}
self.singleton_class.embedded_in object.class.to_s.underscore.to_sym, :inverse_of => association_name
end
parentize_without_custom_fields(object, association_name)
if self.embedded? && self.instance_variable_get(:"@association_name").nil?
self.instance_variable_set(:"@association_name", association_name) # weird bug with proxy class
end
if association_name.to_s.ends_with?('_custom_fields')
self.send(:set_unique_name!)
self.send(:set_alias)
end
end
alias_method_chain :parentize, :custom_fields
end
end
end

View File

@ -1,82 +0,0 @@
module CustomFields
class Field
include ::Mongoid::Document
include ::Mongoid::Timestamps
# types ##
include Types::Default
include Types::String
include Types::Text
include Types::Category
include Types::Boolean
include Types::Date
include Types::File
## fields ##
field :label
field :_alias
field :_name
field :kind
field :hint
field :position, :type => Integer, :default => 0
## validations ##
validates_presence_of :label, :kind
validate :uniqueness_of_label
## methods ##
def field_type
self.class.field_types[self.kind.downcase.to_sym]
end
def apply(klass)
return unless self.valid?
klass.field self._name, :type => self.field_type if self.field_type
apply_method_name = :"apply_#{self.kind.downcase}_type"
if self.respond_to?(apply_method_name)
self.send(apply_method_name, klass)
else
apply_default_type(klass)
end
end
def safe_alias
self.set_alias
self._alias
end
protected
def uniqueness_of_label
duplicate = self.siblings.detect { |f| f.label == self.label && f._id != self._id }
if not duplicate.nil?
self.errors.add(:label, :taken)
end
end
def set_unique_name!
self._name ||= "custom_field_#{self.increment_counter!}"
end
def set_alias
return if self.label.blank? && self._alias.blank?
self._alias = (self._alias.blank? ? self.label : self._alias).parameterize('_').downcase
end
def increment_counter!
next_value = (self._parent.send(:"#{self.association_name}_counter") || 0) + 1
self._parent.send(:"#{self.association_name}_counter=", next_value)
next_value
end
def siblings
self._parent.send(self.association_name)
end
end
end

View File

@ -1,37 +0,0 @@
module CustomFields
module ProxyClassEnabler
extend ActiveSupport::Concern
included do
cattr_accessor :klass_with_custom_fields
def self.to_klass_with_custom_fields(fields)
return klass_with_custom_fields unless klass_with_custom_fields.nil?
klass = Class.new(self)
klass.class_eval <<-EOF
cattr_accessor :custom_fields, :_parent, :association_name
def self.model_name
@_model_name ||= ActiveModel::Name.new(self.superclass)
end
def custom_fields
self.class.custom_fields
end
EOF
klass.hereditary = false
klass.custom_fields = fields
[*fields].each { |field| field.apply(klass) }
klass_with_custom_fields = klass
end
end
end
end

View File

@ -1,29 +0,0 @@
module CustomFields
module Types
module Boolean
extend ActiveSupport::Concern
included do
register_type :boolean
end
module InstanceMethods
def apply_boolean_type(klass)
klass.class_eval <<-EOF
alias :#{self.safe_alias}= :#{self._name}=
def #{self.safe_alias}
::Boolean.set(read_attribute(:#{self._name}))
end
EOF
end
end
end
end
end

View File

@ -1,85 +0,0 @@
module CustomFields
module Types
module Category
extend ActiveSupport::Concern
included do
embeds_many :category_items, :class_name => 'CustomFields::Types::Category::Item'
validates_associated :category_items
accepts_nested_attributes_for :category_items, :allow_destroy => true
register_type :category, ::String
end
module InstanceMethods
def ordered_category_items
self.category_items.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
end
def category_names
self.category_items.collect(&:name)
end
def category_ids
self.category_items.collect(&:_id)
end
def apply_category_type(klass)
klass.cattr_accessor :"#{self.safe_alias}_items"
klass.send("#{self.safe_alias}_items=", self.ordered_category_items)
klass.class_eval <<-EOF
def self.#{self.safe_alias}_names
self.#{self.safe_alias}_items.collect(&:name)
end
def self.group_by_#{self.safe_alias}(list_method = nil)
groups = (if self.embedded?
list_method ||= self.association_name
self._parent.send(list_method)
else
list_method ||= :all
self.send(list_method)
end).to_a.group_by(&:#{self._name})
self.#{self.safe_alias}_items.collect do |category|
{
:name => category.name,
:items => groups[category._id] || []
}.with_indifferent_access
end
end
def #{self.safe_alias}=(name)
category_id = self.class.#{self.safe_alias}_items.find { |item| item.name == name }._id rescue name
write_attribute(:#{self._name}, category_id)
end
def #{self.safe_alias}
category_id = read_attribute(:#{self._name})
self.class.#{self.safe_alias}_items.find { |item| item._id == category_id }.name rescue category_id
end
EOF
end
end
class Item
include Mongoid::Document
field :name
field :position, :type => Integer, :default => 0
embedded_in :custom_field, :inverse_of => :category_items
validates_presence_of :name
end
end
end
end

View File

@ -1,35 +0,0 @@
module CustomFields
module Types
module Date
extend ActiveSupport::Concern
included do
register_type :date, ::Date
end
module InstanceMethods
def apply_date_type(klass)
klass.class_eval <<-EOF
def #{self.safe_alias}
self.#{self._name}.strftime(I18n.t('date.formats.default')) rescue nil
end
def #{self.safe_alias}=(value)
if value.is_a?(String)
date = ::Date._strptime(value, I18n.t('date.formats.default'))
value = Date.new(date[:year], date[:mon], date[:mday])
end
self.#{self._name} = value
end
EOF
end
end
end
end
end

View File

@ -1,37 +0,0 @@
module CustomFields
module Types
module Default
extend ActiveSupport::Concern
included do
cattr_accessor :field_types
end
module InstanceMethods
def apply_default_type(klass)
klass.class_eval <<-EOF
alias :#{self.safe_alias} :#{self._name}
alias :#{self.safe_alias}= :#{self._name}=
EOF
end
end
module ClassMethods
def register_type(kind, klass = ::String)
self.field_types ||= {}
self.field_types[kind.to_sym] = klass unless klass.nil?
self.class_eval <<-EOF
def #{kind.to_s}?
self.kind.downcase == '#{kind}' rescue false
end
EOF
end
end
end
end
end

View File

@ -1,27 +0,0 @@
module CustomFields
module Types
module File
extend ActiveSupport::Concern
included do
register_type :file, nil # do not create the default field
end
module InstanceMethods
def apply_file_type(klass)
klass.mount_uploader self._name, FileUploader
self.apply_default_type(klass)
end
end
class FileUploader < ::CarrierWave::Uploader::Base
end
end
end
end

View File

@ -1,13 +0,0 @@
module CustomFields
module Types
module String
extend ActiveSupport::Concern
included do
register_type :string
end
end
end
end

View File

@ -1,15 +0,0 @@
module CustomFields
module Types
module Text
extend ActiveSupport::Concern
included do
field :text_formatting, :default => 'html'
register_type :text
end
end
end
end

View File

@ -1 +0,0 @@
Hello world !

View File

@ -1,28 +0,0 @@
require 'spec_helper'
describe CustomFields::CustomFieldsFor do
describe 'Saving' do
before(:each) do
@project = Project.new(:name => 'Locomotive')
@project.person_custom_fields.build(:label => 'E-mail', :_alias => 'email', :kind => 'String')
@project.person_custom_fields.build(:label => 'Age', :_alias => 'age', :kind => 'String')
end
context '@create' do
it 'persists parent object' do
lambda { @project.save }.should change(Project, :count).by(1)
end
it 'persists custom fields' do
@project.save && @project.reload
@project.person_custom_fields.count.should == 2
end
end
end
end

View File

@ -1,26 +0,0 @@
require 'spec_helper'
describe CustomFields::Types::Category do
before(:each) do
@project = Project.new(:name => 'Locomotive')
@field = @project.task_custom_fields.build(:label => 'Main category', :_alias => 'main_category', :kind => 'Category')
end
context 'saving category items' do
before(:each) do
@field.category_items.build :name => 'Development'
@field.category_items.build :name => 'Design'
@field.updated_at = Time.now
end
it 'persists items' do
@field.save.should be_true
@project.reload
@project.task_custom_fields.first.category_items.size.should == 2
end
end
end

View File

@ -1,18 +0,0 @@
require 'spec_helper'
describe CustomFields::Types::File do
before(:each) do
@project = Project.new(:name => 'Locomotive')
@project.task_custom_fields.build(:label => 'Screenshot', :_alias => 'screenshot', :kind => 'File')
@project.save
@task = @project.tasks.build
end
it 'attaches file' do
@task.screenshot = FixturedFile.open('doc.txt')
@task.save
@task.screenshot.url.should == '/uploads/doc.txt'
end
end

View File

@ -1,10 +0,0 @@
class Person
include Mongoid::Document
include CustomFields::ProxyClassEnabler
field :full_name
embedded_in :project, :inverse_of => :people
end

View File

@ -1,18 +0,0 @@
class Project
include Mongoid::Document
include CustomFields::ProxyClassEnabler
include CustomFields::CustomFieldsFor
field :name
field :description
references_many :people
embeds_many :tasks
custom_fields_for :people
custom_fields_for :tasks
scope :ordered, :order_by => [[:name, :asc]]
end

View File

@ -1,10 +0,0 @@
class Task
include Mongoid::Document
include CustomFields::ProxyClassEnabler
field :title
embedded_in :project, :inverse_of => :tasks
end

View File

@ -1,27 +0,0 @@
$LOAD_PATH.unshift(File.dirname(__FILE__))
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
MODELS = File.join(File.dirname(__FILE__), 'models')
$LOAD_PATH.unshift(MODELS)
require 'rubygems'
require 'bundler'
Bundler.setup
Bundler.require
require 'mongoid'
require 'mocha'
require 'rspec'
require 'custom_fields'
Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) }
require 'support/mongoid'
require 'support/carrierwave'
Rspec.configure do |config|
config.mock_with :mocha
config.after :suite do
Mongoid.master.collections.select { |c| c.name != 'system.indexes' }.each(&:drop)
end
end

View File

@ -1,31 +0,0 @@
require 'carrierwave/test/matchers'
CarrierWave.configure do |config|
config.storage = :file
config.store_dir = "uploads"
config.cache_dir = "cache"
config.root = File.join(File.dirname(__FILE__), '..', 'tmp')
end
module FixturedFile
def self.open(filename)
File.new(self.path(filename))
end
def self.path(filename)
File.join(File.dirname(__FILE__), '..', 'fixtures', filename)
end
def self.duplicate(filename)
dst = File.join(File.dirname(__FILE__), '..', 'tmp', filename)
FileUtils.cp self.path(filename), dst
dst
end
def self.reset!
FileUtils.rm_rf(File.join(File.dirname(__FILE__), '..', 'tmp'))
FileUtils.mkdir(File.join(File.dirname(__FILE__), '..', 'tmp'))
end
end
FixturedFile.reset!

View File

@ -1,6 +0,0 @@
Mongoid.configure do |config|
name = "custom_fields_test"
host = "localhost"
config.master = Mongo::Connection.new.db(name)
# config.master = Mongo::Connection.new('localhost', '27017', :logger => Logger.new($stdout)).db(name)
end

View File

@ -1,42 +0,0 @@
require 'spec_helper'
describe CustomFields::Field do
it 'is initialized' do
lambda { CustomFields::Field.new }.should_not raise_error
end
context '#mongoid' do
before(:each) do
@field = CustomFields::Field.new(:label => 'manager', :_name => 'field_1', :kind => 'String', :_alias => 'manager')
@field.stubs(:valid?).returns(true)
@project = Project.to_klass_with_custom_fields(@field).new
end
it 'is added to the list of mongoid fields' do
@project.fields['field_1'].should_not be_nil
end
end
context 'on target class' do
before(:each) do
@field = CustomFields::Field.new(:label => 'manager', :_name => 'field_1', :kind => 'String', :_alias => 'manager')
@field.stubs(:valid?).returns(true)
@project = Project.to_klass_with_custom_fields(@field).new
end
it 'has a new field' do
@project.respond_to?(:manager).should be_true
end
it 'sets / retrieves a value' do
@project.manager = 'Mickael Scott'
@project.manager.should == 'Mickael Scott'
end
end
end

View File

@ -1,106 +0,0 @@
require 'spec_helper'
describe CustomFields::CustomFieldsFor do
context '#proxy class' do
before(:each) do
@project = Project.new
@klass = @project.tasks.klass
end
it 'returns the proxy class in the association' do
@klass.should == @project.tasks.build.class
end
it 'has a link to the parent' do
@klass._parent.should == @project
end
it 'has the association name which references to' do
@klass.association_name.should == 'tasks'
end
end
context 'with embedded collection' do
context '#association' do
before(:each) do
@project = Project.new
end
it 'has custom fields for embedded collection' do
@project.respond_to?(:task_custom_fields).should be_true
end
end
context '#building' do
before(:each) do
@project = Project.new
@project.task_custom_fields.build :label => 'Short summary', :_alias => 'summary', :kind => 'String'
@task = @project.tasks.build
end
it 'returns a new document whose Class is different from the original one' do
@task.class.should_not == Task
end
it 'returns a new document with custom field' do
@project.tasks.build
@project.tasks.build
@task.respond_to?(:summary).should be_true
end
it 'sets/gets custom attributes' do
@task.summary = 'Lorem ipsum...'
@task.summary.should == 'Lorem ipsum...'
end
end
end
context 'with related collection' do
context '#association' do
before(:each) do
@project = Project.new
end
it 'has custom fields for related collections' do
@project.respond_to?(:person_custom_fields).should be_true
end
end
context '#building' do
before(:each) do
@project = Project.new
@project.person_custom_fields.build :label => 'Position in the project', :_alias => 'position', :kind => 'String'
@person = @project.people.build
end
it 'returns a new document whose Class is different from the original one' do
@person.class.should_not == Person
end
it 'returns a new document with custom field' do
@person.respond_to?(:position).should be_true
end
it 'sets/gets custom attributes' do
@person.position = 'Designer'
@person.position.should == 'Designer'
end
end
end
end

View File

@ -1,25 +0,0 @@
require 'spec_helper'
describe CustomFields::ProxyClassEnabler do
context '#proxy klass' do
before(:each) do
@klass = Task.to_klass_with_custom_fields([])
end
it 'does not be flagged as a inherited document' do
@klass.new.hereditary?.should be_false
end
it 'has a list of custom fields' do
@klass.custom_fields.should == []
end
it 'has the exact same model name than its parent' do
@klass.model_name.should == 'Task'
end
end
end

View File

@ -1,81 +0,0 @@
require 'spec_helper'
describe CustomFields::Types::Boolean do
context 'on field class' do
before(:each) do
@field = CustomFields::Field.new
end
it 'returns true if it is a Boolean' do
@field.kind = 'boolean'
@field.boolean?.should be_true
end
it 'returns false if it is not a Boolean' do
@field.kind = 'string'
@field.boolean?.should be_false
end
end
context 'on target class' do
before(:each) do
@project = build_project_with_boolean
end
context '#setting' do
context '#true' do
it 'sets value from an integer' do
@project.active = 1
@project.active.should == true
@project.field_1.should == '1'
end
it 'sets value from a string' do
@project.active = '1'
@project.active.should == true
@project.field_1.should == '1'
@project.active = 'true'
@project.active.should == true
@project.field_1.should == 'true'
end
end
context '#false' do
it 'sets value from an integer' do
@project.active = 0
@project.active.should == false
@project.field_1.should == '0'
end
it 'sets value from a string' do
@project.active = '0'
@project.active.should == false
@project.field_1.should == '0'
@project.active = 'false'
@project.active.should == false
@project.field_1.should == 'false'
end
end
end
end
def build_project_with_boolean
field = CustomFields::Field.new(:label => 'Active', :_name => 'field_1', :kind => 'Boolean')
field.stubs(:valid?).returns(true)
Project.to_klass_with_custom_fields(field).new
end
end

View File

@ -1,112 +0,0 @@
require 'spec_helper'
describe CustomFields::Types::Category do
context 'on field class' do
before(:each) do
@field = CustomFields::Field.new
end
it 'has the category items field' do
@field.respond_to?(:category_items).should be_true
end
it 'has the apply method used for the target object' do
@field.respond_to?(:apply_category_type).should be_true
end
it 'returns true if it is a Category' do
@field.kind = 'category'
@field.category?.should be_true
end
it 'returns false if it is not a Category' do
@field.kind = 'string'
@field.category?.should be_false
end
end
context 'on target class' do
before(:each) do
@project = build_project_with_category
end
it 'has getter/setter' do
@project.respond_to?(:global_category).should be_true
@project.respond_to?(:global_category=).should be_true
end
it 'has the values of this category' do
@project.class.global_category_names.should == %w{Maintenance Design Development}
end
it 'sets the category from a name' do
@project.global_category = 'Design'
@project.global_category.should == 'Design'
@project.field_1.should == '42'
end
it 'sets the category even it does not exit' do
@project.global_category = 'Accounting'
@project.global_category.should == 'Accounting'
@project.field_1.should == 'Accounting'
end
context 'group by category' do
before(:each) do
seed_projects
@groups = @project.class.group_by_global_category
end
it 'is an non empty array' do
@groups.class.should == Array
@groups.size.should == 3
end
it 'is an array of hash composed of a name' do
@groups.collect { |g| g[:name] }.should == %w{Maintenance Design Development}
end
it 'is an array of hash composed of a list of objects' do
@groups[0][:items].size.should == 0
@groups[1][:items].size.should == 1
@groups[2][:items].size.should == 2
end
it 'passes method to retrieve items' do
@project.class.expects(:ordered)
@project.class.group_by_global_category(:ordered)
end
end
end
def build_project_with_category
field = build_category
Project.to_klass_with_custom_fields(field).new
end
def build_category
field = CustomFields::Field.new(:label => 'global_category', :_name => 'field_1', :kind => 'Category')
field.stubs(:valid?).returns(true)
field.category_items.build :name => 'Development', :_id => '41', :position => 2
field.category_items.build :name => 'Design', :_id => '42', :position => 1
field.category_items.build :name => 'Maintenance', :_id => '43', :position => 0
field
end
def seed_projects
list = [
@project.class.new(:name => 'Locomotive CMS', :global_category => '41'),
@project.class.new(:name => 'Ruby on Rails', :global_category => '41'),
@project.class.new(:name => 'Dribble', :global_category => '42')
]
@project.class.stubs(:all).returns(list)
end
end

View File

@ -1,59 +0,0 @@
require 'spec_helper'
describe CustomFields::Types::Date do
context 'on field class' do
before(:each) do
@field = CustomFields::Field.new
end
it 'returns true if it is a Date' do
@field.kind = 'Date'
@field.date?.should be_true
end
it 'returns false if it is not a Date' do
@field.kind = 'string'
@field.date?.should be_false
end
end
context 'on target class' do
before(:each) do
@project = build_project_with_date
@date = Date.parse('2010-06-29')
end
it 'sets value from a date' do
@project.started_at = @date
@project.started_at.should == '2010-06-29'
@project.field_1.class.should == Date
@project.field_1.should == @date
end
it 'sets value from a string' do
@project.started_at = '2010-06-29'
@project.started_at.class.should == String
@project.started_at.should == '2010-06-29'
@project.field_1.class.should == Date
@project.field_1.should == @date
end
it 'sets nil value' do
@project.started_at = nil
@project.started_at.should be_nil
@project.field_1.should be_nil
end
end
def build_project_with_date
field = CustomFields::Field.new(:label => 'Started at', :_name => 'field_1', :kind => 'Date')
field.stubs(:valid?).returns(true)
Project.to_klass_with_custom_fields(field).new
end
end

View File

@ -1,23 +0,0 @@
require 'spec_helper'
describe CustomFields::Types::Date do
context 'on field class' do
before(:each) do
@field = CustomFields::Field.new
end
it 'returns true if it is a Date' do
@field.kind = 'File'
@field.file?.should be_true
end
it 'returns false if it is not a Date' do
@field.kind = 'string'
@field.file?.should be_false
end
end
end

View File

@ -1 +0,0 @@
# Uninstall hook code here