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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09252a0d5cef2ec2dbf697837059b9604bad6617f82e2938fb95b0e7ea54749e'
4
- data.tar.gz: 8bed32971351c098816889638e39307b44692445fb5593a6941b28010344ea26
3
+ metadata.gz: 06f7d231ec1dc1effe46cd6b516c12bcffdf98b3feadde4d0f0a5b8e193f2047
4
+ data.tar.gz: 6f21389fa3d8aa3c8a2bc512dfb26874b84c9b3cfd060afc3b9c7ba18cac9781
5
5
  SHA512:
6
- metadata.gz: 7ff0a54d3af341c41ba937311a449ebb3adf582390b4dee9880aa36e548d56433c1314836e0246680b9e8611b9643085a390f48ab9962edbe5aa31501adb5ecd
7
- data.tar.gz: b4f24ce736c8f261252e48e342896a13a0710a36fc2abf17fb3b170e4291f945ad47dc900cb0ee0c2ae839d7af0f3358c4657cf21806f1d4ae1d07178ee7ae18
6
+ metadata.gz: 625e12dd80c2c13f78d5d557504db3cfbce52124846c64d735ed71a16f4f0feb1b41f69a8da8a3c20e58ecc29e3ebff5fdd0cb229dc9c1f78495fb6ff46b56fc
7
+ data.tar.gz: b706248616ceacf293a0b0ff8c9769226ae5e088c026cc655b3937159c5606132b8bb37665e425bac213c2c95e93da43a8132631e50822f94a19f8d6a4030446
@@ -81,7 +81,7 @@ Layout/EmptyLinesAroundModuleBody:
81
81
  Style/HashSyntax:
82
82
  Enabled: true
83
83
 
84
- Layout/IndentFirstArgument:
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/TrailingBlankLines:
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/StringConversionInInterpolation:
200
+ Lint/RedundantStringCoercion:
201
201
  Enabled: true
202
202
 
203
203
  Lint/UriEscapeUnescape:
@@ -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", require: false
12
- gem "rubocop-performance", require: false
13
- gem "rubocop-rails", require: false
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 (:white_check_mark:) to
3
+ The simplest way to handle the primary or default flag to
4
4
  your Rails models.
5
5
 
6
- Supports PostgreSQL, MySQL, and SQLite.
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 GitHub repository](https://github.com/mechanicles/set_as_primary_rails_app)
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's context.
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's context.
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's (association's) context.
77
+ association key `owner_key` if you wan to consider owner (association) context.
71
78
 
72
- **NOTE:** Default primary flag attribute is `primary`, and you can use another one too like `default` but
73
- make sure that flag should be present in the table and should be a boolean column type.
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
- If you run above command for `email_addresses` table, then it creates
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
- Don't forget to run `rails db:migrate` to create actual column in the table.
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #!/usr/bin/env ruby
2
4
 
3
5
  require "bundler/setup"
data/bin/setup CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  #!/usr/bin/env bash
2
4
  set -euo pipefail
3
5
  IFS=$'\n\t'
@@ -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 'primary' to the given table."
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.to_sym %>, :primary, :boolean, default: false, null: false
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
 
@@ -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: -> { self.class._force_primary }
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
- handle_setup_errors(primary_flag_attribute, configuration)
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 handle_setup_errors(primary_flag_attribute, configuration)
36
+ def _handle_setup_errors(primary_flag_attribute, configuration)
37
37
  if !primary_flag_attribute.is_a?(Symbol)
38
- raise SetAsPrimary::Error, "Wrong attribute! Please provide attribute in symbol type."
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 self.public_send(self.class._primary_flag_attribute)
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(self.class._primary_flag_attribute => false)
58
+ scope.update_all(_klass._primary_flag_attribute => false)
59
59
  end
60
60
 
61
61
  def force_primary
62
- count = self.class.where(scope_options).count
63
-
64
- if (count == 1 && !new_record?) || (count == 0 && new_record?)
65
- self.public_send("#{self.class._primary_flag_attribute}=", true)
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 scope_options
70
- return nil if self.class._owner_key.nil?
74
+ def _scope_options
75
+ return nil if _klass._owner_key.nil?
71
76
 
72
- @scope_option ||= if self.class.reflect_on_association(self.class._owner_key).options[:polymorphic]
73
- polymorphic_condition_options
74
- else
75
- owner_id = "#{self.class._owner_key}_id".to_sym
76
- { owner_id => self.public_send(owner_id) }
77
- end
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 polymorphic_condition_options
85
+ def _polymorphic_condition_options
81
86
  owner = self.public_send(self.class._owner_key)
82
87
 
83
88
  {
84
- "#{self.class._owner_key}_id".to_sym => owner.id,
85
- "#{self.class._owner_key}_type".to_sym => owner.class.name
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SetAsPrimary
4
- VERSION = "0.1.1"
4
+ VERSION = "0.1.2"
5
5
  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.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: 2019-12-29 00:00:00.000000000 Z
11
+ date: 2020-01-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport