effective_resources 1.7.4 → 1.7.5

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