rails_core_extensions 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.
- checksums.yaml +7 -0
- data/.gitignore +4 -0
- data/.hound.yml +2 -0
- data/.rspec +2 -0
- data/.ruby-style.yml +233 -0
- data/.travis.yml +14 -0
- data/Gemfile +2 -0
- data/LICENSE.txt +22 -0
- data/README.md +62 -0
- data/Rakefile +12 -0
- data/gemfiles/rails3.gemfile +13 -0
- data/gemfiles/rails4.gemfile +14 -0
- data/lib/rails_core_extensions.rb +41 -0
- data/lib/rails_core_extensions/action_controller_sortable.rb +22 -0
- data/lib/rails_core_extensions/action_view_currency_extensions.rb +26 -0
- data/lib/rails_core_extensions/action_view_extensions.rb +62 -0
- data/lib/rails_core_extensions/action_view_has_many_extensions.rb +32 -0
- data/lib/rails_core_extensions/activatable.rb +38 -0
- data/lib/rails_core_extensions/active_model_extensions.rb +47 -0
- data/lib/rails_core_extensions/active_record_cache_all_attributes.rb +43 -0
- data/lib/rails_core_extensions/active_record_cloning.rb +81 -0
- data/lib/rails_core_extensions/active_record_extensions.rb +228 -0
- data/lib/rails_core_extensions/active_record_liquid_extensions.rb +32 -0
- data/lib/rails_core_extensions/active_support_concern.rb +134 -0
- data/lib/rails_core_extensions/breadcrumb.rb +170 -0
- data/lib/rails_core_extensions/caches_action_without_host.rb +17 -0
- data/lib/rails_core_extensions/concurrency.rb +152 -0
- data/lib/rails_core_extensions/position_initializer.rb +27 -0
- data/lib/rails_core_extensions/railtie.rb +7 -0
- data/lib/rails_core_extensions/sortable.rb +52 -0
- data/lib/rails_core_extensions/tasks/position_initializer.rake +12 -0
- data/lib/rails_core_extensions/time_with_zone.rb +16 -0
- data/lib/rails_core_extensions/version.rb +3 -0
- data/rails_core_extensions.gemspec +38 -0
- data/spec/action_controller_sortable_spec.rb +52 -0
- data/spec/action_view_extensions_spec.rb +25 -0
- data/spec/active_model_extensions_spec.rb +130 -0
- data/spec/active_record_extensions_spec.rb +126 -0
- data/spec/breadcrumb_spec.rb +85 -0
- data/spec/concurrency_spec.rb +110 -0
- data/spec/position_initializer_spec.rb +48 -0
- data/spec/schema.rb +17 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/spec_helper_model_base.rb +37 -0
- data/spec/support/coverage_loader.rb +26 -0
- metadata +294 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
module RailsCoreExtensions
|
2
|
+
class PositionInitializer
|
3
|
+
def initialize(model_class, scope_name = nil, position_column = nil)
|
4
|
+
@model_class = model_class
|
5
|
+
@scope_name = scope_name
|
6
|
+
@position_column = position_column
|
7
|
+
@position_column ||= :position
|
8
|
+
end
|
9
|
+
|
10
|
+
def positionalize
|
11
|
+
groups.each do |objects|
|
12
|
+
objects.each.with_index do |object, index|
|
13
|
+
next if object.position == index + 1
|
14
|
+
scope = @model_class.where(id: object.id)
|
15
|
+
scope.update_all(@position_column => index + 1)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def groups
|
23
|
+
objects = @model_class.order(@position_column)
|
24
|
+
@scope_name ? objects.group_by(&@scope_name.to_sym).values : [objects]
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RailsCoreExtensions
|
2
|
+
class Sortable
|
3
|
+
# params is either:
|
4
|
+
# SIMPLE:
|
5
|
+
# model_body (where model is the model name)
|
6
|
+
# SCOPED:
|
7
|
+
# scope (name of the scope, e.g. category_id)
|
8
|
+
# category_id (or whatever the name of scope is)
|
9
|
+
# model_1_body (or whatever id of scope id)
|
10
|
+
def initialize(params, controller_name)
|
11
|
+
@params = params.symbolize_keys
|
12
|
+
@controller_name = controller_name
|
13
|
+
@klass = controller_name.classify.constantize
|
14
|
+
end
|
15
|
+
|
16
|
+
def sort
|
17
|
+
scope = @params[:scope].try(:to_sym)
|
18
|
+
|
19
|
+
param_key = @controller_name.singularize
|
20
|
+
param_key += "_#{@params[scope]}" if scope
|
21
|
+
|
22
|
+
params_collection = @params["#{param_key}_body".to_sym]
|
23
|
+
|
24
|
+
if params_collection.blank?
|
25
|
+
name = "#{scope.to_s.gsub('_id', '')}_#{@params[scope]}_body".to_sym
|
26
|
+
params_collection = @params[name]
|
27
|
+
end
|
28
|
+
|
29
|
+
collection = @klass.reorder(:position)
|
30
|
+
collection = collection.where(@params.slice(scope.to_sym)) if scope
|
31
|
+
|
32
|
+
sort_collection(collection, params_collection.map(&:to_i))
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def sort_collection(collection_old, collection_new_ids)
|
38
|
+
@klass.transaction do
|
39
|
+
collection_old.each.with_index do |object, index|
|
40
|
+
next if object.id == collection_new_ids[index]
|
41
|
+
|
42
|
+
new_position = collection_new_ids.index(object.id) + 1
|
43
|
+
update(object, new_position)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def update(object, new_position)
|
49
|
+
@klass.where(id: object.id).limit(1).update_all(position: new_position)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
namespace :fix do
|
2
|
+
desc 'Fix acts as list positions if they ever get out of sync for a model'
|
3
|
+
task :acts_as_list_positions => :environment do
|
4
|
+
raise ArgumentError, 'You must specify model with MODEL environment variable' if ENV['MODEL'].nil?
|
5
|
+
|
6
|
+
model_class = ENV['MODEL'].camelize.constantize
|
7
|
+
scope_name = ENV['SCOPE'].try(:to_sym)
|
8
|
+
position_column = ENV['POSITION'].try(:to_sym)
|
9
|
+
|
10
|
+
PositionInitializer.positionalize(model_class, scope_name, position_column)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'rails_core_extensions/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'rails_core_extensions'
|
8
|
+
spec.version = RailsCoreExtensions::VERSION
|
9
|
+
spec.authors = ['Michael Noack', 'Allesandro Berardi']
|
10
|
+
spec.email = ['support@travellink.com.au']
|
11
|
+
spec.description = %q{Set of extensions to core rails libraries.}
|
12
|
+
spec.summary = %q{Set of extensions to core rails libraries.}
|
13
|
+
spec.homepage = 'http://github.com/sealink/rails_core_extensions'
|
14
|
+
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files`.split($/)
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_dependency 'activerecord', ['>= 2.3.0', '< 5.0.0']
|
23
|
+
spec.add_dependency 'actionpack', ['>= 2.3.0', '< 5.0.0']
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler', '~> 1.3'
|
26
|
+
spec.add_development_dependency 'rake'
|
27
|
+
spec.add_development_dependency 'rspec'
|
28
|
+
spec.add_development_dependency 'activerecord-nulldb-adapter'
|
29
|
+
spec.add_development_dependency 'simplecov'
|
30
|
+
spec.add_development_dependency 'simplecov-rcov'
|
31
|
+
spec.add_development_dependency 'coveralls'
|
32
|
+
spec.add_development_dependency 'sqlite3'
|
33
|
+
spec.add_development_dependency 'travis'
|
34
|
+
|
35
|
+
# breadcrumbs optional dependencies
|
36
|
+
spec.add_development_dependency 'make_resourceful'
|
37
|
+
spec.add_development_dependency 'inherited_resources'
|
38
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
require 'rails_core_extensions/sortable'
|
4
|
+
|
5
|
+
connect_to_sqlite
|
6
|
+
|
7
|
+
describe RailsCoreExtensions::Sortable do
|
8
|
+
before do
|
9
|
+
Model = Class.new(ActiveRecord::Base) do
|
10
|
+
default_scope -> { order(:name) }
|
11
|
+
end
|
12
|
+
@one = Model.create!(name: 'One', position: 1, category_id: 1)
|
13
|
+
@two = Model.create!(name: 'Two', position: 2, category_id: 1)
|
14
|
+
@thr = Model.create!(name: 'Thr', position: 3, category_id: 2)
|
15
|
+
end
|
16
|
+
|
17
|
+
after do
|
18
|
+
Model.delete_all
|
19
|
+
Object.send(:remove_const, 'Model')
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:params) { { model_body: [@one.id, @thr.id, @two.id] } }
|
23
|
+
subject { RailsCoreExtensions::Sortable.new(params, 'models') }
|
24
|
+
|
25
|
+
describe 'when unscoped' do
|
26
|
+
let(:scope) { Model.reorder(:position) }
|
27
|
+
specify { expect(scope.pluck(:name)).to eq %w(One Two Thr) }
|
28
|
+
it 'should correctly sort' do
|
29
|
+
subject.sort
|
30
|
+
expect(scope.pluck(:name)).to eq %w(One Thr Two)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe 'when scoped' do
|
35
|
+
let(:scope) { Model.where(category_id: 1).reorder(:position) }
|
36
|
+
specify { expect(scope.pluck(:name)).to eq %w(One Two) }
|
37
|
+
|
38
|
+
let(:params) { { category_id: 1, scope: :category_id, model_1_body: [@two.id, @one.id] } }
|
39
|
+
it 'should correctly sort' do
|
40
|
+
subject.sort
|
41
|
+
expect(scope.pluck(:name)).to eq %w(Two One)
|
42
|
+
end
|
43
|
+
|
44
|
+
describe 'when params scoped differently' do
|
45
|
+
let(:params) { { category_id: 1, scope: :category_id, category_1_body: [@two.id, @one.id] } }
|
46
|
+
it 'should correctly sort' do
|
47
|
+
subject.sort
|
48
|
+
expect(scope.pluck(:name)).to eq %w(Two One)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'rails_core_extensions/action_view_extensions'
|
2
|
+
|
3
|
+
require 'action_view'
|
4
|
+
|
5
|
+
describe RailsCoreExtensions::ActionViewExtensions do
|
6
|
+
before do
|
7
|
+
class TestModel1
|
8
|
+
include ActionView::Helpers::FormTagHelper
|
9
|
+
include ActionView::Helpers::FormOptionsHelper
|
10
|
+
include RailsCoreExtensions::ActionViewExtensions
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
after { Object.send(:remove_const, 'TestModel1') }
|
15
|
+
|
16
|
+
subject { TestModel1.new }
|
17
|
+
|
18
|
+
context '#boolean_select_tag' do
|
19
|
+
it 'should generate and have selected element selected' do
|
20
|
+
expect(subject.boolean_select_tag('name', selected: '0')).to eq(
|
21
|
+
subject.select_tag('name', subject.options_for_select([['Yes', '1'], ['No', '0']], selected: '0'))
|
22
|
+
)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
begin
|
2
|
+
require 'active_model'
|
3
|
+
require 'rails_core_extensions/active_model_extensions'
|
4
|
+
require 'spec_helper_model_base'
|
5
|
+
|
6
|
+
describe ActiveModelExtensions do
|
7
|
+
include NullDB::CustomNullifiedDatabase if defined?(NullDB)
|
8
|
+
|
9
|
+
before do
|
10
|
+
class ActiveModelExtensionsTestModel1 < ModelBase
|
11
|
+
include ActiveModelExtensions::Validations
|
12
|
+
|
13
|
+
attr_accessor :name
|
14
|
+
|
15
|
+
def initialize(options = {})
|
16
|
+
@name = options[:name]
|
17
|
+
end
|
18
|
+
|
19
|
+
validate :validate_required
|
20
|
+
|
21
|
+
def validate_required
|
22
|
+
CustomPresenceValidator.new(:attributes => lambda { ['name'] }).validate(self)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
after { Object.send(:remove_const, klass.name) }
|
28
|
+
|
29
|
+
let(:klass) { ActiveModelExtensionsTestModel1 }
|
30
|
+
|
31
|
+
context 'when model missing name' do
|
32
|
+
subject { klass.new }
|
33
|
+
it { should_not be_valid }
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when model complete' do
|
37
|
+
subject { klass.new(:name => 'Valid') }
|
38
|
+
it { should be_valid }
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ActiveModelExtensions do
|
43
|
+
include NullDB::CustomNullifiedDatabase if defined?(NullDB)
|
44
|
+
|
45
|
+
before do
|
46
|
+
class ActiveModelExtensionsTestModel2 < ModelBase
|
47
|
+
include ActiveModelExtensions::Validations
|
48
|
+
|
49
|
+
attr_accessor :name
|
50
|
+
|
51
|
+
def initialize(options = {})
|
52
|
+
@name = options[:name]
|
53
|
+
end
|
54
|
+
|
55
|
+
validate_presence_by_custom_rules lambda { ['name'] }
|
56
|
+
end
|
57
|
+
|
58
|
+
class ActiveModelExtensionsTestModel3 < ModelBase
|
59
|
+
include ActiveModelExtensions::Validations
|
60
|
+
|
61
|
+
attr_accessor :name, :phone, :mobile
|
62
|
+
|
63
|
+
def initialize(options = {})
|
64
|
+
@name = options[:name]
|
65
|
+
@phone = options[:phone]
|
66
|
+
@mobile = options[:mobile]
|
67
|
+
|
68
|
+
end
|
69
|
+
|
70
|
+
validate_presence_by_custom_rules lambda { ['name', 'phone or mobile'] }
|
71
|
+
end
|
72
|
+
|
73
|
+
class ActiveModelExtensionsTestModelNil < ModelBase
|
74
|
+
include ActiveModelExtensions::Validations
|
75
|
+
def initialize(options = {})
|
76
|
+
end
|
77
|
+
validate_presence_by_custom_rules lambda { nil }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
after { Object.send(:remove_const, klass.name) }
|
82
|
+
|
83
|
+
let(:klass) { ActiveModelExtensionsTestModel2 }
|
84
|
+
|
85
|
+
context 'when model missing name' do
|
86
|
+
subject { klass.new }
|
87
|
+
it { should_not be_valid }
|
88
|
+
end
|
89
|
+
|
90
|
+
context 'when model complete' do
|
91
|
+
subject { klass.new(:name => 'Valid') }
|
92
|
+
it { should be_valid }
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'with or case' do
|
96
|
+
let(:klass) { ActiveModelExtensionsTestModel3 }
|
97
|
+
|
98
|
+
context 'when model has none of conditions' do
|
99
|
+
subject { klass.new(:name => 'Valid') }
|
100
|
+
it { should_not be_valid }
|
101
|
+
end
|
102
|
+
|
103
|
+
context 'when model has first of conditions' do
|
104
|
+
subject { klass.new(:name => 'Valid', :phone => '555-1234') }
|
105
|
+
it { should be_valid }
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'when model has second of conditions' do
|
109
|
+
subject { klass.new(:name => 'Valid', :mobile => '555-1234') }
|
110
|
+
it { should be_valid }
|
111
|
+
end
|
112
|
+
|
113
|
+
context 'when model has all conditions' do
|
114
|
+
subject { klass.new(:name => 'Valid', :phone => '555-1234', :mobile => '555-4321') }
|
115
|
+
it { should be_valid }
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
context 'with nil requirements' do
|
120
|
+
let(:klass) { ActiveModelExtensionsTestModelNil }
|
121
|
+
|
122
|
+
context 'when model valid' do
|
123
|
+
subject { klass.new(:name => 'Valid') }
|
124
|
+
it { should be_valid }
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
rescue LoadError # Spec doesn't run for rails 2
|
130
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
connect_to_sqlite
|
4
|
+
|
5
|
+
describe "optional_fields" do
|
6
|
+
before do
|
7
|
+
Model = Class.new(ActiveRecord::Base) do
|
8
|
+
optional_fields :name, :age, -> { [:age] }
|
9
|
+
end
|
10
|
+
end
|
11
|
+
after { Object.send(:remove_const, 'Model') }
|
12
|
+
|
13
|
+
it 'should know what fields are optional' do
|
14
|
+
expect(Model).to be_age_enabled
|
15
|
+
expect(Model).to_not be_name_enabled
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'should be able to change optional fields' do
|
19
|
+
Model.enabled_fields = [:age, :name]
|
20
|
+
expect(Model).to be_name_enabled
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe "ActiveRecord::Base" do
|
25
|
+
before(:all) { Model = Class.new(ActiveRecord::Base) }
|
26
|
+
after (:all) { Object.send(:remove_const, 'Model') }
|
27
|
+
|
28
|
+
before do
|
29
|
+
@mock_model = double("mock model")
|
30
|
+
end
|
31
|
+
|
32
|
+
it "should create a new record if new_or_update! is passed a hash without an :id" do
|
33
|
+
attributes = {:fake_column => 'nothing really'}
|
34
|
+
expect(Model).to receive(:new).with(attributes)
|
35
|
+
Model.new_or_update!(attributes)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should update record if new_or_update! is passed hash with :id" do
|
39
|
+
attributes = {:fake_column => 'nothing really', :id => 1}
|
40
|
+
expect(Model).to receive(:find) { @mock_model }
|
41
|
+
expect(@mock_model).to receive(:update_attributes!)
|
42
|
+
Model.new_or_update!(attributes)
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
describe RailsCoreExtensions::ActionControllerSortable do
|
48
|
+
class NormalController < ActionController::Base
|
49
|
+
end
|
50
|
+
|
51
|
+
class SortableController < ActionController::Base
|
52
|
+
sortable
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'should sort' do
|
56
|
+
# map(&:to_sym) for ruby 1.8 compatibility
|
57
|
+
expect(NormalController.new.methods.map(&:to_sym)).to_not include(:sort)
|
58
|
+
expect(SortableController.new.methods.map(&:to_sym)).to include(:sort)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe ActiveRecordExtensions do
|
63
|
+
class Parent < ActiveRecord::Base
|
64
|
+
has_many :children
|
65
|
+
def transfer_children_from(old_parent)
|
66
|
+
transfer_records(Child, [old_parent])
|
67
|
+
end
|
68
|
+
end
|
69
|
+
class Child < ActiveRecord::Base
|
70
|
+
belongs_to :parent
|
71
|
+
end
|
72
|
+
|
73
|
+
let(:old) { Parent.create! }
|
74
|
+
let(:new) { Parent.create! }
|
75
|
+
|
76
|
+
before do
|
77
|
+
new.children.create!
|
78
|
+
old.children.create!
|
79
|
+
end
|
80
|
+
|
81
|
+
after do
|
82
|
+
Parent.delete_all
|
83
|
+
Child.delete_all
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'should transfer records' do
|
87
|
+
expect(new.children.size).to eq 1
|
88
|
+
expect(old.children.size).to eq 1
|
89
|
+
new.transfer_children_from(old)
|
90
|
+
expect(new.reload.children.size).to eq 2
|
91
|
+
expect(old.reload.children.size).to eq 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
describe ActiveRecordExtensions do
|
96
|
+
before(:all) do
|
97
|
+
Model = Class.new(ActiveRecord::Base) do
|
98
|
+
cache_all_attributes :by => 'name'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
after (:all) { Object.send(:remove_const, 'Model') }
|
102
|
+
|
103
|
+
before do
|
104
|
+
allow(Model).to receive(:cache) { ActiveSupport::Cache::MemoryStore.new }
|
105
|
+
allow(Model).to receive(:should_cache?) { true }
|
106
|
+
end
|
107
|
+
|
108
|
+
after do
|
109
|
+
Model.delete_all
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'should cache all attributes' do
|
113
|
+
@first = Model.create!(:name => 'First')
|
114
|
+
@second = Model.create!(:name => 'Second')
|
115
|
+
|
116
|
+
expected = {'First' => @first.attributes, 'Second' => @second.attributes}
|
117
|
+
|
118
|
+
# Test underlying generate attributes hash method works
|
119
|
+
expect(Model.generate_attributes_hash).to eq expected
|
120
|
+
expect(Model.attribute_cache).to eq expected
|
121
|
+
|
122
|
+
# Test after save/destroy it updates
|
123
|
+
@first.destroy
|
124
|
+
expect(Model.attribute_cache).to eq 'Second' => @second.attributes
|
125
|
+
end
|
126
|
+
end
|