custom_fields 0.0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README +18 -0
- data/init.rb +2 -0
- data/lib/custom_fields.rb +28 -0
- data/lib/custom_fields/custom_fields_for.rb +50 -0
- data/lib/custom_fields/extensions/mongoid/associations/embeds_many.rb +31 -0
- data/lib/custom_fields/extensions/mongoid/associations/proxy.rb +20 -0
- data/lib/custom_fields/extensions/mongoid/associations/references_many.rb +33 -0
- data/lib/custom_fields/extensions/mongoid/hierarchy.rb +28 -0
- data/lib/custom_fields/field.rb +82 -0
- data/lib/custom_fields/proxy_class_enabler.rb +40 -0
- data/lib/custom_fields/types/boolean.rb +29 -0
- data/lib/custom_fields/types/category.rb +88 -0
- data/lib/custom_fields/types/date.rb +35 -0
- data/lib/custom_fields/types/default.rb +37 -0
- data/lib/custom_fields/types/file.rb +27 -0
- data/lib/custom_fields/types/string.rb +13 -0
- data/lib/custom_fields/types/text.rb +15 -0
- data/spec/integration/custom_fields_for_spec.rb +28 -0
- data/spec/integration/types/category_spec.rb +48 -0
- data/spec/integration/types/file_spec.rb +18 -0
- data/spec/models/person.rb +10 -0
- data/spec/models/project.rb +18 -0
- data/spec/models/task.rb +10 -0
- data/spec/spec_helper.rb +27 -0
- data/spec/support/carrierwave.rb +31 -0
- data/spec/support/mongoid.rb +6 -0
- data/spec/unit/custom_field_spec.rb +42 -0
- data/spec/unit/custom_fields_for_spec.rb +106 -0
- data/spec/unit/proxy_class_enabler_spec.rb +25 -0
- data/spec/unit/types/boolean_spec.rb +81 -0
- data/spec/unit/types/category_spec.rb +116 -0
- data/spec/unit/types/date_spec.rb +59 -0
- data/spec/unit/types/file_spec.rb +23 -0
- metadata +116 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
module CustomFields
|
2
|
+
module Types
|
3
|
+
module Date
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
register_type :date, ::Date
|
9
|
+
end
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
|
13
|
+
def apply_date_type(klass)
|
14
|
+
|
15
|
+
klass.class_eval <<-EOF
|
16
|
+
def #{self.safe_alias}
|
17
|
+
self.#{self._name}.strftime(I18n.t('date.formats.default')) rescue nil
|
18
|
+
end
|
19
|
+
|
20
|
+
def #{self.safe_alias}=(value)
|
21
|
+
if value.is_a?(String)
|
22
|
+
date = ::Date._strptime(value, I18n.t('date.formats.default'))
|
23
|
+
value = Date.new(date[:year], date[:mon], date[:mday])
|
24
|
+
end
|
25
|
+
self.#{self._name} = value
|
26
|
+
end
|
27
|
+
EOF
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module CustomFields
|
2
|
+
module Types
|
3
|
+
module Default
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
cattr_accessor :field_types
|
8
|
+
end
|
9
|
+
|
10
|
+
module InstanceMethods
|
11
|
+
|
12
|
+
def apply_default_type(klass)
|
13
|
+
klass.class_eval <<-EOF
|
14
|
+
alias :#{self.safe_alias} :#{self._name}
|
15
|
+
alias :#{self.safe_alias}= :#{self._name}=
|
16
|
+
EOF
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
module ClassMethods
|
22
|
+
|
23
|
+
def register_type(kind, klass = ::String)
|
24
|
+
self.field_types ||= {}
|
25
|
+
self.field_types[kind.to_sym] = klass unless klass.nil?
|
26
|
+
|
27
|
+
self.class_eval <<-EOF
|
28
|
+
def #{kind.to_s}?
|
29
|
+
self.kind.downcase == '#{kind}' rescue false
|
30
|
+
end
|
31
|
+
EOF
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module CustomFields
|
2
|
+
module Types
|
3
|
+
module File
|
4
|
+
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
register_type :file, nil # do not create the default field
|
9
|
+
end
|
10
|
+
|
11
|
+
module InstanceMethods
|
12
|
+
|
13
|
+
def apply_file_type(klass)
|
14
|
+
|
15
|
+
klass.mount_uploader self._name, FileUploader
|
16
|
+
|
17
|
+
self.apply_default_type(klass)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
class FileUploader < ::CarrierWave::Uploader::Base
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CustomFields::CustomFieldsFor do
|
4
|
+
|
5
|
+
describe 'Saving' do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@project = Project.new(:name => 'Locomotive')
|
9
|
+
@project.person_custom_fields.build(:label => 'E-mail', :_alias => 'email', :kind => 'String')
|
10
|
+
@project.person_custom_fields.build(:label => 'Age', :_alias => 'age', :kind => 'String')
|
11
|
+
end
|
12
|
+
|
13
|
+
context '@create' do
|
14
|
+
|
15
|
+
it 'persists parent object' do
|
16
|
+
lambda { @project.save }.should change(Project, :count).by(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'persists custom fields' do
|
20
|
+
@project.save && @project.reload
|
21
|
+
@project.person_custom_fields.count.should == 2
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CustomFields::Types::Category do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@project = Project.new(:name => 'Locomotive')
|
7
|
+
@field = @project.task_custom_fields.build(:label => 'Main category', :_alias => 'main_category', :kind => 'Category')
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'saving category items' do
|
11
|
+
|
12
|
+
before(:each) do
|
13
|
+
@field.category_items.build :name => 'Development'
|
14
|
+
@field.category_items.build :name => 'Design'
|
15
|
+
@field.updated_at = Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'persists items' do
|
19
|
+
@field.save.should be_true
|
20
|
+
@project.reload
|
21
|
+
@project.task_custom_fields.first.category_items.size.should == 2
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'assigning a category and persists' do
|
25
|
+
|
26
|
+
it 'sets the category from a category name' do
|
27
|
+
task = @project.tasks.build(:main_category => 'Design')
|
28
|
+
task.save && @project.reload
|
29
|
+
@project.tasks.first.main_category.should == 'Design'
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'sets the category from an id (string)' do
|
33
|
+
task = @project.tasks.build(:main_category => @field.category_items.last._id.to_s)
|
34
|
+
task.save && @project.reload
|
35
|
+
@project.tasks.first.main_category.should == 'Design'
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'sets the category from an id (BSON::ObjectID)' do
|
39
|
+
task = @project.tasks.build(:main_category => @field.category_items.last._id)
|
40
|
+
task.save && @project.reload
|
41
|
+
@project.tasks.first.main_category.should == 'Design'
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CustomFields::Types::File do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@project = Project.new(:name => 'Locomotive')
|
7
|
+
@project.task_custom_fields.build(:label => 'Screenshot', :_alias => 'screenshot', :kind => 'File')
|
8
|
+
@project.save
|
9
|
+
@task = @project.tasks.build
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'attaches file' do
|
13
|
+
@task.screenshot = FixturedFile.open('doc.txt')
|
14
|
+
@task.save
|
15
|
+
@task.screenshot.url.should == '/uploads/doc.txt'
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Project
|
2
|
+
|
3
|
+
include Mongoid::Document
|
4
|
+
include CustomFields::ProxyClassEnabler
|
5
|
+
include CustomFields::CustomFieldsFor
|
6
|
+
|
7
|
+
field :name
|
8
|
+
field :description
|
9
|
+
|
10
|
+
references_many :people
|
11
|
+
embeds_many :tasks
|
12
|
+
|
13
|
+
custom_fields_for :people
|
14
|
+
custom_fields_for :tasks
|
15
|
+
|
16
|
+
scope :ordered, :order_by => [[:name, :asc]]
|
17
|
+
|
18
|
+
end
|
data/spec/models/task.rb
ADDED
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
2
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
|
3
|
+
|
4
|
+
MODELS = File.join(File.dirname(__FILE__), 'models')
|
5
|
+
$LOAD_PATH.unshift(MODELS)
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'bundler'
|
9
|
+
Bundler.setup
|
10
|
+
Bundler.require
|
11
|
+
|
12
|
+
require 'mongoid'
|
13
|
+
require 'mocha'
|
14
|
+
require 'rspec'
|
15
|
+
require 'custom_fields'
|
16
|
+
|
17
|
+
Dir[ File.join(MODELS, "*.rb") ].sort.each { |file| require File.basename(file) }
|
18
|
+
|
19
|
+
require 'support/mongoid'
|
20
|
+
require 'support/carrierwave'
|
21
|
+
|
22
|
+
Rspec.configure do |config|
|
23
|
+
config.mock_with :mocha
|
24
|
+
config.after :suite do
|
25
|
+
Mongoid.master.collections.select { |c| c.name != 'system.indexes' }.each(&:drop)
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'carrierwave/test/matchers'
|
2
|
+
|
3
|
+
CarrierWave.configure do |config|
|
4
|
+
config.storage = :file
|
5
|
+
config.store_dir = "uploads"
|
6
|
+
config.cache_dir = "cache"
|
7
|
+
config.root = File.join(File.dirname(__FILE__), '..', 'tmp')
|
8
|
+
end
|
9
|
+
|
10
|
+
module FixturedFile
|
11
|
+
def self.open(filename)
|
12
|
+
File.new(self.path(filename))
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.path(filename)
|
16
|
+
File.join(File.dirname(__FILE__), '..', 'fixtures', filename)
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.duplicate(filename)
|
20
|
+
dst = File.join(File.dirname(__FILE__), '..', 'tmp', filename)
|
21
|
+
FileUtils.cp self.path(filename), dst
|
22
|
+
dst
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.reset!
|
26
|
+
FileUtils.rm_rf(File.join(File.dirname(__FILE__), '..', 'tmp'))
|
27
|
+
FileUtils.mkdir(File.join(File.dirname(__FILE__), '..', 'tmp'))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
FixturedFile.reset!
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CustomFields::Field do
|
4
|
+
|
5
|
+
it 'is initialized' do
|
6
|
+
lambda { CustomFields::Field.new }.should_not raise_error
|
7
|
+
end
|
8
|
+
|
9
|
+
context '#mongoid' do
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@field = CustomFields::Field.new(:label => 'manager', :_name => 'field_1', :kind => 'String', :_alias => 'manager')
|
13
|
+
@field.stubs(:valid?).returns(true)
|
14
|
+
@project = Project.to_klass_with_custom_fields(@field).new
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'is added to the list of mongoid fields' do
|
18
|
+
@project.fields['field_1'].should_not be_nil
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'on target class' do
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
@field = CustomFields::Field.new(:label => 'manager', :_name => 'field_1', :kind => 'String', :_alias => 'manager')
|
27
|
+
@field.stubs(:valid?).returns(true)
|
28
|
+
@project = Project.to_klass_with_custom_fields(@field).new
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'has a new field' do
|
32
|
+
@project.respond_to?(:manager).should be_true
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'sets / retrieves a value' do
|
36
|
+
@project.manager = 'Mickael Scott'
|
37
|
+
@project.manager.should == 'Mickael Scott'
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe CustomFields::CustomFieldsFor do
|
4
|
+
|
5
|
+
context '#proxy class' do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@project = Project.new
|
9
|
+
@klass = @project.tasks.klass
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'returns the proxy class in the association' do
|
13
|
+
@klass.should == @project.tasks.build.class
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'has a link to the parent' do
|
17
|
+
@klass._parent.should == @project
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'has the association name which references to' do
|
21
|
+
@klass.association_name.should == 'tasks'
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with embedded collection' do
|
27
|
+
|
28
|
+
context '#association' do
|
29
|
+
|
30
|
+
before(:each) do
|
31
|
+
@project = Project.new
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'has custom fields for embedded collection' do
|
35
|
+
@project.respond_to?(:task_custom_fields).should be_true
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
context '#building' do
|
41
|
+
|
42
|
+
before(:each) do
|
43
|
+
@project = Project.new
|
44
|
+
@project.task_custom_fields.build :label => 'Short summary', :_alias => 'summary', :kind => 'String'
|
45
|
+
@task = @project.tasks.build
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'returns a new document whose Class is different from the original one' do
|
49
|
+
@task.class.should_not == Task
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns a new document with custom field' do
|
53
|
+
@project.tasks.build
|
54
|
+
@project.tasks.build
|
55
|
+
@task.respond_to?(:summary).should be_true
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'sets/gets custom attributes' do
|
59
|
+
@task.summary = 'Lorem ipsum...'
|
60
|
+
@task.summary.should == 'Lorem ipsum...'
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
context 'with related collection' do
|
68
|
+
|
69
|
+
context '#association' do
|
70
|
+
|
71
|
+
before(:each) do
|
72
|
+
@project = Project.new
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'has custom fields for related collections' do
|
76
|
+
@project.respond_to?(:person_custom_fields).should be_true
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context '#building' do
|
82
|
+
|
83
|
+
before(:each) do
|
84
|
+
@project = Project.new
|
85
|
+
@project.person_custom_fields.build :label => 'Position in the project', :_alias => 'position', :kind => 'String'
|
86
|
+
@person = @project.people.build
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'returns a new document whose Class is different from the original one' do
|
90
|
+
@person.class.should_not == Person
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'returns a new document with custom field' do
|
94
|
+
@person.respond_to?(:position).should be_true
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'sets/gets custom attributes' do
|
98
|
+
@person.position = 'Designer'
|
99
|
+
@person.position.should == 'Designer'
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|