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.
Files changed (34) hide show
  1. data/README +18 -0
  2. data/init.rb +2 -0
  3. data/lib/custom_fields.rb +28 -0
  4. data/lib/custom_fields/custom_fields_for.rb +50 -0
  5. data/lib/custom_fields/extensions/mongoid/associations/embeds_many.rb +31 -0
  6. data/lib/custom_fields/extensions/mongoid/associations/proxy.rb +20 -0
  7. data/lib/custom_fields/extensions/mongoid/associations/references_many.rb +33 -0
  8. data/lib/custom_fields/extensions/mongoid/hierarchy.rb +28 -0
  9. data/lib/custom_fields/field.rb +82 -0
  10. data/lib/custom_fields/proxy_class_enabler.rb +40 -0
  11. data/lib/custom_fields/types/boolean.rb +29 -0
  12. data/lib/custom_fields/types/category.rb +88 -0
  13. data/lib/custom_fields/types/date.rb +35 -0
  14. data/lib/custom_fields/types/default.rb +37 -0
  15. data/lib/custom_fields/types/file.rb +27 -0
  16. data/lib/custom_fields/types/string.rb +13 -0
  17. data/lib/custom_fields/types/text.rb +15 -0
  18. data/spec/integration/custom_fields_for_spec.rb +28 -0
  19. data/spec/integration/types/category_spec.rb +48 -0
  20. data/spec/integration/types/file_spec.rb +18 -0
  21. data/spec/models/person.rb +10 -0
  22. data/spec/models/project.rb +18 -0
  23. data/spec/models/task.rb +10 -0
  24. data/spec/spec_helper.rb +27 -0
  25. data/spec/support/carrierwave.rb +31 -0
  26. data/spec/support/mongoid.rb +6 -0
  27. data/spec/unit/custom_field_spec.rb +42 -0
  28. data/spec/unit/custom_fields_for_spec.rb +106 -0
  29. data/spec/unit/proxy_class_enabler_spec.rb +25 -0
  30. data/spec/unit/types/boolean_spec.rb +81 -0
  31. data/spec/unit/types/category_spec.rb +116 -0
  32. data/spec/unit/types/date_spec.rb +59 -0
  33. data/spec/unit/types/file_spec.rb +23 -0
  34. 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,13 @@
1
+ module CustomFields
2
+ module Types
3
+ module String
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ register_type :string
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ module CustomFields
2
+ module Types
3
+ module Text
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ field :text_formatting, :default => 'html'
9
+
10
+ register_type :text
11
+ end
12
+
13
+ end
14
+ end
15
+ 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,10 @@
1
+ class Person
2
+
3
+ include Mongoid::Document
4
+ include CustomFields::ProxyClassEnabler
5
+
6
+ field :full_name
7
+
8
+ embedded_in :project, :inverse_of => :people
9
+
10
+ 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
@@ -0,0 +1,10 @@
1
+ class Task
2
+
3
+ include Mongoid::Document
4
+ include CustomFields::ProxyClassEnabler
5
+
6
+ field :title
7
+
8
+ embedded_in :project, :inverse_of => :tasks
9
+
10
+ end
@@ -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,6 @@
1
+ Mongoid.configure do |config|
2
+ name = "custom_fields_test"
3
+ host = "localhost"
4
+ config.master = Mongo::Connection.new.db(name)
5
+ # config.master = Mongo::Connection.new('localhost', '27017', :logger => Logger.new($stdout)).db(name)
6
+ end
@@ -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