rails_core_extensions 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.hound.yml +2 -0
  4. data/.rspec +2 -0
  5. data/.ruby-style.yml +233 -0
  6. data/.travis.yml +14 -0
  7. data/Gemfile +2 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +62 -0
  10. data/Rakefile +12 -0
  11. data/gemfiles/rails3.gemfile +13 -0
  12. data/gemfiles/rails4.gemfile +14 -0
  13. data/lib/rails_core_extensions.rb +41 -0
  14. data/lib/rails_core_extensions/action_controller_sortable.rb +22 -0
  15. data/lib/rails_core_extensions/action_view_currency_extensions.rb +26 -0
  16. data/lib/rails_core_extensions/action_view_extensions.rb +62 -0
  17. data/lib/rails_core_extensions/action_view_has_many_extensions.rb +32 -0
  18. data/lib/rails_core_extensions/activatable.rb +38 -0
  19. data/lib/rails_core_extensions/active_model_extensions.rb +47 -0
  20. data/lib/rails_core_extensions/active_record_cache_all_attributes.rb +43 -0
  21. data/lib/rails_core_extensions/active_record_cloning.rb +81 -0
  22. data/lib/rails_core_extensions/active_record_extensions.rb +228 -0
  23. data/lib/rails_core_extensions/active_record_liquid_extensions.rb +32 -0
  24. data/lib/rails_core_extensions/active_support_concern.rb +134 -0
  25. data/lib/rails_core_extensions/breadcrumb.rb +170 -0
  26. data/lib/rails_core_extensions/caches_action_without_host.rb +17 -0
  27. data/lib/rails_core_extensions/concurrency.rb +152 -0
  28. data/lib/rails_core_extensions/position_initializer.rb +27 -0
  29. data/lib/rails_core_extensions/railtie.rb +7 -0
  30. data/lib/rails_core_extensions/sortable.rb +52 -0
  31. data/lib/rails_core_extensions/tasks/position_initializer.rake +12 -0
  32. data/lib/rails_core_extensions/time_with_zone.rb +16 -0
  33. data/lib/rails_core_extensions/version.rb +3 -0
  34. data/rails_core_extensions.gemspec +38 -0
  35. data/spec/action_controller_sortable_spec.rb +52 -0
  36. data/spec/action_view_extensions_spec.rb +25 -0
  37. data/spec/active_model_extensions_spec.rb +130 -0
  38. data/spec/active_record_extensions_spec.rb +126 -0
  39. data/spec/breadcrumb_spec.rb +85 -0
  40. data/spec/concurrency_spec.rb +110 -0
  41. data/spec/position_initializer_spec.rb +48 -0
  42. data/spec/schema.rb +17 -0
  43. data/spec/spec_helper.rb +31 -0
  44. data/spec/spec_helper_model_base.rb +37 -0
  45. data/spec/support/coverage_loader.rb +26 -0
  46. 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,7 @@
1
+ module RailsCoreExtensions
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load 'rails_core_extensions/tasks/position_initializer.rake'
5
+ end
6
+ end
7
+ 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,16 @@
1
+ module ActiveSupport
2
+ class TimeWithZone
3
+ def seconds_since
4
+ (Time.now - self).to_i
5
+ end
6
+
7
+ def minutes_since
8
+ seconds_since/60
9
+ end
10
+
11
+ def hours_since
12
+ minutes_since/60
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,3 @@
1
+ module RailsCoreExtensions
2
+ VERSION = '0.0.1'
3
+ 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