rails-properties 3.4.3
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.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.travis.yml +74 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +22 -0
- data/README.md +135 -0
- data/Rakefile +6 -0
- data/ci/Gemfile-rails-3-1 +5 -0
- data/ci/Gemfile-rails-3-2 +5 -0
- data/ci/Gemfile-rails-4-0 +6 -0
- data/ci/Gemfile-rails-4-1 +6 -0
- data/ci/Gemfile-rails-4-2 +6 -0
- data/ci/Gemfile-rails-5-0 +5 -0
- data/ci/Gemfile-rails-5-1 +5 -0
- data/ci/Gemfile-rails-5-2 +5 -0
- data/lib/generators/rails_properties/migration/migration_generator.rb +23 -0
- data/lib/generators/rails_properties/migration/templates/migration.rb +21 -0
- data/lib/rails-properties.rb +23 -0
- data/lib/rails-properties/base.rb +48 -0
- data/lib/rails-properties/configuration.rb +32 -0
- data/lib/rails-properties/property_object.rb +84 -0
- data/lib/rails-properties/scopes.rb +34 -0
- data/lib/rails-properties/version.rb +3 -0
- data/rails-properties.gemspec +29 -0
- data/spec/configuration_spec.rb +108 -0
- data/spec/database.yml +3 -0
- data/spec/properties_spec.rb +248 -0
- data/spec/property_object_spec.rb +153 -0
- data/spec/queries_spec.rb +101 -0
- data/spec/scopes_spec.rb +31 -0
- data/spec/serialize_spec.rb +40 -0
- data/spec/spec_helper.rb +111 -0
- data/spec/support/matchers/perform_queries.rb +22 -0
- data/spec/support/query_counter.rb +17 -0
- metadata +172 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe RailsProperties::PropertyObject do
|
4
|
+
let(:user) { User.create! :name => 'Mr. Pink' }
|
5
|
+
|
6
|
+
if RailsProperties.can_protect_attributes?
|
7
|
+
let(:new_property_object) { user.property_objects.build({ :var => 'dashboard'}, :without_protection => true) }
|
8
|
+
let(:saved_property_object) { user.property_objects.create!({ :var => 'dashboard', :value => { 'theme' => 'pink', 'filter' => false}}, :without_protection => true) }
|
9
|
+
else
|
10
|
+
let(:new_property_object) { user.property_objects.build({ :var => 'dashboard'}) }
|
11
|
+
let(:saved_property_object) { user.property_objects.create!({ :var => 'dashboard', :value => { 'theme' => 'pink', 'filter' => false}}) }
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "serialization" do
|
15
|
+
it "should have a hash default" do
|
16
|
+
expect(RailsProperties::PropertyObject.new.value).to eq({})
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "Getter and Setter" do
|
21
|
+
context "on unsaved properties" do
|
22
|
+
it "should respond to setters" do
|
23
|
+
expect(new_property_object).to respond_to(:foo=)
|
24
|
+
expect(new_property_object).to respond_to(:bar=)
|
25
|
+
end
|
26
|
+
|
27
|
+
it "should not respond to some getters" do
|
28
|
+
expect { new_property_object.foo! }.to raise_error(NoMethodError)
|
29
|
+
expect { new_property_object.foo? }.to raise_error(NoMethodError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should not respond if a block is given" do
|
33
|
+
expect {
|
34
|
+
new_property_object.foo do
|
35
|
+
end
|
36
|
+
}.to raise_error(NoMethodError)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should not respond if params are given" do
|
40
|
+
expect { new_property_object.foo(42) }.to raise_error(NoMethodError)
|
41
|
+
expect { new_property_object.foo(42,43) }.to raise_error(NoMethodError)
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should return nil for unknown attribute" do
|
45
|
+
expect(new_property_object.foo).to eq(nil)
|
46
|
+
expect(new_property_object.bar).to eq(nil)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should return defaults" do
|
50
|
+
expect(new_property_object.theme).to eq('blue')
|
51
|
+
expect(new_property_object.view).to eq('monthly')
|
52
|
+
expect(new_property_object.filter).to eq(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should return defaults when using `try`" do
|
56
|
+
expect(new_property_object.try(:theme)).to eq('blue')
|
57
|
+
expect(new_property_object.try(:view)).to eq('monthly')
|
58
|
+
expect(new_property_object.try(:filter)).to eq(true)
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should store different objects to value hash" do
|
62
|
+
new_property_object.integer = 42
|
63
|
+
new_property_object.float = 1.234
|
64
|
+
new_property_object.string = 'Hello, World!'
|
65
|
+
new_property_object.array = [ 1,2,3 ]
|
66
|
+
new_property_object.symbol = :foo
|
67
|
+
|
68
|
+
expect(new_property_object.value).to eq('integer' => 42,
|
69
|
+
'float' => 1.234,
|
70
|
+
'string' => 'Hello, World!',
|
71
|
+
'array' => [ 1,2,3 ],
|
72
|
+
'symbol' => :foo)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should set and return attributes" do
|
76
|
+
new_property_object.theme = 'pink'
|
77
|
+
new_property_object.foo = 42
|
78
|
+
new_property_object.bar = 'hello'
|
79
|
+
|
80
|
+
expect(new_property_object.theme).to eq('pink')
|
81
|
+
expect(new_property_object.foo).to eq(42)
|
82
|
+
expect(new_property_object.bar).to eq('hello')
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should set dirty trackers on change" do
|
86
|
+
new_property_object.theme = 'pink'
|
87
|
+
expect(new_property_object).to be_value_changed
|
88
|
+
expect(new_property_object).to be_changed
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
context "on saved properties" do
|
93
|
+
it "should not set dirty trackers on property same value" do
|
94
|
+
saved_property_object.theme = 'pink'
|
95
|
+
expect(saved_property_object).not_to be_value_changed
|
96
|
+
expect(saved_property_object).not_to be_changed
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should delete key on assigning nil" do
|
100
|
+
saved_property_object.theme = nil
|
101
|
+
expect(saved_property_object.value).to eq({ 'filter' => false })
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "update_attributes" do
|
107
|
+
it 'should save' do
|
108
|
+
expect(new_property_object.update_attributes(:foo => 42, :bar => 'string')).to be_truthy
|
109
|
+
new_property_object.reload
|
110
|
+
|
111
|
+
expect(new_property_object.foo).to eq(42)
|
112
|
+
expect(new_property_object.bar).to eq('string')
|
113
|
+
expect(new_property_object).not_to be_new_record
|
114
|
+
expect(new_property_object.id).not_to be_zero
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'should not save blank hash' do
|
118
|
+
expect(new_property_object.update_attributes({})).to be_truthy
|
119
|
+
end
|
120
|
+
|
121
|
+
if RailsProperties.can_protect_attributes?
|
122
|
+
it 'should not allow changing protected attributes' do
|
123
|
+
new_property_object.update_attributes!(:var => 'calendar', :foo => 42)
|
124
|
+
|
125
|
+
expect(new_property_object.var).to eq('dashboard')
|
126
|
+
expect(new_property_object.foo).to eq(42)
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
describe "save" do
|
132
|
+
it "should save" do
|
133
|
+
new_property_object.foo = 42
|
134
|
+
new_property_object.bar = 'string'
|
135
|
+
expect(new_property_object.save).to be_truthy
|
136
|
+
new_property_object.reload
|
137
|
+
|
138
|
+
expect(new_property_object.foo).to eq(42)
|
139
|
+
expect(new_property_object.bar).to eq('string')
|
140
|
+
expect(new_property_object).not_to be_new_record
|
141
|
+
expect(new_property_object.id).not_to be_zero
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
describe "validation" do
|
146
|
+
it "should not validate for unknown var" do
|
147
|
+
new_property_object.var = "unknown-var"
|
148
|
+
|
149
|
+
expect(new_property_object).not_to be_valid
|
150
|
+
expect(new_property_object.errors[:var]).to be_present
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'Queries performed' do
|
4
|
+
context 'New record' do
|
5
|
+
let!(:user) { User.new :name => 'Mr. Pink' }
|
6
|
+
|
7
|
+
it 'should be saved by one SQL query' do
|
8
|
+
expect {
|
9
|
+
user.save!
|
10
|
+
}.to perform_queries(1)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'should be saved with properties for one key by two SQL queries' do
|
14
|
+
expect {
|
15
|
+
user.properties(:dashboard).foo = 42
|
16
|
+
user.properties(:dashboard).bar = 'string'
|
17
|
+
user.save!
|
18
|
+
}.to perform_queries(2)
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'should be saved with properties for two keys by three SQL queries' do
|
22
|
+
expect {
|
23
|
+
user.properties(:dashboard).foo = 42
|
24
|
+
user.properties(:dashboard).bar = 'string'
|
25
|
+
user.properties(:calendar).bar = 'string'
|
26
|
+
user.save!
|
27
|
+
}.to perform_queries(3)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context 'Existing record without properties' do
|
32
|
+
let!(:user) { User.create! :name => 'Mr. Pink' }
|
33
|
+
|
34
|
+
it 'should be saved without SQL queries' do
|
35
|
+
expect {
|
36
|
+
user.save!
|
37
|
+
}.to perform_queries(0)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'should be saved with properties for one key by two SQL queries' do
|
41
|
+
expect {
|
42
|
+
user.properties(:dashboard).foo = 42
|
43
|
+
user.properties(:dashboard).bar = 'string'
|
44
|
+
user.save!
|
45
|
+
}.to perform_queries(2)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'should be saved with properties for two keys by three SQL queries' do
|
49
|
+
expect {
|
50
|
+
user.properties(:dashboard).foo = 42
|
51
|
+
user.properties(:dashboard).bar = 'string'
|
52
|
+
user.properties(:calendar).bar = 'string'
|
53
|
+
user.save!
|
54
|
+
}.to perform_queries(3)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context 'Existing record with properties' do
|
59
|
+
let!(:user) do
|
60
|
+
User.create! :name => 'Mr. Pink' do |user|
|
61
|
+
user.properties(:dashboard).theme = 'pink'
|
62
|
+
user.properties(:calendar).scope = 'all'
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'should be saved without SQL queries' do
|
67
|
+
expect {
|
68
|
+
user.save!
|
69
|
+
}.to perform_queries(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'should be saved with properties for one key by one SQL queries' do
|
73
|
+
expect {
|
74
|
+
user.properties(:dashboard).foo = 42
|
75
|
+
user.properties(:dashboard).bar = 'string'
|
76
|
+
user.save!
|
77
|
+
}.to perform_queries(1)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'should be saved with properties for two keys by two SQL queries' do
|
81
|
+
expect {
|
82
|
+
user.properties(:dashboard).foo = 42
|
83
|
+
user.properties(:dashboard).bar = 'string'
|
84
|
+
user.properties(:calendar).bar = 'string'
|
85
|
+
user.save!
|
86
|
+
}.to perform_queries(2)
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'should be destroyed by two SQL queries' do
|
90
|
+
expect {
|
91
|
+
user.destroy
|
92
|
+
}.to perform_queries(2)
|
93
|
+
end
|
94
|
+
|
95
|
+
it "should update properties by one SQL query" do
|
96
|
+
expect {
|
97
|
+
user.properties(:dashboard).update_attributes! :foo => 'bar'
|
98
|
+
}.to perform_queries(1)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
data/spec/scopes_spec.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'scopes' do
|
4
|
+
let!(:user1) { User.create! :name => 'Mr. White' do |user| user.properties(:dashboard).theme = 'white' end }
|
5
|
+
let!(:user2) { User.create! :name => 'Mr. Blue' }
|
6
|
+
|
7
|
+
it "should find objects with existing properties" do
|
8
|
+
expect(User.with_properties).to eq([user1])
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should find objects with properties for key" do
|
12
|
+
expect(User.with_properties_for(:dashboard)).to eq([user1])
|
13
|
+
expect(User.with_properties_for(:foo)).to eq([])
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should records without properties" do
|
17
|
+
expect(User.without_properties).to eq([user2])
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should records without properties for key" do
|
21
|
+
expect(User.without_properties_for(:foo)).to eq([user1, user2])
|
22
|
+
expect(User.without_properties_for(:dashboard)).to eq([user2])
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should require symbol as key" do
|
26
|
+
[ nil, "string", 42 ].each do |invalid_key|
|
27
|
+
expect { User.without_properties_for(invalid_key) }.to raise_error(ArgumentError)
|
28
|
+
expect { User.with_properties_for(invalid_key) }.to raise_error(ArgumentError)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Serialization" do
|
4
|
+
let!(:user) do
|
5
|
+
User.create! :name => 'Mr. White' do |user|
|
6
|
+
user.properties(:dashboard).theme = 'white'
|
7
|
+
user.properties(:calendar).scope = 'all'
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe 'created properties' do
|
12
|
+
it 'should be serialized' do
|
13
|
+
user.reload
|
14
|
+
|
15
|
+
dashboard_properties = user.property_objects.where(:var => 'dashboard').first
|
16
|
+
calendar_properties = user.property_objects.where(:var => 'calendar').first
|
17
|
+
|
18
|
+
expect(dashboard_properties.var).to eq('dashboard')
|
19
|
+
expect(dashboard_properties.value).to eq({'theme' => 'white'})
|
20
|
+
|
21
|
+
expect(calendar_properties.var).to eq('calendar')
|
22
|
+
expect(calendar_properties.value).to eq({'scope' => 'all'})
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe 'updated properties' do
|
27
|
+
it 'should be serialized' do
|
28
|
+
user.properties(:dashboard).update_attributes! :smart => true
|
29
|
+
|
30
|
+
dashboard_properties = user.property_objects.where(:var => 'dashboard').first
|
31
|
+
calendar_properties = user.property_objects.where(:var => 'calendar').first
|
32
|
+
|
33
|
+
expect(dashboard_properties.var).to eq('dashboard')
|
34
|
+
expect(dashboard_properties.value).to eq({'theme' => 'white', 'smart' => true})
|
35
|
+
|
36
|
+
expect(calendar_properties.var).to eq('calendar')
|
37
|
+
expect(calendar_properties.value).to eq({'scope' => 'all'})
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'coveralls'
|
3
|
+
|
4
|
+
SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new([
|
5
|
+
SimpleCov::Formatter::HTMLFormatter,
|
6
|
+
Coveralls::SimpleCov::Formatter
|
7
|
+
])
|
8
|
+
SimpleCov.start do
|
9
|
+
add_filter '/spec/'
|
10
|
+
end
|
11
|
+
|
12
|
+
# Requires supporting ruby files with custom matchers and macros, etc,
|
13
|
+
# in spec/support/ and its subdirectories.
|
14
|
+
Dir[File.expand_path(File.join(File.dirname(__FILE__),'support','**','*.rb'))].each {|f| require f}
|
15
|
+
|
16
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
17
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
18
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
19
|
+
# loaded once.
|
20
|
+
#
|
21
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
22
|
+
RSpec.configure do |config|
|
23
|
+
# Run specs in random order to surface order dependencies. If you find an
|
24
|
+
# order dependency and want to debug it, you can fix the order by providing
|
25
|
+
# the seed, which is printed after each run.
|
26
|
+
# --seed 1234
|
27
|
+
# config.order = 'random'
|
28
|
+
|
29
|
+
config.before(:each) do
|
30
|
+
clear_db
|
31
|
+
end
|
32
|
+
|
33
|
+
config.after :suite do
|
34
|
+
RailsPropertiesMigration.migrate(:down)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'active_record'
|
39
|
+
require 'protected_attributes' if ENV['PROTECTED_ATTRIBUTES'] == 'true'
|
40
|
+
require 'rails-properties'
|
41
|
+
|
42
|
+
if I18n.respond_to?(:enforce_available_locales=)
|
43
|
+
I18n.enforce_available_locales = false
|
44
|
+
end
|
45
|
+
|
46
|
+
class User < ActiveRecord::Base
|
47
|
+
has_properties do |s|
|
48
|
+
s.key :dashboard, :defaults => { :theme => 'blue', :view => 'monthly', :filter => true }
|
49
|
+
s.key :calendar, :defaults => { :scope => 'company'}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class GuestUser < User
|
54
|
+
has_properties do |s|
|
55
|
+
s.key :dashboard, :defaults => { :theme => 'red', :view => 'monthly', :filter => true }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
class Account < ActiveRecord::Base
|
60
|
+
has_properties :portal
|
61
|
+
end
|
62
|
+
|
63
|
+
class Project < ActiveRecord::Base
|
64
|
+
has_properties :info, :class_name => 'ProjectPropertyObject'
|
65
|
+
end
|
66
|
+
|
67
|
+
class ProjectPropertyObject < RailsProperties::PropertyObject
|
68
|
+
validate do
|
69
|
+
unless self.owner_name.present? && self.owner_name.is_a?(String)
|
70
|
+
errors.add(:base, "Owner name is missing")
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def setup_db
|
76
|
+
ActiveRecord::Base.configurations = YAML.load_file(File.dirname(__FILE__) + '/database.yml')
|
77
|
+
ActiveRecord::Base.establish_connection(:sqlite)
|
78
|
+
ActiveRecord::Migration.verbose = false
|
79
|
+
|
80
|
+
print "Testing with ActiveRecord #{ActiveRecord::VERSION::STRING}"
|
81
|
+
if ActiveRecord::VERSION::MAJOR == 4
|
82
|
+
print " #{defined?(ProtectedAttributes) ? 'with' : 'without'} gem `protected_attributes`"
|
83
|
+
end
|
84
|
+
puts
|
85
|
+
|
86
|
+
require File.expand_path('../../lib/generators/rails_properties/migration/templates/migration.rb', __FILE__)
|
87
|
+
RailsPropertiesMigration.migrate(:up)
|
88
|
+
|
89
|
+
ActiveRecord::Schema.define(:version => 1) do
|
90
|
+
create_table :users do |t|
|
91
|
+
t.string :type
|
92
|
+
t.string :name
|
93
|
+
end
|
94
|
+
|
95
|
+
create_table :accounts do |t|
|
96
|
+
t.string :subdomain
|
97
|
+
end
|
98
|
+
|
99
|
+
create_table :projects do |t|
|
100
|
+
t.string :name
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def clear_db
|
106
|
+
User.delete_all
|
107
|
+
Account.delete_all
|
108
|
+
RailsProperties::PropertyObject.delete_all
|
109
|
+
end
|
110
|
+
|
111
|
+
setup_db
|
@@ -0,0 +1,22 @@
|
|
1
|
+
RSpec::Matchers.define :perform_queries do |expected|
|
2
|
+
match do |block|
|
3
|
+
query_count(&block) == expected
|
4
|
+
end
|
5
|
+
|
6
|
+
failure_message do |actual|
|
7
|
+
"Expected to run #{expected} queries, got #{@counter.query_count}"
|
8
|
+
end
|
9
|
+
|
10
|
+
def query_count(&block)
|
11
|
+
@counter = ActiveRecord::QueryCounter.new
|
12
|
+
ActiveSupport::Notifications.subscribe('sql.active_record', @counter.to_proc)
|
13
|
+
yield
|
14
|
+
ActiveSupport::Notifications.unsubscribe(@counter.to_proc)
|
15
|
+
|
16
|
+
@counter.query_count
|
17
|
+
end
|
18
|
+
|
19
|
+
def supports_block_expectations?
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|