set_as_primary 0.1.1 → 0.1.2
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 +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
|
[](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
|