very simple api for contents (just create for now) + fix a lot of bugs in the custom fields plugin and some enhancements as well + add an ui for manage categories + fix bugs

This commit is contained in:
dinedine 2010-06-10 15:30:22 +02:00
parent fc690d8a0b
commit 7ccc3d4548
37 changed files with 285 additions and 187 deletions

View File

@ -2,7 +2,7 @@ module Admin::CustomFieldsHelper
def options_for_field_kind(selected = nil)
# %w{String Text Boolean Email File Date}
options = %w{String Text Select}.map do |kind|
options = %w{String Text Category}.map do |kind|
[t("admin.custom_fields.kind.#{kind.downcase}"), kind]
end
end

View File

@ -3,6 +3,9 @@ class ContentInstance
include Mongoid::Document
include Mongoid::Timestamps
## extensions ##
include CustomFields::ProxyClassEnabler
## fields (dynamic fields) ##
field :_position_in_list, :type => Integer, :default => 0
@ -17,6 +20,10 @@ class ContentInstance
## methods ##
def to_liquid
Locomotive::Liquid::Drops::Content.new(self)
end
protected
def require_highlighted_field

View File

@ -9,7 +9,8 @@
= render 'admin/custom_fields/index', :f => f, :collection_name => 'contents'
= f.foldable_inputs :name => :options, :class => 'switchable' do
= f.input :highlighted_field_name, :as => :select, :collection => options_for_highlighted_field(f.object, 'contents'), :include_blank => false
= f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false
- unless f.object.new_record?
= f.foldable_inputs :name => :options, :class => 'switchable' do
= f.input :highlighted_field_name, :as => :select, :collection => options_for_highlighted_field(f.object, 'contents'), :include_blank => false
= f.input :order_by, :as => :select, :collection => options_for_order_by(f.object, 'contents'), :include_blank => false

View File

@ -13,3 +13,5 @@
= render 'form', :f => f
= render 'admin/shared/form_actions', :back_url => admin_pages_url, :button_label => :create
= render 'admin/custom_fields/edit'

View File

@ -1,3 +1,7 @@
- content_for :head do
= javascript_include_tag 'admin/plugins/json2', 'admin/plugins/fancybox', 'admin/custom_fields/category', 'admin/contents'
= stylesheet_link_tag 'admin/plugins/fancybox', 'admin/box'
- highlighted_field_name = @content.content_type.highlighted_field_name
= f.inputs :name => :other_fields do
@ -5,6 +9,12 @@
- required = highlighted_field_name == field._name
- if field.string?
= f.input field._alias.to_sym, :label => field.label, :required => required
= f.input field._alias.to_sym, :label => field.label, :hint => field.hint, :required => required
- elsif field.text?
= f.input field._alias.to_sym, :label => field.label, :as => :text, :required => required
= f.input field._alias.to_sym, :label => field.label, :hint => field.hint, :as => :text, :required => required
- elsif field.category?
= f.custom_input field._alias.to_sym, :label => field.label, :hint => field.hint, :css => 'toggle' do
= f.select field._name.to_sym, field.ordered_category_items.collect { |item| [item.name, item.id] }
%button.button.light.edit-categories-link{ :type => 'button', :'data-url' => edit_admin_custom_field_path(@content_type.slug, field) }
%span= t('.edit_categories')

View File

@ -5,4 +5,5 @@
= form_tag '#', :class => 'formtastic' do
= fields_for CustomFields::Field.new, :builder => Formtastic::SemanticFormHelper.builder do |g|
= g.inputs :name => :information do
= g.input :_alias
= g.input :_alias
= g.input :hint

View File

@ -12,6 +12,8 @@
= g.hidden_field :position, :class => 'position'
= g.hidden_field :_alias, :class => 'alias'
= g.hidden_field :hint, :class => 'hint'
= g.text_field :label
@ -27,7 +29,7 @@
= link_to image_tag('admin/form/pen.png'), '#edit-custom-field', :class => 'edit first'
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove', :confirm => t('admin.messages.confirm')
= f.fields_for collection_name.to_sym, custom_fields.build(:label => 'field name'), :child_index => '-1' do |g|
= f.fields_for collection_name.to_sym, custom_fields.build(:label => 'field name', :_alias => ''), :child_index => '-1' do |g|
%li{ :class => 'item template' }
%span.handle
= image_tag 'admin/form/icons/drag.png'
@ -35,6 +37,8 @@
= g.hidden_field :position, :class => 'position'
= g.hidden_field :_alias, :class => 'alias'
= g.hidden_field :hint, :class => 'hint'
= g.text_field :label, :class => 'string label void'

View File

@ -6,7 +6,7 @@
= f.input :title
= f.input :layout_id, :as => :select, :collection => current_site.layouts.all, :input_html => { :data_url => admin_layout_page_parts_url('_id_to_replace_') }
= 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,55 +0,0 @@
- collection_name = "#{collection_name.singularize}_custom_fields"
- custom_fields = f.object.send(collection_name.to_sym)
- ordered_custom_fields = f.object.send(:"ordered_#{collection_name}")
= f.foldable_inputs :name => :custom_fields, :class => 'editable-list fields' do
- ordered_custom_fields.each do |field|
= f.fields_for collection_name.to_sym, field, :child_index => field._index do |g|
%li{ :class => "item added #{'error' unless field.errors.empty?}"}
%span.handle
= image_tag 'admin/form/icons/drag.png'
= g.hidden_field :position, :class => 'position'
= g.hidden_field :_alias, :class => 'alias'
= g.text_field :label
—
%em= t("admin.custom_fields.kind.#{field.kind.downcase}")
= g.select :kind, options_for_field_kind
 
%span.actions
= link_to image_tag('admin/form/pen.png'), '#', :class => 'edit first'
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove', :confirm => t('admin.messages.confirm')
= f.fields_for collection_name.to_sym, custom_fields.build(:label => 'field name'), :child_index => '-1' do |g|
%li{ :class => 'item template' }
%span.handle
= image_tag 'admin/form/icons/drag.png'
= g.hidden_field :position, :class => 'position'
= g.hidden_field :_alias, :class => 'alias'
= g.text_field :label, :class => 'string label void'
—
%em
= g.select :kind, options_for_field_kind
 
%span.actions
= link_to image_tag('admin/form/pen.png'), '#', :class => 'edit first'
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove', :confirm => t('admin.messages.confirm')
%button{ :class => 'button light add', :type => 'button' }
%span= t('admin.buttons.new_item')

View File

@ -10,7 +10,7 @@
= stylesheet_link_tag 'admin/layout', 'admin/plugins/toggle', 'admin/menu', 'admin/buttons', 'admin/formtastic', 'admin/formtastic_changes', 'admin/application', :media => 'screen', :cache => Rails.env.production?
= javascript_include_tag 'admin/jquery', 'admin/jquery.ui', 'admin/rails', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/plugins/cookie', 'admin/application', :cache => Rails.env.production?
= javascript_include_tag 'admin/jquery', 'admin/jquery.ui', 'admin/rails', 'admin/utils', 'admin/plugins/toggle', 'admin/plugins/growl', 'admin/plugins/cookie', 'admin/application', :cache => Rails.env.production?
%script{ :type => 'text/javascript' }
= find_and_preserve(growl_message)

View File

@ -38,7 +38,11 @@ en:
kind:
string: Simple Input
text: Text
select: Select
category: Select
edit_category:
title: Edit options
help: Manage the list of options for your select box.
collection_label: List of options
sessions:
new:
@ -174,7 +178,9 @@ en:
new:
title: '{{type}} — new item'
edit:
title: '{{type}} — editing item'
title: '{{type}} — editing item'
form:
edit_categories: Edit options
formtastic:
titles:

View File

@ -49,7 +49,10 @@ Rails.application.routes.draw do |map|
resources :contents, :path => "content_types/:slug/contents" do
put :sort, :on => :collection
end
resources :api_contents, :path => "api/:slug/contents", :controller => 'api_contents', :only => [:create]
resources :custom_fields, :path => "content_types/:slug/fields"
end
# magic urls

View File

@ -4,13 +4,10 @@ x make an engine:
- write doc about setting up the parent app
x helpers do not work
- deploy on Heroku
x refactoring: CustomFields::CustomField => CustomFields::Field
- new custom field type
- category
x optimization custom_fields: use dynamic class for a collection instead of modifying the metaclass each time we build an item
- deploy on Heroku
- missing translation in english
BACKLOG:
- devise messages in French
@ -36,6 +33,8 @@ NICE TO HAVE:
- asset collections: custom resizing if image
- super_finder
- better icons for mime type
- hint for custom fields (super easy to do !)
- tiny mce or similar for custom field text type.
DONE:
x admin layout
@ -111,4 +110,10 @@ x theme assets picker (???)
x lightbox (http://fancybox.net/api)
x select it
x flash upload (http://www.plupload.com/example_custom.php)
x refactor theme assets / assets uploaders
x refactor theme assets / assets uploaders
x refactoring: CustomFields::CustomField => CustomFields::Field
x optimization custom_fields: use dynamic class for a collection instead of modifying the metaclass each time we build an item
x new custom field type: category
x model
x ui
x liquid

View File

@ -24,31 +24,20 @@ module Locomotive
def first
content = @content_type.ordered_contents(@context['with_scope']).first
build_content_drop(content) unless content.nil?
end
def last
content = @content_type.ordered_contents(@context['with_scope']).last
build_content_drop(content) unless content.nil?
end
def each(&block)
@collection ||= @content_type.ordered_contents(@context['with_scope'])
to_content_drops.each(&block)
end
def to_content_drops
@collection.map { |c| build_content_drop(c) }
end
def build_content_drop(content)
Locomotive::Liquid::Drops::Content.new(content)
end
def paginate(options = {})
@collection ||= @content_type.ordered_contents(@context['with_scope']).paginate(options)
{
:collection => to_content_drops,
:collection => @collection,
:current_page => @collection.current_page,
:previous_page => @collection.previous_page,
:next_page => @collection.next_page,
@ -56,7 +45,16 @@ module Locomotive
:total_pages => @collection.total_pages,
:per_page => @collection.per_page
}
end
end
def api
{ 'create' => @context.registers[:controller].send('admin_api_contents_url', @content_type.slug) }
end
def before_method(meth)
klass = @content_type.contents.klass # delegate to the proxy class
klass.send(meth)
end
end
end
end

View File

@ -8,7 +8,7 @@ module Locomotive
end
def before_method(meth)
asset = site.theme_assets.where(:content_type => 'javascript', :slug => meth.to_s).first
asset = @site.theme_assets.where(:content_type => 'javascript', :slug => meth.to_s).first
!asset.nil? ? asset.source.url : nil
end

View File

@ -24,7 +24,7 @@ module Locomotive
@collection_name = $1
@per_page = $2.to_i
else
raise SyntaxError.new("Syntax Error in 'paginate' - Valid syntax: paginate [collection] by [number]")
raise ::Liquid::SyntaxError.new("Syntax Error in 'paginate' - Valid syntax: paginate [collection] by [number]")
end
super

View File

@ -18,6 +18,7 @@ class MiscFormBuilder < Formtastic::SemanticFormBuilder
html = options[:with_label] ? self.label(options[:label] || name) : ''
html += template.capture(&block) || ''
html += inline_hints_for(name, options) || ''
html += self.errors_on(name) || ''
template.content_tag(:li, template.find_and_preserve(html), :class => "#{options[:css]} #{'error' unless @object.errors[name].empty?}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 287 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 525 B

View File

@ -7,7 +7,7 @@ $(document).ready(function() {
if (!slug.hasClass('filled')) {
setTimeout(function() {
slug.val(input.val().replace(/\s/g, '_').toLowerCase());
slug.val(makeSlug(input.val()));
}, 50);
}
});

View File

@ -13,5 +13,15 @@ $(document).ready(function() {
items: 'li.content',
stop: function(event, ui) { updateContentsOrder(); }
});
$('button.edit-categories-link').click(function() {
var link = $(this);
$.fancybox({
titleShow: false,
href: link.attr('data-url'),
padding: 0,
onComplete: function() { SetupCustomFieldCategoryEditor(link.prev()); },
onCleanup: function() { console.log('closing...'); }
})
});
});

View File

@ -124,10 +124,16 @@ $(document).ready(function() {
e.stopPropagation();
});
$('#fancybox-wrap #custom_fields_custom_field__alias').val(link.parent().prevAll('.alias').val());
var alias = link.parent().prevAll('.alias').val();
if (alias == '') alias = makeSlug(link.parent().prevAll('.label').val());
$('#fancybox-wrap #custom_fields_field__alias').val(alias);
var hint = link.parent().prevAll('.hint').val();
$('#fancybox-wrap #custom_fields_field_hint').val(hint);
},
onCleanup: function() {
link.parent().prevAll('.alias').val($('#fancybox-wrap #custom_fields_custom_field__alias').val());
link.parent().prevAll('.alias').val($('#fancybox-wrap #custom_fields_field__alias').val());
link.parent().prevAll('.hint').val($('#fancybox-wrap #custom_fields_field_hint').val());
}
})
});

View File

@ -38,7 +38,7 @@ $(document).ready(function() {
if (!slug.hasClass('filled')) {
setTimeout(function() {
slug.val(input.val().replace(/\s/g, '_').toLowerCase()).addClass('touched');
slug.val(makeSlug(input.val())).addClass('touched');
}, 50);
}
});

View File

@ -7,7 +7,7 @@ $(document).ready(function() {
if (!slug.hasClass('filled')) {
setTimeout(function() {
slug.val(input.val().replace(/[\s']/g, '_').toLowerCase());
slug.val(makeSlug(input.val()));
}, 50);
}
});

View File

@ -13,14 +13,31 @@
}
#fancybox-inner form.formtastic legend span {
background-image: url("/images/admin/form/header-small.png");
width: 450px;
background-image: url("/images/admin/form/header-popup.png");
width: 453px;
}
#fancybox-inner form.formtastic ol {
background-image: url("/images/admin/form/footer-small.png");
background-image: url("/images/admin/form/footer-popup.png");
}
#fancybox-inner form.formtastic .editable-list ol {
width: 433px;
}
#fancybox-inner form.formtastic .editable-list li {
width: 413px;
}
#fancybox-inner form.formtastic .editable-list li {
background-image: url("/images/admin/form/item-popup.png");
}
#fancybox-inner form.formtastic .editable-list li.template {
background-image: url("/images/admin/form/big_item-popup.png");
}
#fancybox-inner p { color:#8B8D9A; font-size:0.8em; }
/* ___ asset picker ___ */
@ -35,5 +52,39 @@ div.asset-picker ul li.new-asset { display: none; }
/* ___ custom fields ___ */
#edit-custom-field {
width: 470px;
}
width: 473px;
}
#edit-custom-field-category {
width: 493px;
}
#edit-custom-field-category .inner {
padding: 10px 10px 61px 10px;
}
/* ___ form action ___ */
#fancybox-inner .popup-actions {
position: absolute;
left: 0px;
bottom: 0px;
height: 61px;
width: 100%;
background: #8b8d9a;
}
#fancybox-inner .popup-actions p {
padding: 15px;
margin: 0px;
text-align: right;
}
/*#fancybox-inner .actions a {
color: #fff;
text-decoration: none;
font-size: 0.8em;
position: relative;
top: 4px;
}*/

View File

@ -13,21 +13,17 @@ Rake::RDocTask.new(:rdoc) do |rdoc|
rdoc.rdoc_files.include('lib/**/*.rb')
end
# Spec::Rake::SpecTask.new(:rcov) do |spec|
# spec.libs << 'lib' << 'spec'
# spec.pattern = 'spec/**/*_spec.rb'
# spec.rcov = true
# 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 => [:check_dependencies, 'spec:unit', 'spec:integration']
task :spec => ['spec:unit', 'spec:integration']
task :default => :spec

View File

@ -2,10 +2,10 @@ $:.unshift File.expand_path(File.dirname(__FILE__))
require 'active_support'
require 'custom_fields/extensions/mongoid/document'
require 'custom_fields/extensions/mongoid/associations/proxy'
require 'custom_fields/extensions/mongoid/associations/has_many_related'
require 'custom_fields/extensions/mongoid/associations/embeds_many'
require 'custom_fields/extensions/mongoid/document'
require 'custom_fields/types/default'
require 'custom_fields/types/category'
require 'custom_fields/proxy_class_enabler'

View File

@ -2,6 +2,7 @@
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
@ -9,6 +10,8 @@ module Mongoid #:nodoc:
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
@ -23,22 +26,6 @@ module Mongoid #:nodoc:
alias_method_chain :initialize, :custom_fields
def build_with_custom_field_settings(attrs = {}, type = nil)
document = build_without_custom_field_settings(attrs, type)
if @association_name.ends_with?('_custom_fields')
document.class_eval <<-EOV
self.associations = {} # prevent associations to be nil
embedded_in :#{@parent.class.to_s.underscore}, :inverse_of => :#{@association_name}
EOV
document.send(:set_unique_name!)
document.send(:set_alias)
end
document
end
alias_method_chain :build, :custom_field_settings
end
end
end

View File

@ -12,6 +12,8 @@ module Mongoid #:nodoc:
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

View File

@ -11,6 +11,10 @@ module Mongoid #:nodoc
object.respond_to?(custom_fields_association_name(association_name))
end
def klass
@klass
end
end
end
end

View File

@ -2,34 +2,27 @@
module Mongoid #:nodoc:
module Document
module InstanceMethods
# def parentize_with_custom_fields(object, association_name)
# parentize_without_custom_fields(object, association_name)
#
# if self.custom_fields?(object, association_name)
# # puts "[parentize_with_custom_fields] association_name = #{association_name} / #{self.custom_fields_association_name(association_name)}"
# object.send(self.custom_fields_association_name(association_name)).each do |field|
# field.apply(self)
# end
#
# self.instance_eval <<-EOV
# def custom_fields
# fields = self._parent.send(:#{self.custom_fields_association_name(association_name)})
# fields.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
# end
# EOV
# end
# end
#
# alias_method_chain :parentize, :custom_fields
#
# 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)) &&
# object.associations[association_name]
# end
end
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

@ -9,10 +9,11 @@ module CustomFields
include Types::Category
## fields ##
field :label, :type => String
field :_alias, :type => String # need it for instance in: > asset.description (description being a custom field)
field :_name, :type => String
field :kind, :type => String
field :label
field :_alias # need it for instance in: > asset.description (description being a custom field)
field :_name
field :kind
field :hint
field :position, :type => Integer, :default => 0
## validations ##
@ -47,23 +48,6 @@ module CustomFields
apply_default_type(klass)
end
end
# def apply_to_object(object)
# return unless self.valid?
#
# # trick mongoid: fields are now on a the singleton class level also called metaclass
# self.singleton_class.fields = self.fields.clone
# self.singleton_class.send(:define_method, :fields) { self.singleton_class.fields }
#
# object.singleton_class.field self._name, :type => self.field_type
#
# case self.kind
# when 'Category'
# apply_category_type(object)
# else
# apply_default_type(object)
# end
# end
def safe_alias
self.set_alias
@ -85,7 +69,7 @@ module CustomFields
def set_alias
return if self.label.blank? && self._alias.blank?
self._alias = (self._alias || self.label).parameterize('_').downcase
self._alias = (self._alias.blank? ? self.label : self._alias).parameterize('_').downcase
end
def increment_counter!

View File

@ -12,7 +12,7 @@ module CustomFields
klass = Class.new(self)
klass.class_eval <<-EOF
cattr_accessor :custom_fields
cattr_accessor :custom_fields, :_parent, :association_name
def self.model_name
@_model_name ||= ActiveModel::Name.new(self.superclass)

View File

@ -6,6 +6,10 @@ module CustomFields
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
end
module InstanceMethods
@ -17,21 +21,36 @@ module CustomFields
def apply_category_type(klass)
klass.cattr_accessor :"#{self.safe_alias}_items"
klass.send("#{self.safe_alias}_items=", self.category_items)
klass.send("#{self.safe_alias}_items=", self.ordered_category_items)
klass.class_eval <<-EOF
def self.#{self.safe_alias}_names
#{self.safe_alias}_items.collect(&:name)
self.#{self.safe_alias}_items.collect(&:name)
end
def self.group_by_#{self.safe_alias}
groups = (if self.embedded?
self._parent.send(self.association_name).all
else
self.all
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.where(:name => name).first._id rescue nil
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(category_id).name rescue nil
self.class.#{self.safe_alias}_items.find { |item| item._id == category_id }.name rescue category_id
end
EOF
end
@ -46,6 +65,8 @@ module CustomFields
field :position, :type => Integer, :default => 0
embedded_in :custom_field, :inverse_of => :category_items
validates_presence_of :name
end
end
end

View File

@ -20,6 +20,7 @@ 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
Rspec.configure do |config|

View File

@ -2,6 +2,27 @@ 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
@ -13,7 +34,7 @@ describe CustomFields::CustomFieldsFor do
it 'has custom fields for embedded collection' do
@project.respond_to?(:task_custom_fields).should be_true
end
end
context '#building' do

View File

@ -30,13 +30,43 @@ describe CustomFields::Types::Category do
end
it 'has the values of this category' do
@project.class.global_category_names.should == %w{Development Design Maintenance}
@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_not be_nil
@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
end
end
@ -49,10 +79,19 @@ describe CustomFields::Types::Category do
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'
field.category_items.build :name => 'Design'
field.category_items.build :name => 'Maintenance'
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