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 'liquid', :git => 'git://github.com/locomotivecms/liquid.git', :ref => 'a41213c77cbc81dab87d'
gem 'bson_ext', '>= 1.0.1' 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 'mongoid_acts_as_tree', '0.1.5'
gem 'mongo_session_store', '2.0.0.pre' gem 'mongo_session_store', '2.0.0.pre'
gem 'warden' gem 'warden'
@ -22,6 +22,7 @@ gem 'heroku'
gem 'httparty', '0.6.1' gem 'httparty', '0.6.1'
gem 'RedCloth' gem 'RedCloth'
gem 'inherited_resources', '1.1.2' gem 'inherited_resources', '1.1.2'
gem 'custom_fields', :git => 'git://github.com/locomotivecms/custom_fields.git'
gem 'jeweler' gem 'jeweler'
# Development environment # 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 GIT
remote: git://github.com/floehopper/mocha.git remote: git://github.com/floehopper/mocha.git
revision: d1715ff revision: d1715ff
@ -17,6 +5,12 @@ GIT
mocha (0.9.8.20090918115329) mocha (0.9.8.20090918115329)
rake rake
GIT
remote: git://github.com/locomotivecms/custom_fields.git
revision: d393307
specs:
custom_fields (0.0.0.1)
GIT GIT
remote: git://github.com/locomotivecms/liquid.git remote: git://github.com/locomotivecms/liquid.git
revision: a41213c revision: a41213c
@ -152,10 +146,16 @@ GEM
treetop (>= 1.4.5) treetop (>= 1.4.5)
mime-types (1.16) mime-types (1.16)
mimetype-fu (0.1.2) mimetype-fu (0.1.2)
mongo (1.0.6) mongo (1.0.7)
bson (>= 1.0.4) bson (>= 1.0.4)
mongo_session_store (2.0.0.pre) mongo_session_store (2.0.0.pre)
actionpack (~> 3.0) 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) mongoid_acts_as_tree (0.1.5)
bson (>= 0.20.1) bson (>= 0.20.1)
mongoid (<= 2.0.0) mongoid (<= 2.0.0)
@ -246,6 +246,7 @@ DEPENDENCIES
cgi_multipart_eof_fix cgi_multipart_eof_fix
cucumber cucumber
cucumber-rails cucumber-rails
custom_fields!
database_cleaner database_cleaner
devise! devise!
factory_girl_rails factory_girl_rails
@ -262,7 +263,7 @@ DEPENDENCIES
mimetype-fu mimetype-fu
mocha! mocha!
mongo_session_store (= 2.0.0.pre) mongo_session_store (= 2.0.0.pre)
mongoid! mongoid (= 2.0.0.beta.16)
mongoid_acts_as_tree (= 0.1.5) mongoid_acts_as_tree (= 0.1.5)
mongrel mongrel
pickle! 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 # @DEPRECATED
class PagePartsController < BaseController # module Admin
# class PagePartsController < BaseController
layout nil #
# layout nil
respond_to :json #
# respond_to :json
def index #
parts = current_site.layouts.find(params[:layout_id]).parts # def index
# parts = current_site.layouts.find(params[:layout_id]).parts
respond_with parts.collect(&:attributes) #
end # respond_with parts.collect(&:attributes)
# end
end #
end # end
# end

View File

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

View File

@ -5,15 +5,6 @@ module Models
def render(context) def render(context)
self.template.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
end end

View File

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

View File

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

View File

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

View File

@ -1,6 +1,5 @@
%ul %ul
= admin_submenu_item 'site', edit_admin_current_site_url = 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 'snippets', admin_snippets_url
= admin_submenu_item 'theme_assets', admin_theme_assets_url = admin_submenu_item 'theme_assets', admin_theme_assets_url
= admin_submenu_item 'account', edit_admin_my_account_url = admin_submenu_item 'account', edit_admin_my_account_url

View File

@ -20,7 +20,6 @@ en:
assets: Assets assets: Assets
settings: Settings settings: Settings
pages: Pages pages: Pages
layouts: Layouts
snippets: Snippets snippets: Snippets
account: My account account: My account
site: Site site: Site
@ -108,22 +107,6 @@ en:
week: 1 week week: 1 week
month: 1 month 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: snippets:
index: index:
title: Listing snippets title: Listing snippets
@ -291,7 +274,7 @@ en:
cache_strategy: "Cache the page for better performance. The 'Simple' choice is a good compromise." 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." templatized: "Use the page as a template for a model you defined."
snippet: 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: site:
meta_keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO." 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." 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 assets: Média
settings: Paramètres settings: Paramètres
pages: Pages pages: Pages
layouts: Gabarits
snippets: Snippets snippets: Snippets
account: Mon compte account: Mon compte
site: Site site: Site
@ -108,22 +107,6 @@ fr:
week: 1 semaine week: 1 semaine
month: 1 mois 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: snippets:
index: index:
title: Liste des snippets 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." 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." templatized: "Utilise la page comme un template pour un modèle défini."
snippet: 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: 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_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." meta_description: "Description utilisée à l'intérieur de la balise HEAD. Requis pour un meilleur référencement."

View File

@ -7,7 +7,6 @@ en:
messages: messages:
domain_taken: "%{value} is already taken" domain_taken: "%{value} is already taken"
invalid_domain: "%{value} is invalid" 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" needs_admin_account: "One admin account is required at least"
protected_page: "You can not remove index or 404 pages" protected_page: "You can not remove index or 404 pages"
extname_changed: "New file does not have the original extension" extname_changed: "New file does not have the original extension"

View File

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

View File

@ -40,16 +40,6 @@ en:
notice: "My site was successfully updated." notice: "My site was successfully updated."
alert: "My site was not 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: snippets:
create: create:
notice: "Snippet was successfully created." notice: "Snippet was successfully created."

View File

@ -40,16 +40,6 @@ fr:
notice: "Mon site a été mis à jour avec succès." notice: "Mon site a été mis à jour avec succès."
alert: "Mon site n'a pas été mis à jour." 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: snippets:
create: create:
notice: "Le snippet a été crée avec succès." 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 get :get_path, :on => :collection
end end
resources :layouts do
resources :page_parts, :only => :index
end
resources :snippets resources :snippets
resources :sites resources :sites

View File

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

View File

@ -27,13 +27,13 @@ end
### Common ### Common
def create_layout_samples # def create_layout_samples
Factory(:layout, :site => @site, :name => 'One column', :value => %{<html> # Factory(:layout, :site => @site, :name => 'One column', :value => %{<html>
<head> # <head>
<title>My website</title> # <title>My website</title>
</head> # </head>
<body> # <body>
<div id="main">{{ content_for_layout }}</div> # <div id="main">{{ content_for_layout }}</div>
</body> # </body>
</html>}) # </html>})
end # 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) @page = create_content_page(page_slug, '', template)
end end
# creates a layout # # creates a part within a page
Given /^a layout named "([^"]*)" with the source:$/ do |layout_name, layout_body| # Given /^the page named "([^"]*)" has the part "([^"]*)" with the content:$/ do |page_slug, part_slug, part_contents|
@layout = Factory(:layout, :name => layout_name, :value => layout_body, :site => @site) # page = @site.pages.where(:slug => page_slug).first
end # raise "Could not find page: #{page_slug}" unless page
#
# creates a part within a page # # find or crate page part
Given /^the page named "([^"]*)" has the part "([^"]*)" with the content:$/ do |page_slug, part_slug, part_contents| # part = page.parts.where(:slug => part_slug).first
page = @site.pages.where(:slug => page_slug).first # unless part
raise "Could not find page: #{page_slug}" unless page # part = page.parts.build(:name => part_slug.titleize, :slug => part_slug)
# end
# find or crate page part #
part = page.parts.where(:slug => part_slug).first # # set part value
unless part # part.value = part_contents
part = page.parts.build(:name => part_slug.titleize, :slug => part_slug) # part.should be_valid
end #
# # save page with embedded part
# set part value # page.save
part.value = part_contents # end
part.should be_valid
# save page with embedded part
page.save
end
# try to render a page by slug # try to render a page by slug
When /^I view the rendered page at "([^"]*)"$/ do |path| 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 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
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); var newRow = lastRow.clone(true).removeClass('template').addClass('added').insertBefore(lastRow);
// should copy the value of the select box // 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') var input = newRow.find('input.label')
.attr('name', 'site[domains][]'); .attr('name', input_name + '[domains][]');
if (lastRow.find('input.label').val() == '') input.val("undefined"); if (lastRow.find('input.label').val() == '') input.val("undefined");
// then reset the form // then reset the form

View File

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

View File

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

View File

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

View File

@ -3,7 +3,7 @@ require 'spec_helper'
describe Locomotive::Liquid::Tags::WithScope do describe Locomotive::Liquid::Tags::WithScope do
it 'should decode options (boolean, interger, ...)' 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 = scope.send(:decode, scope.instance_variable_get(:@attributes))
attributes['active'].should == true attributes['active'].should == true
attributes['price'].should == 42 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 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 describe 'acts as tree' do
before(:each) do before(:each) do
@ -295,71 +181,6 @@ describe Page do
end 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 describe 'templatized extension' do
before(:each) do before(:each) do

View File

@ -121,25 +121,16 @@ describe Site do
end end
describe 'delete in cascade' do describe 'deleting in cascade' do
before(:each) do before(:each) do
@site = Factory(:site) @site = Factory(:site)
end end
it 'should destroy pages' do it 'should also destroy pages' do
@site.pages.expects(:destroy_all) lambda {
@site.destroy
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 @site.destroy
}.should change(Page, :count).by(-2)
end 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