has_custom_fields 0.0.5 → 0.1.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/Gemfile +7 -0
- data/README.md +114 -0
- data/Rakefile +16 -107
- data/has_custom_fields.gemspec +25 -40
- data/init.rb +4 -0
- data/lib/has_custom_fields.rb +45 -396
- data/lib/has_custom_fields/base.rb +20 -0
- data/lib/has_custom_fields/class_methods.rb +212 -0
- data/lib/has_custom_fields/instance_methods.rb +124 -0
- data/lib/has_custom_fields/railtie.rb +25 -0
- data/lib/has_custom_fields/version.rb +3 -0
- data/spec/db/database.yml +21 -0
- data/spec/db/schema.rb +43 -0
- data/spec/has_custom_fields_spec.rb +150 -0
- data/spec/spec_helper.rb +28 -25
- data/spec/test_models/organization.rb +3 -0
- data/spec/test_models/user.rb +6 -0
- metadata +66 -31
- data/README.rdoc +0 -117
- data/SPECDOC +0 -23
- data/VERSION +0 -1
- data/has_custom_fields.tmproj +0 -63
- data/lib/custom_fields/custom_field_base.rb +0 -29
- data/spec/database.yml +0 -12
- data/spec/debug.log +0 -3211
- data/spec/fixtures/document.rb +0 -7
- data/spec/fixtures/people.yml +0 -4
- data/spec/fixtures/person.rb +0 -13
- data/spec/fixtures/person_contact_infos.yml +0 -10
- data/spec/fixtures/post.rb +0 -6
- data/spec/fixtures/post_attributes.yml +0 -15
- data/spec/fixtures/posts.yml +0 -9
- data/spec/fixtures/preference.rb +0 -5
- data/spec/fixtures/preferences.yml +0 -10
- data/spec/models/eav_model_with_no_arguments_spec.rb +0 -82
- data/spec/models/eav_model_with_options_spec.rb +0 -38
- data/spec/models/eav_validation_spec.rb +0 -12
- data/spec/rcov.opts +0 -1
- data/spec/schema.rb +0 -50
- data/spec/spec.opts +0 -2
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Has Custom Fields' do
|
4
|
+
|
5
|
+
context "with no fields defined" do
|
6
|
+
|
7
|
+
describe "class methods" do
|
8
|
+
|
9
|
+
it "raises an error if missing a scope to the has_custom_fields class" do
|
10
|
+
class TestUser < ActiveRecord::Base; end
|
11
|
+
expect {
|
12
|
+
TestUser.send(:has_custom_fields)
|
13
|
+
}.to raise_error(ArgumentError, 'Must define :scope => [] on the has_custom_fields class method')
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns an empty array" do
|
17
|
+
org = Organization.create!(:name => 'ABC Corp')
|
18
|
+
User.custom_field_fields(:organization, org.id).should == []
|
19
|
+
end
|
20
|
+
|
21
|
+
it "returns an empty array if the scoped object doesn't exist" do
|
22
|
+
User.custom_field_fields(:organization, nil).should == []
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises an exception if the scope doesn't exist" do
|
26
|
+
expect {
|
27
|
+
User.custom_field_fields(:something, nil)
|
28
|
+
}.to raise_error(HasCustomFields::InvalidScopeError, 'Class User does not have scope :something defined for has_custom_fields')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "instance methods" do
|
34
|
+
|
35
|
+
before(:each) do
|
36
|
+
@org = Organization.create!(:name => 'ABC Corp')
|
37
|
+
@user = User.create!(:name => 'Mikel')
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises an exception if the field does not exist" do
|
41
|
+
expect {
|
42
|
+
@user.custom_fields[:organization][@org.id]['High Potential']
|
43
|
+
}.to raise_error(ActiveRecord::RecordNotFound, 'No field High Potential for organization 1')
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "creating fields" do
|
51
|
+
|
52
|
+
it "creates the fields" do
|
53
|
+
@org = Organization.create!(:name => 'ABC Corp')
|
54
|
+
expect {
|
55
|
+
UserField.create!(:organization_id => @org.id, :name => 'Value', :style => 'text')
|
56
|
+
}.to change(HasCustomFields::UserField, :count).by(1)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with fields defined" do
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
@org = Organization.create!(:name => 'ABC Corp')
|
65
|
+
UserField.create!(:organization_id => @org.id, :name => 'Value', :style => 'text')
|
66
|
+
UserField.create!(:organization_id => @org.id, :name => 'Customer', :style => 'checkbox')
|
67
|
+
UserField.create!(:organization_id => @org.id, :name => 'Category', :style => 'select',
|
68
|
+
:user_field_select_options => [UserFieldSelectOption.create!(:option => 'CatA'),
|
69
|
+
UserFieldSelectOption.create!(:option => 'CatB'),
|
70
|
+
UserFieldSelectOption.create!(:option => 'CatC')])
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "class methods" do
|
74
|
+
|
75
|
+
it "returns an array of UserFields" do
|
76
|
+
User.custom_field_fields(:organization, @org.id).length.should == 3
|
77
|
+
values = User.custom_field_fields(:organization, @org.id).map(&:name)
|
78
|
+
values.should include('Customer')
|
79
|
+
values.should include('Value')
|
80
|
+
values.should include('Category')
|
81
|
+
end
|
82
|
+
|
83
|
+
it "returns an array of select options" do
|
84
|
+
select_options = User.custom_field_fields(:organization, @org.id).last.user_field_select_options.map(&:option)
|
85
|
+
select_options.should == ["CatA","CatB","CatC"]
|
86
|
+
end
|
87
|
+
|
88
|
+
it "should return an array of select options" do
|
89
|
+
select_options = User.custom_field_fields(:organization, @org.id).last.select_options_data
|
90
|
+
select_options.should == ["CatA","CatB","CatC"]
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should set up the has_many and belongs_to relationships" do
|
94
|
+
User.custom_field_fields(:organization, @org.id).first.respond_to?(:user_field_select_options).should == true
|
95
|
+
User.custom_field_fields(:organization, @org.id).last.user_field_select_options.first.respond_to?(:user_field).should == true
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "instance methods" do
|
101
|
+
|
102
|
+
before(:each) do
|
103
|
+
@user = User.create!(:name => 'Mikel', :organization => @org)
|
104
|
+
end
|
105
|
+
|
106
|
+
it "sets attributes accessible on the custom_fields virtual attribute" do
|
107
|
+
@user.update_attributes(:name => 'Mikel', :email => 'mikel@example.org',
|
108
|
+
:custom_fields => {:organization => {@org.id => {'Value' => '10000'}}})
|
109
|
+
@user.name.should == 'Mikel'
|
110
|
+
@user.email.should == 'mikel@example.org'
|
111
|
+
@user.custom_fields[:organization][@org.id]['Value'].should_not be_nil
|
112
|
+
end
|
113
|
+
|
114
|
+
it "returns nil if there is no value defined" do
|
115
|
+
@user.custom_fields[:organization][@org.id]['Customer'].should be_nil
|
116
|
+
@user.custom_fields[:organization][@org.id]['Value'].should be_nil
|
117
|
+
end
|
118
|
+
|
119
|
+
it "sets the value of the field and persists it in the database" do
|
120
|
+
expect {
|
121
|
+
@user.update_attributes(:custom_fields => {:organization => {@org.id => {'Value' => '10000', 'Customer' => '1'}}})
|
122
|
+
@user.custom_fields[:organization][@org.id]['Customer'].should == '1'
|
123
|
+
@user.custom_fields[:organization][@org.id]['Value'].should == '10000'
|
124
|
+
}.to change(UserAttribute, :count).by(2)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "does not persist in the database if the value is nil or blank" do
|
128
|
+
expect {
|
129
|
+
@user.update_attributes(:custom_fields => {:organization => {@org.id => {'Value' => '', 'Customer' => nil}}})
|
130
|
+
@user.custom_fields[:organization][@org.id]['Customer'].should be_nil
|
131
|
+
@user.custom_fields[:organization][@org.id]['Value'].should be_nil
|
132
|
+
}.to change(UserAttribute, :count).by(0)
|
133
|
+
end
|
134
|
+
|
135
|
+
it "deletes the value from the database if the value is nil or blank" do
|
136
|
+
@user.update_attributes(:custom_fields => {:organization => {@org.id => {'Value' => '10000', 'Customer' => '1'}}})
|
137
|
+
|
138
|
+
expect {
|
139
|
+
@user.update_attributes(:custom_fields => {:organization => {@org.id => {'Value' => '', 'Customer' => nil}}})
|
140
|
+
@user.custom_fields[:organization][@org.id]['Customer'].should be_nil
|
141
|
+
@user.custom_fields[:organization][@org.id]['Value'].should be_nil
|
142
|
+
}.to change(UserAttribute, :count).by(-2)
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,34 +1,37 @@
|
|
1
|
-
|
1
|
+
require "active_support"
|
2
|
+
require "active_record"
|
3
|
+
require "database_cleaner"
|
2
4
|
|
3
|
-
ENV[
|
5
|
+
ENV['debug'] = 'test' unless ENV['debug']
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
require 'active_record/fixtures'
|
7
|
+
# Establish DB Connection
|
8
|
+
config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'db', 'database.yml')))
|
9
|
+
ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
|
10
|
+
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
Debugger.start
|
14
|
-
rescue LoadError
|
15
|
-
end
|
12
|
+
# Load Test Schema into the Database
|
13
|
+
load(File.dirname(__FILE__) + "/db/schema.rb")
|
16
14
|
|
17
|
-
require
|
15
|
+
require File.dirname(__FILE__) + '/../init'
|
18
16
|
|
19
|
-
|
20
|
-
ActiveRecord::Base.logger = Logger.new(File.dirname(__FILE__) + "/debug.log")
|
21
|
-
ActiveRecord::Base.establish_connection(config[ENV['DB'] || 'mysql'])
|
17
|
+
# Load in the test models
|
22
18
|
|
23
|
-
|
24
|
-
|
19
|
+
require File.dirname(__FILE__) + '/test_models/user'
|
20
|
+
require File.dirname(__FILE__) + '/test_models/organization'
|
25
21
|
|
26
|
-
|
27
|
-
|
28
|
-
config.
|
29
|
-
|
30
|
-
|
22
|
+
RSpec.configure do |config|
|
23
|
+
|
24
|
+
config.before(:suite) do
|
25
|
+
DatabaseCleaner.strategy = :transaction
|
26
|
+
DatabaseCleaner.clean_with(:truncation)
|
27
|
+
end
|
31
28
|
|
32
|
-
|
29
|
+
config.before(:each) do
|
30
|
+
DatabaseCleaner.start
|
31
|
+
end
|
33
32
|
|
34
|
-
|
33
|
+
config.after(:each) do
|
34
|
+
DatabaseCleaner.clean
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: has_custom_fields
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 1
|
9
|
+
- 1
|
10
|
+
version: 0.1.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- kylejginavan
|
@@ -15,11 +15,11 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date: 2012-
|
18
|
+
date: 2012-02-21 00:00:00 -06:00
|
19
19
|
default_executable:
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: activerecord
|
23
23
|
prerelease: false
|
24
24
|
requirement: &id001 !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
@@ -28,10 +28,54 @@ dependencies:
|
|
28
28
|
- !ruby/object:Gem::Version
|
29
29
|
hash: 3
|
30
30
|
segments:
|
31
|
+
- 3
|
32
|
+
- 1
|
31
33
|
- 0
|
32
|
-
version:
|
34
|
+
version: 3.1.0
|
33
35
|
type: :runtime
|
34
36
|
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: rspec
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 0
|
48
|
+
version: "0"
|
49
|
+
type: :development
|
50
|
+
version_requirements: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
52
|
+
name: database_cleaner
|
53
|
+
prerelease: false
|
54
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
55
|
+
none: false
|
56
|
+
requirements:
|
57
|
+
- - ">="
|
58
|
+
- !ruby/object:Gem::Version
|
59
|
+
hash: 3
|
60
|
+
segments:
|
61
|
+
- 0
|
62
|
+
version: "0"
|
63
|
+
type: :development
|
64
|
+
version_requirements: *id003
|
65
|
+
- !ruby/object:Gem::Dependency
|
66
|
+
name: sqlite3
|
67
|
+
prerelease: false
|
68
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
type: :development
|
78
|
+
version_requirements: *id004
|
35
79
|
description: Uses a vertical schema to add custom fields.
|
36
80
|
email: kylejginavan@gmail.com
|
37
81
|
executables: []
|
@@ -40,34 +84,25 @@ extensions: []
|
|
40
84
|
|
41
85
|
extra_rdoc_files:
|
42
86
|
- LICENSE
|
43
|
-
- README.
|
87
|
+
- README.md
|
44
88
|
files:
|
45
|
-
-
|
46
|
-
-
|
89
|
+
- Gemfile
|
90
|
+
- init.rb
|
47
91
|
- Rakefile
|
48
|
-
-
|
49
|
-
-
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
50
94
|
- has_custom_fields.gemspec
|
51
|
-
- has_custom_fields.
|
52
|
-
- lib/
|
95
|
+
- lib/has_custom_fields/base.rb
|
96
|
+
- lib/has_custom_fields/class_methods.rb
|
97
|
+
- lib/has_custom_fields/instance_methods.rb
|
98
|
+
- lib/has_custom_fields/railtie.rb
|
99
|
+
- lib/has_custom_fields/version.rb
|
53
100
|
- lib/has_custom_fields.rb
|
54
|
-
- spec/database.yml
|
55
|
-
- spec/
|
56
|
-
- spec/
|
57
|
-
- spec/
|
58
|
-
- spec/
|
59
|
-
- spec/fixtures/person_contact_infos.yml
|
60
|
-
- spec/fixtures/post.rb
|
61
|
-
- spec/fixtures/post_attributes.yml
|
62
|
-
- spec/fixtures/posts.yml
|
63
|
-
- spec/fixtures/preference.rb
|
64
|
-
- spec/fixtures/preferences.yml
|
65
|
-
- spec/models/eav_model_with_no_arguments_spec.rb
|
66
|
-
- spec/models/eav_model_with_options_spec.rb
|
67
|
-
- spec/models/eav_validation_spec.rb
|
68
|
-
- spec/rcov.opts
|
69
|
-
- spec/schema.rb
|
70
|
-
- spec/spec.opts
|
101
|
+
- spec/db/database.yml
|
102
|
+
- spec/db/schema.rb
|
103
|
+
- spec/test_models/user.rb
|
104
|
+
- spec/test_models/organization.rb
|
105
|
+
- spec/has_custom_fields_spec.rb
|
71
106
|
- spec/spec_helper.rb
|
72
107
|
has_rdoc: true
|
73
108
|
homepage: http://github.com/kylejginavan/has_custom_fields
|
data/README.rdoc
DELETED
@@ -1,117 +0,0 @@
|
|
1
|
-
HasCustomFields
|
2
|
-
==============
|
3
|
-
|
4
|
-
HasCustomFields allow for the Entity-attribute-value model (EAV), also
|
5
|
-
known as object-attribute-value model and open schema on any of your ActiveRecord
|
6
|
-
models.
|
7
|
-
|
8
|
-
= What is Entity-attribute-value model?
|
9
|
-
Entity-attribute-value model (EAV) is a data model that is used in circumstances
|
10
|
-
where the number of attributes (properties, parameters) that can be used to describe
|
11
|
-
a thing (an "entity" or "object") is potentially very vast, but the number that will
|
12
|
-
actually apply to a given entity is relatively modest.
|
13
|
-
|
14
|
-
= Typical Problem
|
15
|
-
A good example of this is where you need to store
|
16
|
-
lots (possible hundreds) of optional attributes on an object. My typical
|
17
|
-
reference example is when you have a User object. You want to store the
|
18
|
-
user's preferences between sessions. Every search, sort, etc in your
|
19
|
-
application you want to keep track of so when the user visits that section
|
20
|
-
of the application again you can simply restore the display to how it was.
|
21
|
-
|
22
|
-
So your controller might have:
|
23
|
-
|
24
|
-
Project.find :all, :conditions => current_user.project_search,
|
25
|
-
:order => current_user.project_order
|
26
|
-
|
27
|
-
But there could be hundreds of these little attributes that you really don't
|
28
|
-
want to store directly on the user object. It would make your table have too
|
29
|
-
many columns so it would be too much of a pain to deal with. Also there might
|
30
|
-
be performance problems. So instead you might do something like
|
31
|
-
this:
|
32
|
-
|
33
|
-
class User < ActiveRecord::Base
|
34
|
-
has_many :preferences
|
35
|
-
end
|
36
|
-
|
37
|
-
class Preferences < ActiveRecord::Base
|
38
|
-
belongs_to :user
|
39
|
-
end
|
40
|
-
|
41
|
-
Now simply give the Preference model a "name" and "value" column and you are
|
42
|
-
set..... except this is now too complicated. To retrieve a attribute you will
|
43
|
-
need to do something like:
|
44
|
-
|
45
|
-
Project.find :all,
|
46
|
-
:conditions => current_user.preferences.find_by_name('project_search').value,
|
47
|
-
:order => current_user.preferences.find_by_name('project_order').value
|
48
|
-
|
49
|
-
Sure you could fix this through a few methods on your model. But what about
|
50
|
-
saving?
|
51
|
-
|
52
|
-
current_user.preferences.create :name => 'project_search',
|
53
|
-
:value => "lastname LIKE 'jones%'"
|
54
|
-
current_user.preferences.create :name => 'project_order',
|
55
|
-
:value => "name"
|
56
|
-
|
57
|
-
Again this seems to much. Again we could add some methods to our model to
|
58
|
-
make this simpler but do we want to do this on every model. NO! So instead
|
59
|
-
we use this plugin which does everything for us.
|
60
|
-
|
61
|
-
= Capabilities
|
62
|
-
|
63
|
-
The HasCustomFields plugin is capable of modeling this problem in a intuitive
|
64
|
-
way. Instead of having to deal with a related model you treat all attributes
|
65
|
-
(both on the model and related) as if they are all on the model. The plugin
|
66
|
-
will try to save all attributes to the model (normal ActiveRecord behavior)
|
67
|
-
but if there is no column for an attribute it will try to save it to a
|
68
|
-
related model whose purpose is to store these many sparsely populated
|
69
|
-
attributes.
|
70
|
-
|
71
|
-
The main design goals are:
|
72
|
-
|
73
|
-
* Have the eav attributes feel like normal attributes. Simple gets and sets
|
74
|
-
will add and remove records from the related model.
|
75
|
-
* Allow for more than one related model. So for example on my User model I might
|
76
|
-
have some eav behavior going into a contact_info table while others are
|
77
|
-
going in a user_preferences table.
|
78
|
-
* Allow a model to determine what a valid eav attribute is for a given
|
79
|
-
related model so our model still can generate a NoMethodError.
|
80
|
-
|
81
|
-
Example
|
82
|
-
=======
|
83
|
-
|
84
|
-
Will make the current class have eav behaviour.
|
85
|
-
|
86
|
-
class Post < ActiveRecord::Base
|
87
|
-
has_custom_field_behavior
|
88
|
-
end
|
89
|
-
post = Post.find_by_title 'hello world'
|
90
|
-
puts "My post intro is: #{post.intro}"
|
91
|
-
post.teaser = 'An awesome introduction to the blog'
|
92
|
-
post.save
|
93
|
-
|
94
|
-
The above example should work even though "intro" and "teaser" are not
|
95
|
-
attributes on the Post model.
|
96
|
-
|
97
|
-
= Installation
|
98
|
-
|
99
|
-
./script/plugin install acts_as_custom_field_model
|
100
|
-
|
101
|
-
= RUNNING UNIT TESTS
|
102
|
-
|
103
|
-
== Creating the test database
|
104
|
-
|
105
|
-
The test databases will be created from the info specified in test/database.yml.
|
106
|
-
Either change that file to match your database or change your database to
|
107
|
-
match that file.
|
108
|
-
|
109
|
-
== Running with Rake
|
110
|
-
|
111
|
-
The easiest way to run the unit tests is through Rake. By default sqlite3
|
112
|
-
will be the database run. Just change your env variable DB to be the database
|
113
|
-
adaptor (specified in database.yml) that you want to use. The database and
|
114
|
-
permissions must already be setup but the tables will be created for you
|
115
|
-
from schema.rb.
|
116
|
-
|
117
|
-
Copyright (c) 2008 Marcus Wyatt, released under the MIT license
|