set_as_primary 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -3
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -3
- data/README.md +55 -15
- data/bin/console +2 -0
- data/bin/setup +2 -0
- data/lib/generators/set_as_primary/set_as_primary_generator.rb +37 -4
- data/lib/generators/set_as_primary/templates/migration.rb +5 -1
- data/lib/set_as_primary.rb +34 -25
- data/lib/set_as_primary/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 06f7d231ec1dc1effe46cd6b516c12bcffdf98b3feadde4d0f0a5b8e193f2047
|
4
|
+
data.tar.gz: 6f21389fa3d8aa3c8a2bc512dfb26874b84c9b3cfd060afc3b9c7ba18cac9781
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 625e12dd80c2c13f78d5d557504db3cfbce52124846c64d735ed71a16f4f0feb1b41f69a8da8a3c20e58ecc29e3ebff5fdd0cb229dc9c1f78495fb6ff46b56fc
|
7
|
+
data.tar.gz: b706248616ceacf293a0b0ff8c9769226ae5e088c026cc655b3937159c5606132b8bb37665e425bac213c2c95e93da43a8132631e50822f94a19f8d6a4030446
|
data/.rubocop.yml
CHANGED
@@ -81,7 +81,7 @@ Layout/EmptyLinesAroundModuleBody:
|
|
81
81
|
Style/HashSyntax:
|
82
82
|
Enabled: true
|
83
83
|
|
84
|
-
Layout/
|
84
|
+
Layout/FirstArgumentIndentation:
|
85
85
|
Enabled: true
|
86
86
|
|
87
87
|
# Method definitions after `private` or `protected` isolated calls need one
|
@@ -170,7 +170,7 @@ Layout/Tab:
|
|
170
170
|
Enabled: true
|
171
171
|
|
172
172
|
# Blank lines should not have any spaces.
|
173
|
-
Layout/
|
173
|
+
Layout/TrailingEmptyLines:
|
174
174
|
Enabled: true
|
175
175
|
|
176
176
|
# No trailing whitespace.
|
@@ -197,7 +197,7 @@ Lint/RequireParentheses:
|
|
197
197
|
Lint/ShadowingOuterLocalVariable:
|
198
198
|
Enabled: true
|
199
199
|
|
200
|
-
Lint/
|
200
|
+
Lint/RedundantStringCoercion:
|
201
201
|
Enabled: true
|
202
202
|
|
203
203
|
Lint/UriEscapeUnescape:
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## 0.1.2 (2019-02-02)
|
2
|
+
- Supported unique partial index.
|
3
|
+
- Changed generator definition. Now it also accepts `flag_name`, `owner_key`.
|
4
|
+
- Handled `force_primary` in `after_create` callback.
|
5
|
+
- Refactored the code.
|
6
|
+
|
1
7
|
## 0.1.1 (2019-12-29)
|
2
8
|
|
3
9
|
- Added support for single model with no association.
|
data/Gemfile
CHANGED
@@ -8,7 +8,7 @@ gemspec
|
|
8
8
|
gem "activerecord", "~> 6.0.0"
|
9
9
|
|
10
10
|
group :development do
|
11
|
-
gem "rubocop", ">= 0.47"
|
12
|
-
gem "rubocop-performance"
|
13
|
-
gem "rubocop-rails"
|
11
|
+
gem "rubocop", ">= 0.47"
|
12
|
+
gem "rubocop-performance"
|
13
|
+
gem "rubocop-rails"
|
14
14
|
end
|
data/README.md
CHANGED
@@ -1,12 +1,19 @@
|
|
1
1
|
# SetAsPrimary
|
2
2
|
|
3
|
-
The simplest way to handle the primary or default flag
|
3
|
+
The simplest way to handle the primary or default flag to
|
4
4
|
your Rails models.
|
5
5
|
|
6
|
-
|
6
|
+
Features:
|
7
|
+
|
8
|
+
* Supports single model (without association), model with (`belongs_to`) association, and even polymorphic associations
|
9
|
+
* Force primary
|
10
|
+
* Supports PostgreSQL's unique partial index (constraint)
|
11
|
+
* Supports PostgreSQL, MySQL, and SQLite
|
12
|
+
|
13
|
+
|
7
14
|
|
8
15
|
[Demo Rails application](https://cryptic-lake-90495.herokuapp.com/) |
|
9
|
-
[Demo Rails application
|
16
|
+
[Source code of Demo Rails application](https://github.com/mechanicles/set_as_primary_rails_app)
|
10
17
|
|
11
18
|
[![Build Status](https://travis-ci.org/mechanicles/set_as_primary.svg?branch=master)](https://travis-ci.org/mechanicles/set_as_primary)
|
12
19
|
|
@@ -33,7 +40,7 @@ Address, etc., which belong to the User/Person model or polymorphic model. There
|
|
33
40
|
you might need to set a primary email address, primary phone number, or default
|
34
41
|
address for a user, and this gem helps you to do that.
|
35
42
|
|
36
|
-
It also supports single model with no association
|
43
|
+
It also supports a single model with no association context.
|
37
44
|
|
38
45
|
Examples:
|
39
46
|
|
@@ -57,20 +64,20 @@ class Address < ApplicationRecord
|
|
57
64
|
set_as_primary :primary, owner_key: :owner
|
58
65
|
end
|
59
66
|
|
60
|
-
# Single model with no owner/association
|
67
|
+
# Single model with no owner/association context.
|
61
68
|
class Post < ApplicationRecord
|
62
69
|
include SetAsPrimary
|
63
70
|
|
64
|
-
set_as_primary
|
71
|
+
set_as_primary :primary
|
65
72
|
end
|
66
73
|
```
|
67
74
|
|
68
75
|
You need to include `SetAsPrimary` module in your model where you want to handle the primary flag.
|
69
76
|
Then to `set_as_primary` class helper method, pass your primary flag attribute. You might need to pass
|
70
|
-
association key `owner_key` if you wan to consider owner
|
77
|
+
association key `owner_key` if you wan to consider owner (association) context.
|
71
78
|
|
72
|
-
**
|
73
|
-
make sure that flag should be present in the table and should be a boolean
|
79
|
+
**Note:** Default primary flag attribute is `primary`, and you can use another one too like `default` but
|
80
|
+
make sure that flag should be present in the table and should be a boolean data type column.
|
74
81
|
|
75
82
|
#### Migration
|
76
83
|
|
@@ -78,20 +85,55 @@ If your table does not have the primary flag column, then you can add it by runn
|
|
78
85
|
following command in your rails project:
|
79
86
|
|
80
87
|
```ssh
|
81
|
-
rails generate set_as_primary your_table_name
|
88
|
+
rails generate set_as_primary your_table_name flag_name
|
89
|
+
```
|
90
|
+
|
91
|
+
Example:
|
92
|
+
|
93
|
+
If you want to add a `primary` column to your `posts` table, then you can run command like this:
|
94
|
+
|
95
|
+
```shell
|
96
|
+
rails generate set_as_primary posts primary
|
97
|
+
```
|
98
|
+
|
99
|
+
Then migration gets created like this:
|
100
|
+
|
101
|
+
```ruby
|
102
|
+
class AddPrimaryColumnToPosts < ActiveRecord::Migration[6.0]
|
103
|
+
def change
|
104
|
+
add_column :posts, :primary, :boolean, default: false, null: false
|
105
|
+
# NOTE: Please uncomment following line if you want only one 'true' (constraint) in the table.
|
106
|
+
# add_index :posts, :primary, unique: true, where: "(posts.primary IS TRUE)"
|
107
|
+
end
|
108
|
+
end
|
109
|
+
```
|
110
|
+
|
111
|
+
If you want to create a primary column to `email_addresses` table, then you can run command like this:
|
112
|
+
|
113
|
+
```shell
|
114
|
+
rails generate set_as_primary email_addresses primary user
|
82
115
|
```
|
83
116
|
|
84
|
-
|
85
|
-
migration like this:
|
117
|
+
Then it creates migration like this:
|
86
118
|
|
87
119
|
```ruby
|
88
120
|
class AddPrimaryColumnToEmailAddresses < ActiveRecord::Migration[6.0]
|
89
121
|
def change
|
90
122
|
add_column :email_addresses, :primary, :boolean, default: false, null: false
|
123
|
+
# NOTE: Please uncomment following line if you want only one 'true' (constraint) in the table.
|
124
|
+
# add_index :email_addresses, %i[user_id, primary], unique: true, where: "(email_addresses.primary IS TRUE)"
|
91
125
|
end
|
92
126
|
end
|
93
127
|
```
|
94
|
-
|
128
|
+
You might have seen extra commented lines there. These lines are there for handling the unique constraint. Currently, these lines get created only for `PostgreSQL` adapter as it supports partial index.
|
129
|
+
|
130
|
+
Please note that here we have passed an extra option `user` in the command that is nothing but the owner/association name. This extra option helps to handle the unique partial index.
|
131
|
+
|
132
|
+
**Note:** Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+. But I also found that SQLite gives an error so currently this gem only supports PostgreSQL's unique partial index constraint.
|
133
|
+
|
134
|
+
**Even if we don't have constraint (only one 'true' constraint in the table), this gem takes care of it so don't worry about the constraint.**
|
135
|
+
|
136
|
+
Once migration file gets created, don't forget to run `rails db:migrate` to create an actual column in the table.
|
95
137
|
|
96
138
|
#### force_primary
|
97
139
|
|
@@ -114,8 +156,6 @@ After checking out the repo, run `bin/setup` to install dependencies. Then, run
|
|
114
156
|
`rake test` to run the tests. You can also run `bin/console` for an interactive
|
115
157
|
prompt that will allow you to experiment.
|
116
158
|
|
117
|
-
To install this gem onto your local machine, run `bundle exec rake install`.
|
118
|
-
|
119
159
|
## Contributing
|
120
160
|
|
121
161
|
Bug reports and pull requests are welcome on GitHub at
|
data/bin/console
CHANGED
data/bin/setup
CHANGED
@@ -6,21 +6,54 @@ module SetAsPrimary
|
|
6
6
|
module Generators
|
7
7
|
class SetAsPrimaryGenerator < Rails::Generators::Base
|
8
8
|
include Rails::Generators::Migration
|
9
|
-
|
10
9
|
source_root File.expand_path("templates", __dir__)
|
11
|
-
argument :table_name, type: :string
|
12
10
|
|
13
|
-
desc "Adds a boolean column
|
11
|
+
desc "Adds a user defined boolean column to the given table."
|
12
|
+
|
13
|
+
argument :table_name, type: :string
|
14
|
+
argument :flag_name, type: :string
|
15
|
+
argument :owner_key, type: :string, default: ""
|
14
16
|
|
15
17
|
def copy_migration
|
16
18
|
migration_template "migration.rb", "db/migrate/add_primary_column_to_#{table_name}.rb",
|
17
|
-
migration_version: migration_version
|
19
|
+
migration_version: migration_version,
|
20
|
+
index_on: index_on,
|
21
|
+
support_partial_index: support_partial_index
|
18
22
|
end
|
19
23
|
|
20
24
|
def migration_version
|
21
25
|
"[#{ActiveRecord::VERSION::MAJOR}.#{ActiveRecord::VERSION::MINOR}]"
|
22
26
|
end
|
23
27
|
|
28
|
+
def index_on
|
29
|
+
if owner_key.present?
|
30
|
+
klass = table_name.classify.constantize
|
31
|
+
owner_association = klass.reflect_on_association(owner_key.to_sym)
|
32
|
+
|
33
|
+
if owner_association.nil?
|
34
|
+
raise ActiveRecord::AssociationNotFoundError.new(klass, owner_key)
|
35
|
+
end
|
36
|
+
|
37
|
+
owner_id_key = "#{owner_key}_id"
|
38
|
+
|
39
|
+
if owner_association.options[:polymorphic]
|
40
|
+
owner_type_key = "#{owner_key}_type"
|
41
|
+
"%i[#{owner_id_key}, #{owner_type_key}, #{flag_name}]"
|
42
|
+
else
|
43
|
+
"%i[#{owner_id_key}, #{flag_name}]"
|
44
|
+
end
|
45
|
+
else
|
46
|
+
":#{flag_name}"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def support_partial_index
|
51
|
+
# NOTE: Partial indexes are only supported for PostgreSQL and SQLite 3.8.0+.
|
52
|
+
# Also found that, even if we use SQLite 3.8.0+, we still get a syntax error.
|
53
|
+
# So currently we have ignored SQLite.
|
54
|
+
ActiveRecord::Base.connection.adapter_name.downcase.to_sym == :postgresql
|
55
|
+
end
|
56
|
+
|
24
57
|
def self.next_migration_number(dirname)
|
25
58
|
ActiveRecord::Generators::Base.next_migration_number(dirname)
|
26
59
|
end
|
@@ -1,6 +1,10 @@
|
|
1
1
|
class AddPrimaryColumnTo<%= table_name.camelize %> < ActiveRecord::Migration<%= migration_version %>
|
2
2
|
def change
|
3
|
-
add_column :<%= table_name
|
3
|
+
add_column :<%= table_name %>, :<%= flag_name %>, :boolean, default: false, null: false
|
4
|
+
<%- if support_partial_index -%>
|
5
|
+
# NOTE: Please uncomment following line if you want only one 'true' (constraint) in the table.
|
6
|
+
# add_index :<%= table_name %>, <%= index_on %>, unique: true, where: "(<%= table_name %>.<%= flag_name %> IS TRUE)"
|
7
|
+
<%- end -%>
|
4
8
|
end
|
5
9
|
end
|
6
10
|
|
data/lib/set_as_primary.rb
CHANGED
@@ -10,22 +10,22 @@ module SetAsPrimary
|
|
10
10
|
|
11
11
|
included do
|
12
12
|
before_save :unset_old_primary
|
13
|
-
before_save :force_primary, if: -> {
|
13
|
+
before_save :force_primary, if: -> { _klass._force_primary }
|
14
|
+
after_destroy :force_primary, if: -> { _klass._force_primary }
|
14
15
|
|
15
16
|
instance_eval do
|
16
17
|
class_attribute :_primary_flag_attribute, :_owner_key, :_force_primary
|
17
18
|
|
18
19
|
def set_as_primary(primary_flag_attribute = :primary, options = {})
|
19
20
|
if primary_flag_attribute.is_a?(Hash)
|
20
|
-
options = primary_flag_attribute
|
21
|
-
primary_flag_attribute = :primary
|
21
|
+
options = primary_flag_attribute; primary_flag_attribute = :primary
|
22
22
|
end
|
23
23
|
|
24
24
|
configuration = { owner_key: nil, force_primary: true }
|
25
25
|
|
26
26
|
configuration.update(options) if options.is_a?(Hash)
|
27
27
|
|
28
|
-
|
28
|
+
_handle_setup_errors(primary_flag_attribute, configuration)
|
29
29
|
|
30
30
|
self._primary_flag_attribute = primary_flag_attribute
|
31
31
|
self._owner_key = configuration[:owner_key]
|
@@ -33,9 +33,9 @@ module SetAsPrimary
|
|
33
33
|
end
|
34
34
|
|
35
35
|
private
|
36
|
-
def
|
36
|
+
def _handle_setup_errors(primary_flag_attribute, configuration)
|
37
37
|
if !primary_flag_attribute.is_a?(Symbol)
|
38
|
-
raise SetAsPrimary::Error, "
|
38
|
+
raise SetAsPrimary::Error, "wrong argument type (expected Symbol)"
|
39
39
|
end
|
40
40
|
|
41
41
|
owner_key = configuration[:owner_key]
|
@@ -49,40 +49,49 @@ module SetAsPrimary
|
|
49
49
|
|
50
50
|
private
|
51
51
|
def unset_old_primary
|
52
|
-
return unless
|
53
|
-
|
54
|
-
scope = self.class.where(scope_options) if scope_options.present?
|
52
|
+
return unless public_send(_klass._primary_flag_attribute)
|
55
53
|
|
54
|
+
scope = _klass
|
55
|
+
scope = scope.where(_scope_options) if _scope_options.present?
|
56
56
|
scope = scope.where("id != ?", id) unless new_record?
|
57
57
|
|
58
|
-
scope.update_all(
|
58
|
+
scope.update_all(_klass._primary_flag_attribute => false)
|
59
59
|
end
|
60
60
|
|
61
61
|
def force_primary
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
62
|
+
scope = _klass
|
63
|
+
scope = scope.where(_scope_options) if _scope_options.present?
|
64
|
+
count = scope.count
|
65
|
+
|
66
|
+
if count == 1 && destroyed?
|
67
|
+
object = scope.first
|
68
|
+
object.update_columns(_klass._primary_flag_attribute => true)
|
69
|
+
elsif (count == 1 && !new_record?) || (count == 0 && new_record?)
|
70
|
+
public_send("#{_klass._primary_flag_attribute}=", true)
|
66
71
|
end
|
67
72
|
end
|
68
73
|
|
69
|
-
def
|
70
|
-
return nil if
|
74
|
+
def _scope_options
|
75
|
+
return nil if _klass._owner_key.nil?
|
71
76
|
|
72
|
-
@
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
77
|
+
@_scope_options ||= if _klass.reflect_on_association(_klass._owner_key).options[:polymorphic]
|
78
|
+
_polymorphic_condition_options
|
79
|
+
else
|
80
|
+
owner_id = "#{_klass._owner_key}_id".to_sym
|
81
|
+
{ owner_id => public_send(owner_id) }
|
82
|
+
end
|
78
83
|
end
|
79
84
|
|
80
|
-
def
|
85
|
+
def _polymorphic_condition_options
|
81
86
|
owner = self.public_send(self.class._owner_key)
|
82
87
|
|
83
88
|
{
|
84
|
-
"#{
|
85
|
-
"#{
|
89
|
+
"#{_klass._owner_key}_id".to_sym => owner.id,
|
90
|
+
"#{_klass._owner_key}_type".to_sym => owner.class.name
|
86
91
|
}
|
87
92
|
end
|
93
|
+
|
94
|
+
def _klass
|
95
|
+
self.class
|
96
|
+
end
|
88
97
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: set_as_primary
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Santosh Wadghule
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2020-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|