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.
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