effective_resources 1.7.4 → 1.7.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 321d1bd446ce934af73f92967e26577e09f16de314d0c0c780630901ccc23823
4
- data.tar.gz: f65a37ad4e83d73e6d3da2ac2bf3a0a1588b7ca86e425ddb2d901ba9e9dfd571
3
+ metadata.gz: 2c872b773ee626165d7d0dee9d363e9375fd7702c9f0330ce2492c380fcac45b
4
+ data.tar.gz: eadd2e25503395461ea1823d8b9e719eb80cb405373fcae2253c3467631c18ea
5
5
  SHA512:
6
- metadata.gz: e828b71738b3f48d9819fbffcabf1e8a94a44ae0e410069a0a7f1b64751cdb02fb23ce75dd0ad7ffc687ce6c0955112c4da1f9c864cc62256832fd308938b936
7
- data.tar.gz: d2be424f57532b120b7a1e465e158592a2f5cc20ba2c8b6ac21dba864d9f8c844143d9e229507b1ad9e7792285065665ec57c87eedcfaab760e40fedf02ec095
6
+ metadata.gz: 2a91bcaab5fe78374b9771ddfe9117bb0594202e1e91dfc9d39922dc153b0ea38d64a5643365488097efb9fd258d8915fbdea12e7b9b7098fec456f46f9bbe30
7
+ data.tar.gz: b49ff8dc969fbb45db46a4db1ed97b3f369a2734db20e2c5957b28fe0aa5928045ef73d8170e06a975170dca40d8c5a5d09d3e31600b00904783ce35dca6ea2a
@@ -10,25 +10,28 @@ module Effective
10
10
  raise 'expected resource class to have effective_resource do .. end' if effective_resource.model.blank?
11
11
 
12
12
  permitted_params = permitted_params_for(resource, effective_resource.namespaces)
13
+ permitted_name = params.key?(effective_resource.name) ? effective_resource.name : effective_resource.resource_name
13
14
 
14
15
  if Rails.env.development?
15
16
  Rails.logger.info "Effective::CrudController#resource_permitted_params:"
16
- Rails.logger.info "params.require(:#{effective_resource.resource_name}).permit(#{permitted_params.to_s[1...-1]})"
17
+ Rails.logger.info "params.require(:#{permitted_name}).permit(#{permitted_params.to_s[1...-1]})"
17
18
  end
18
19
 
19
- params.require(effective_resource.resource_name).permit(*permitted_params)
20
+ params.require(permitted_name).permit(*permitted_params)
20
21
  end
21
22
 
22
23
  # If the resource is ActiveModel, just permit all
23
24
  # This can still be overridden by a controller
24
25
  def resource_active_model_permitted_params
26
+ permitted_name = params.key?(effective_resource.name) ? effective_resource.name : effective_resource.resource_name
27
+
25
28
  if Rails.env.development?
26
29
  Rails.logger.info "Effective::CrudController#resource_permitted_params:"
27
- Rails.logger.info "params.require(:#{effective_resource.resource_name}).permit!"
30
+ Rails.logger.info "params.require(:#{permitted_name}).permit!"
28
31
  end
29
32
 
30
- if params[effective_resource.resource_name].present?
31
- params.require(effective_resource.resource_name).permit!
33
+ if params[permitted_name].present?
34
+ params.require(permitted_name).permit!
32
35
  else
33
36
  params.require((effective_resource.namespaces + [effective_resource.resource_name]).join('_')).permit!
34
37
  end
@@ -0,0 +1,167 @@
1
+ # EffectiveDeviseUsr
2
+ #
3
+ # Mark your user model with devise_for then effective_devise_user
4
+
5
+ module EffectiveDeviseUser
6
+ extend ActiveSupport::Concern
7
+
8
+ module Base
9
+ def effective_devise_user
10
+ include ::EffectiveDeviseUser
11
+ end
12
+ end
13
+
14
+ included do
15
+ effective_resource do
16
+ encrypted_password :string
17
+ reset_password_token :string
18
+ reset_password_sent_at :datetime
19
+ remember_created_at :datetime
20
+ sign_in_count :integer
21
+ current_sign_in_at :datetime
22
+ last_sign_in_at :datetime
23
+ current_sign_in_ip :inet
24
+ last_sign_in_ip :inet
25
+
26
+ # Devise invitable attributes
27
+ invitation_token :string
28
+ invitation_created_at :datetime
29
+ invitation_sent_at :datetime
30
+ invitation_accepted_at :datetime
31
+ invitation_limit :integer
32
+ invited_by_type :string
33
+ invited_by_id :integer
34
+ invitations_count :integer
35
+
36
+ # Omniauth
37
+ uid :string
38
+ provider :string
39
+
40
+ access_token :string
41
+ refresh_token :string
42
+ token_expires_at :datetime
43
+
44
+ name :string
45
+ avatar_url :string
46
+ end
47
+
48
+ # Devise invitable ignores model validations, so we manually check for duplicate email addresses.
49
+ before_save(if: -> { new_record? && invitation_sent_at.present? }) do
50
+ if email.blank?
51
+ self.errors.add(:email, "can't be blank")
52
+ raise("email can't be blank")
53
+ end
54
+
55
+ if self.class.where(email: email.downcase.strip).exists?
56
+ self.errors.add(:email, 'has already been taken')
57
+ raise("email has already been taken")
58
+ end
59
+ end
60
+
61
+ # Clear the provider if an oauth signed in user resets password
62
+ before_save(if: -> { persisted? && encrypted_password_changed? }) do
63
+ assign_attributes(provider: nil, access_token: nil, refresh_token: nil, token_expires_at: nil)
64
+ end
65
+ end
66
+
67
+ module ClassMethods
68
+ def permitted_sign_up_params # Should contain all fields as per views/users/_sign_up_fields
69
+ [:email, :password, :password_confirmation, :first_name, :last_name, :name, :login]
70
+ end
71
+
72
+ def from_omniauth(auth, params)
73
+ invitation_token = (params.presence || {})['invitation_token']
74
+
75
+ email = (auth.info.email.presence || "#{auth.uid}@#{auth.provider}.none").downcase
76
+ image = auth.info.image
77
+ name = auth.info.name || auth.dig(:extra, :raw_info, :login)
78
+
79
+ user = if invitation_token
80
+ find_by_invitation_token(invitation_token, false) || raise(ActiveRecord::RecordNotFound)
81
+ else
82
+ where(uid: auth.uid).or(where(email: email)).first || self.new()
83
+ end
84
+
85
+ user.assign_attributes(
86
+ uid: auth.uid,
87
+ provider: auth.provider,
88
+ email: email,
89
+ avatar_url: image,
90
+ name: name,
91
+ first_name: (auth.info.first_name.presence || name.split(' ').first.presence || 'First'),
92
+ last_name: (auth.info.last_name.presence || name.split(' ').last.presence || 'Last')
93
+ )
94
+
95
+ if auth.respond_to?(:credentials)
96
+ user.assign_attributes(
97
+ access_token: auth.credentials.token,
98
+ refresh_token: auth.credentials.refresh_token,
99
+ token_expires_at: Time.zone.at(auth.credentials.expires_at), # We are given integer datetime e.g. '1549394077'
100
+ )
101
+ end
102
+
103
+ # Make a password
104
+ user.password = Devise.friendly_token[0, 20] if user.encrypted_password.blank?
105
+
106
+ # Devise Invitable
107
+ invitation_token ? user.accept_invitation! : user.save!
108
+
109
+ # Devise Confirmable
110
+ user.confirm if user.respond_to?(:confirm)
111
+
112
+ user
113
+ end
114
+
115
+ # https://github.com/heartcombo/devise/blob/master/lib/devise/models/recoverable.rb#L134
116
+ def send_reset_password_instructions(attributes = {})
117
+ recoverable = find_or_initialize_with_errors(reset_password_keys, attributes, :not_found)
118
+ return recoverable unless recoverable.persisted?
119
+
120
+ # Add custom errors and require a confirmation if previous sign in was provider
121
+ if recoverable.provider.present? && attributes[:confirm_new_password].blank?
122
+ recoverable.errors.add(:email, "previous sign in was with #{recoverable.provider}")
123
+ recoverable.errors.add(:confirm_new_password, 'please confirm to proceed')
124
+ end
125
+
126
+ recoverable.send_reset_password_instructions if recoverable.errors.blank?
127
+ recoverable
128
+ end
129
+
130
+ end
131
+
132
+ # EffectiveDeviseUser Instance Methods
133
+
134
+ def reinvite!
135
+ invite!
136
+ end
137
+
138
+ def active_for_authentication?
139
+ super && (respond_to?(:archived?) ? !archived? : true)
140
+ end
141
+
142
+ def inactive_message
143
+ (respond_to?(:archived?) && archived?) ? :archived : super
144
+ end
145
+
146
+ # Any password will work in development or mode
147
+ def valid_password?(password)
148
+ Rails.env.development? || Rails.env.staging? || super
149
+ end
150
+
151
+ # Send devise & devise_invitable emails via active job
152
+ def send_devise_notification(notification, *args)
153
+ raise('expected args Hash') unless args.respond_to?(:last) && args.last.kind_of?(Hash)
154
+
155
+
156
+
157
+ if defined?(Tenant)
158
+ tenant = Tenant.current || raise('expected a current tenant')
159
+ args.last[:tenant] ||= tenant
160
+ end
161
+
162
+ wait = (5 if notification == :invitation_instructions && !Rails.env.test?)
163
+
164
+ devise_mailer.send(notification, self, *args).deliver_later(wait: wait)
165
+ end
166
+
167
+ end
@@ -14,10 +14,18 @@ module Effective
14
14
  include Effective::Resources::Relation
15
15
  include Effective::Resources::Sql
16
16
 
17
+
18
+ # In practice, this is initialized two ways
19
+ # With a klass and a namespace from effective_datatables
20
+ # Or with a controller_path from crud controller
21
+
17
22
  # post, Post, Admin::Post, admin::Post, admin/posts, admin/post, admin/effective::post
18
23
  def initialize(input, namespace: nil, relation: nil, &block)
19
24
  _initialize_input(input, namespace: namespace, relation: relation)
25
+
26
+ # This is an effective_resource do ... end block
20
27
  _initialize_model(&block) if block_given?
28
+
21
29
  self
22
30
  end
23
31
 
@@ -7,33 +7,27 @@ module Effective
7
7
  POST_VERBS = ['POST', 'PUT', 'PATCH']
8
8
  CRUD_ACTIONS = %i(index new create show edit update destroy)
9
9
 
10
- # This was written for the Edit actions fallback templates and Datatables
11
- # Effective::Resource.new('admin/posts').routes[:index]
12
- def routes
13
- @routes ||= (
14
- matches = [
15
- [namespace, route_name.pluralize].compact.join('/'),
16
- [namespace, route_name].compact.join('/'),
17
- ]
18
-
19
- # Check main Rails app
20
- routes = Rails.application.routes.routes.select do |route|
21
- (matches & [route.defaults[:controller]]).present? && !route.name.to_s.end_with?('root')
22
- end
10
+ # This will have been set by init from crud_controller, or from a class and namespace
11
+ def controller_path
12
+ @controller_path ||= ([namespace, plural_name].compact * '/')
13
+ end
23
14
 
24
- if routes.blank?
25
- matches = [
26
- [namespace, plural_name].compact.join('/'),
27
- [namespace, name].compact.join('/')
28
- ]
15
+ def routes
16
+ @routes ||= begin
17
+ routes = nil
18
+ engines = [Rails.application] + Rails::Engine.subclasses.reverse
19
+
20
+ # Check from controller_path. This is generally correct.
21
+ engines.each do |engine|
22
+ routes = engine.routes.routes.select do |route|
23
+ controller_path == route.defaults[:controller] && !route.name.to_s.end_with?('root')
24
+ end
29
25
 
30
- # Check main Rails app
31
- routes = Rails.application.routes.routes.select do |route|
32
- (matches & [route.defaults[:controller]]).present? && !route.name.to_s.end_with?('root')
26
+ if routes.present?
27
+ @routes_app = engine; break
33
28
  end
34
29
  end
35
30
 
36
- # Check engine routes
37
31
  if routes.blank?
38
32
  matches = [
39
33
  [namespace, route_name.pluralize].compact.join('/'),
@@ -46,21 +40,19 @@ module Effective
46
40
  ['effective', namespace, name].compact.join('/')
47
41
  ]
48
42
 
49
- (Rails::Engine.subclasses.reverse + [Rails.application]).each do |engine|
43
+ engines.each do |engine|
50
44
  routes = engine.routes.routes.select do |route|
51
45
  (matches & [route.defaults[:controller]]).present? && !route.name.to_s.end_with?('root')
52
46
  end
53
47
 
54
48
  if routes.present?
55
- @routes_app = engine
56
- break
49
+ @routes_app = engine; break
57
50
  end
58
-
59
51
  end
60
52
  end
61
53
 
62
54
  Array(routes).inject({}) { |h, route| h[route.defaults[:action].to_sym] = route; h }
63
- )
55
+ end
64
56
  end
65
57
 
66
58
  def routes_app
@@ -137,58 +129,39 @@ module Effective
137
129
  end
138
130
 
139
131
  def crud_actions
140
- @crud_actions ||= (actions & CRUD_ACTIONS)
132
+ (actions & CRUD_ACTIONS)
141
133
  end
142
134
 
143
135
  # GET actions
144
136
  def collection_actions
145
- @collection_actions ||= (
146
- routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) }.tap(&:compact!)
147
- )
137
+ routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) } - [nil]
148
138
  end
149
139
 
150
140
  def collection_get_actions
151
- @collection_get_actions ||= (
152
- routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) && is_get_route?(route) }.tap(&:compact!)
153
- )
141
+ routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) && is_get_route?(route) } - [nil]
154
142
  end
155
143
 
156
144
  def collection_post_actions
157
- @collection_post_actions ||= (
158
- routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) && is_post_route?(route) }.tap(&:compact!)
159
- )
145
+ routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) && is_post_route?(route) } - [nil]
160
146
  end
161
147
 
162
148
  # All actions
163
149
  def member_actions
164
- @member_actions ||= (
165
- routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) }.tap(&:compact!)
166
- )
150
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) } - [nil]
167
151
  end
168
152
 
169
153
  # GET actions
170
154
  def member_get_actions
171
- @member_get_actions ||= (
172
- routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_get_route?(route) }.tap(&:compact!)
173
- )
155
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_get_route?(route) } - [nil]
174
156
  end
175
157
 
176
158
  def member_delete_actions
177
- @member_delete_actions ||= (
178
- routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_delete_route?(route) }.tap(&:compact!)
179
- )
159
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_delete_route?(route) } - [nil]
180
160
  end
181
161
 
182
162
  # POST/PUT/PATCH actions
183
163
  def member_post_actions
184
- @member_post_actions ||= (
185
- routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_post_route?(route) }.tap(&:compact!)
186
- )
187
- end
188
-
189
- # Same as controller_path in the view
190
- def controller_path
191
- [namespace, plural_name].compact * '/'
164
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_post_route?(route) } - [nil]
192
165
  end
193
166
 
194
167
  private
@@ -8,6 +8,11 @@ module Effective
8
8
  @initialized_name = input
9
9
  @model_klass = (relation ? _klass_by_input(relation) : _klass_by_input(input))
10
10
 
11
+ # Consider controller_name
12
+ if @model_klass && input.kind_of?(String) && namespace.blank?
13
+ @controller_path = input
14
+ end
15
+
11
16
  # Consider namespaces
12
17
  if namespace
13
18
  @namespaces = (namespace.kind_of?(String) ? namespace.split('/') : Array(namespace))
@@ -62,30 +67,30 @@ module Effective
62
67
  end
63
68
  end
64
69
 
70
+
71
+ # 'acpa/admin/shirts'
65
72
  def _klass_by_name(input)
66
73
  input = input.to_s
67
74
  input = input[1..-1] if input.start_with?('/')
68
75
 
69
76
  names = input.split('/')
70
77
 
71
- # Crazy classify
72
- 0.upto(names.length-1) do |index|
73
- class_name = names[index..-1].map { |name| name.classify } * '::'
74
- klass = class_name.safe_constantize
75
-
76
- if klass.blank? && index > 0
77
- class_name = (names[0..index-1].map { |name| name.classify.pluralize } + names[index..-1].map { |name| name.classify }) * '::'
78
- klass = class_name.safe_constantize
79
- end
80
-
81
- if klass.present?
82
- @namespaces ||= names[0...index]
83
- @model_klass = klass
84
- return klass
78
+ # Classify based on namespace
79
+ # acpa/admin/shirts
80
+ (names.length).downto(1).each do |n|
81
+ names.combination(n).to_a.each do |pieces|
82
+ klass_pieces = pieces.map { |piece| piece.classify } * '::'
83
+ klass = klass_pieces.safe_constantize
84
+
85
+ if klass.present? && klass.class != Module
86
+ @namespaces ||= (names - pieces)
87
+ @model_klass = klass
88
+ return klass
89
+ end
85
90
  end
86
91
  end
87
92
 
88
- # Crazy engine
93
+ # Crazy effective engine.
89
94
  if names[0] == 'admin'
90
95
  class_name = (['effective'] + names[1..-1]).map { |name| name.classify } * '::'
91
96
  klass = class_name.safe_constantize
@@ -8,6 +8,8 @@ module Effective
8
8
 
9
9
  def datatable_klass
10
10
  if defined?(EffectiveDatatables)
11
+ "#{controller_path.classify.pluralize}Datatable".safe_constantize ||
12
+ "#{controller_path.classify}Datatable".safe_constantize ||
11
13
  "#{namespaced_class_name.pluralize}Datatable".safe_constantize ||
12
14
  "#{namespaced_module_name.pluralize}Datatable".safe_constantize ||
13
15
  "#{namespaced_class_name.pluralize.gsub('::', '')}Datatable".safe_constantize ||
@@ -27,6 +27,8 @@ module EffectiveResources
27
27
  ActiveRecord::Base.extend(ActsAsSlugged::Base)
28
28
  ActiveRecord::Base.extend(ActsAsStatused::Base)
29
29
  ActiveRecord::Base.extend(ActsAsWizard::Base)
30
+
31
+ ActiveRecord::Base.extend(EffectiveDeviseUser::Base)
30
32
  ActiveRecord::Base.extend(EffectiveResource::Base)
31
33
  end
32
34
  end
@@ -1,3 +1,3 @@
1
1
  module EffectiveResources
2
- VERSION = '1.7.4'.freeze
2
+ VERSION = '1.7.5'.freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: effective_resources
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.7.4
4
+ version: 1.7.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Code and Effect
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-04 00:00:00.000000000 Z
11
+ date: 2021-02-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rails
@@ -141,6 +141,7 @@ files:
141
141
  - app/models/concerns/acts_as_statused.rb
142
142
  - app/models/concerns/acts_as_tokened.rb
143
143
  - app/models/concerns/acts_as_wizard.rb
144
+ - app/models/concerns/effective_devise_user.rb
144
145
  - app/models/concerns/effective_resource.rb
145
146
  - app/models/effective/access_denied.rb
146
147
  - app/models/effective/action_failed.rb