effective_resources 1.7.3 → 1.7.8

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: a63a4abae27e2b70ac922d2f3eca07c701842b9ef2482677d200236f05a05dc8
4
- data.tar.gz: 136206e80f42d5c019f9113d46c3260d19115f7f40ef58f0064f74b57283167f
3
+ metadata.gz: 7a4d2d6652da6f85e884ceb383ff012fda17863ad4b84c7cfaf5fec76a8632af
4
+ data.tar.gz: 155b5a31cc14908ea944978d940d550227bcb823e14bbc42e6f34108ece06c35
5
5
  SHA512:
6
- metadata.gz: 2b08dd74cfa1605c8dcf8e8082612b078ff20b93f9bfbb931f41b8d58ec2795f4a6ca0ed13f837860df4b3166c2069136ab26e0b19a46fa812af2293674bb56a
7
- data.tar.gz: a64adaf287722c737951827174f1074ad0580688540944551f406d66fb28dadf74ca7c845c57f008aea6036c355655674328c37c89cc90be36f40738753db436
6
+ metadata.gz: 4227d0379585019dafae032ac3300cc98ceed87f81bfcb45a35ab9b10c5e15f18dd7e6420b1973bef3e1567dd8ac71d78049b6ec6711153899568ed0d3398fc3
7
+ data.tar.gz: 700279f4126eefafe9b14d06b4dea16b9cf49c1f2b21fd472a17a80e3e2863d2f5d6318f481d3f862653d95f753960f5a0fbc79802301e3879422cec89866df8
data/README.md CHANGED
@@ -362,7 +362,7 @@ rails test
362
362
 
363
363
  ## License
364
364
 
365
- MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
365
+ MIT License. Copyright [Code and Effect Inc.](http://www.codeandeffect.com/)
366
366
 
367
367
  ## Contributing
368
368
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Effective
2
3
  module CrudController
3
4
  extend ActiveSupport::Concern
@@ -12,6 +13,7 @@ module Effective
12
13
  included do
13
14
  define_actions_from_routes
14
15
  define_callbacks :resource_render, :resource_before_save, :resource_after_save, :resource_after_commit, :resource_error
16
+ layout -> { resource_layout }
15
17
  end
16
18
 
17
19
  module ClassMethods
@@ -111,6 +113,11 @@ module Effective
111
113
  datatable
112
114
  end
113
115
 
116
+ def resource_layout
117
+ namespace = controller_path.include?('admin/') ? 'admin' : 'application'
118
+ defined?(Tenant) ? "#{Tenant.current}/#{namespace}" : namespace
119
+ end
120
+
114
121
  def resource_params_method_name
115
122
  ['permitted_params', "#{resource_name}_params", "#{resource_plural_name}_params"].each do |name|
116
123
  return name if respond_to?(name, true)
@@ -53,7 +53,7 @@ module Effective
53
53
 
54
54
  respond_to do |format|
55
55
  format.html { }
56
- format.js { render('new.js') }
56
+ format.js { render('new', formats: :js) }
57
57
  end
58
58
  end
59
59
 
@@ -95,7 +95,7 @@ module Effective
95
95
 
96
96
  respond_to do |format|
97
97
  format.html { }
98
- format.js { render('show.js') }
98
+ format.js { render('show', formats: :js) }
99
99
  end
100
100
 
101
101
  end
@@ -112,7 +112,7 @@ module Effective
112
112
 
113
113
  respond_to do |format|
114
114
  format.html { }
115
- format.js { render('edit.js') }
115
+ format.js { render('edit', formats: :js) }
116
116
  end
117
117
 
118
118
  end
@@ -176,7 +176,10 @@ module Effective
176
176
 
177
177
  respond_to do |format|
178
178
  format.html { }
179
- format.js { render(template_present?(action) ? action : 'member_action.js', locals: { action: action }) }
179
+ format.js do
180
+ template = template_present?(action) ? action : 'member_action'
181
+ render(template, formats: :js, locals: { action: action })
182
+ end
180
183
  end
181
184
 
182
185
  return
@@ -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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Effective
2
4
  module CrudController
3
5
  module Respond
@@ -94,7 +96,10 @@ module Effective
94
96
  end
95
97
 
96
98
  def template_present?(action)
97
- lookup_context.template_exists?("#{action}.#{request.format.symbol.to_s.sub('json', 'js').presence || 'html'}", _prefixes)
99
+ #lookup_context.template_exists?("#{action}.#{request.format.symbol.to_s.sub('json', 'js').presence || 'html'}", _prefixes)
100
+
101
+ formats = [request.format.symbol.to_s.sub('json', 'js').presence || 'html']
102
+ lookup_context.template_exists?(action, _prefixes, formats: formats)
98
103
  end
99
104
 
100
105
  end
@@ -76,6 +76,7 @@ module Effective
76
76
  def resource_flash(status, resource, action, e: nil)
77
77
  submit = commit_action(action)
78
78
  message = submit[status].respond_to?(:call) ? instance_exec(&submit[status]) : submit[status]
79
+
79
80
  return message.gsub('@resource', resource.to_s) if message.present?
80
81
  return nil if message.blank? && submit.key?(status)
81
82
 
@@ -93,6 +94,9 @@ module Effective
93
94
 
94
95
  # Should return a new resource based on the passed one
95
96
  def duplicate_resource(resource)
97
+ return resource.duplicate if resource.respond_to?(:duplicate)
98
+ return resource.duplicate! if resource.respond_to?(:duplicate!)
99
+ return resource.deep_dup if resource.respond_to?(:deep_dup)
96
100
  resource.dup
97
101
  end
98
102
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Effective
2
4
  module FlashMessages
3
5
  extend ActiveSupport::Concern
@@ -6,7 +8,11 @@ module Effective
6
8
  def flash_success(resource, action = nil, name: nil)
7
9
  raise 'expected an ActiveRecord resource' unless (name || resource.class.respond_to?(:model_name))
8
10
 
9
- "Successfully #{action_verb(action)} #{name || resource}".html_safe
11
+ name ||= begin
12
+ resource.destroyed? ? resource.class.model_name.to_s.downcase.split('::').last : resource.to_s.presence
13
+ end
14
+
15
+ "Successfully #{action_verb(action)} #{name || 'resource'}".html_safe
10
16
  end
11
17
 
12
18
  # flash.now[:danger] = flash_danger(@post)
@@ -18,7 +24,9 @@ module Effective
18
24
 
19
25
  messages = flash_errors(resource, e: e)
20
26
 
21
- name ||= resource.to_s.presence
27
+ name ||= begin
28
+ resource.destroyed? ? resource.class.model_name.to_s.downcase.split('::').last : resource.to_s.presence
29
+ end
22
30
 
23
31
  ["Unable to #{action}", (" #{name}" if name), (": #{messages}" if messages)].compact.join.html_safe
24
32
  end
@@ -27,7 +35,10 @@ module Effective
27
35
  def flash_errors(resource, e: nil)
28
36
  raise 'expected an ActiveRecord resource' unless resource.respond_to?(:errors)
29
37
 
30
- messages = resource.errors.map do |attribute, message|
38
+ messages = resource.errors.map do |error|
39
+ attribute = error.respond_to?(:attribute) ? error.attribute : error.first
40
+ message = error.respond_to?(:message) ? error.message : error.last
41
+
31
42
  if message[0] == message[0].upcase # If the error begins with a capital letter
32
43
  message
33
44
  elsif attribute == :base
@@ -52,6 +63,8 @@ module Effective
52
63
  'deleted'
53
64
  elsif word == 'undo'
54
65
  'undid'
66
+ elsif word == 'run'
67
+ 'ran'
55
68
  elsif word.end_with?('e')
56
69
  action.sub(word, word + 'd')
57
70
  elsif ['a', 'i', 'o', 'u'].include?(word[-1])
@@ -107,15 +107,16 @@ module EffectiveResourcesHelper
107
107
 
108
108
  # Select Partial
109
109
  partial = if partial.kind_of?(Symbol)
110
- "effective/resource/actions_#{partial}.html"
110
+ "effective/resource/actions_#{partial}"
111
111
  else
112
- "#{partial.presence || 'effective/resource/actions'}.html"
112
+ partial.presence || 'effective/resource/actions'
113
113
  end
114
114
 
115
115
  # Assign Locals
116
116
  locals = {
117
117
  resource: resource,
118
118
  effective_resource: effective_resource,
119
+ formats: [:html],
119
120
  format_block: (block if block_given?),
120
121
  namespace: namespace,
121
122
  actions: actions,
@@ -123,7 +124,14 @@ module EffectiveResourcesHelper
123
124
  }.compact.merge(locals)
124
125
 
125
126
  if resource.kind_of?(Array)
126
- render(partial: partial, collection: resource, as: :resource, locals: locals.except(:resource), spacer_template: spacer_template)
127
+ render(
128
+ partial: partial,
129
+ formats: [:html],
130
+ collection: resource,
131
+ as: :resource,
132
+ locals: locals.except(:resource),
133
+ spacer_template: spacer_template
134
+ )
127
135
  else
128
136
  render(partial, locals)
129
137
  end
@@ -143,16 +151,24 @@ module EffectiveResourcesHelper
143
151
  atts = { :namespace => (effective_resource.namespace.to_sym if effective_resource.namespace), effective_resource.name.to_sym => resource }.compact.merge(atts)
144
152
 
145
153
  if lookup_context.template_exists?("form_#{action}", controller._prefixes, :partial)
146
- render "form_#{action}", atts
147
- elsif lookup_context.template_exists?('form', controller._prefixes, :partial)
148
- render 'form', atts
149
- elsif lookup_context.template_exists?('form', effective_resource.plural_name, :partial)
150
- render "#{effective_resource.plural_name}/form", atts
151
- elsif lookup_context.template_exists?('form', effective_resource.name, :partial)
152
- render "#{effective_resource.name}/form", atts
153
- else
154
- render 'form', atts # Will raise the regular error
154
+ return render("form_#{action}", atts)
155
+ end
156
+
157
+ if lookup_context.template_exists?('form', controller._prefixes, :partial)
158
+ return render('form', atts)
159
+ end
160
+
161
+ effective_resource.view_paths.each do |view_path|
162
+ if lookup_context.template_exists?("form_#{action}", [view_path], :partial)
163
+ return render(view_path + '/' + "form_#{action}", atts)
164
+ end
165
+
166
+ if lookup_context.template_exists?('form', [view_path], :partial)
167
+ return render(view_path + '/' + 'form', atts)
168
+ end
155
169
  end
170
+
171
+ render('form', atts) # Will raise the regular error
156
172
  end
157
173
 
158
174
  # Similar to render_resource_form
@@ -169,14 +185,16 @@ module EffectiveResourcesHelper
169
185
  atts = { :namespace => (effective_resource.namespace.to_sym if effective_resource.namespace), effective_resource.name.to_sym => resource }.compact.merge(atts)
170
186
 
171
187
  if lookup_context.template_exists?(effective_resource.name, controller._prefixes, :partial)
172
- render(effective_resource.name, atts)
173
- elsif lookup_context.template_exists?(effective_resource.name, [effective_resource.plural_name], :partial)
174
- render(effective_resource.plural_name + '/' + effective_resource.name, atts)
175
- elsif lookup_context.template_exists?(effective_resource.name, [effective_resource.name], :partial)
176
- render(effective_resource.name + '/' + effective_resource.name, atts)
177
- else
178
- render(resource, atts) # Will raise the regular error
188
+ return render(effective_resource.name, atts)
179
189
  end
190
+
191
+ effective_resource.view_paths.each do |view_path|
192
+ if lookup_context.template_exists?(effective_resource.name, [view_path], :partial)
193
+ return render(view_path + '/' + effective_resource.name, atts)
194
+ end
195
+ end
196
+
197
+ render(resource, atts) # Will raise the regular error
180
198
  end
181
199
 
182
200
  # Tableize attributes
@@ -16,7 +16,9 @@ module ActsAsSlugged
16
16
  included do
17
17
  extend FinderMethods
18
18
 
19
- before_validation { self.slug ||= build_slug }
19
+ before_validation do
20
+ assign_attributes(slug: build_slug) if slug.blank?
21
+ end
20
22
 
21
23
  validates :slug,
22
24
  presence: true, uniqueness: true, exclusion: { in: excluded_slugs }, length: { maximum: 255 },
@@ -44,7 +46,7 @@ module ActsAsSlugged
44
46
 
45
47
  # Instance Methods
46
48
  def build_slug
47
- slug = self.to_s.parameterize.downcase[0, 250]
49
+ slug = to_s.parameterize.downcase[0, 250]
48
50
 
49
51
  if self.class.excluded_slugs.include?(slug)
50
52
  slug = "#{slug}-#{self.class.name.demodulize.parameterize}"
@@ -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
@@ -27,6 +27,7 @@ module Effective
27
27
  @type ||= (
28
28
  case obj
29
29
  when :boolean ; :boolean
30
+ when :config ; :config
30
31
  when :currency ; :currency
31
32
  when :date ; :date
32
33
  when :datetime ; :datetime
@@ -73,6 +74,9 @@ module Effective
73
74
  case type
74
75
  when :boolean
75
76
  [true, 'true', 't', '1'].include?(value)
77
+ when :config
78
+ raise('expected an ActiveSupport::OrderedOptions') unless value.kind_of?(ActiveSupport::OrderedOptions)
79
+ parse_ordered_options(value)
76
80
  when :date, :datetime
77
81
  if (digits = value.to_s.scan(/(\d+)/).flatten).present?
78
82
  date = if digits.first.length == 4 # 2017-01-10
@@ -169,5 +173,14 @@ module Effective
169
173
  name == other.name && type == other.type
170
174
  end
171
175
 
176
+ # This returns a nested ActiveSupport::OrderedOptions.new config
177
+ def parse_ordered_options(obj)
178
+ return obj unless obj.kind_of?(Hash)
179
+
180
+ ActiveSupport::OrderedOptions.new.tap do |config|
181
+ obj.each { |key, value| config[key] = parse_ordered_options(value) }
182
+ end
183
+ end
184
+
172
185
  end
173
186
  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,60 +7,43 @@ 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]
10
+ # This will have been set by init from crud_controller, or from a class and namespace
11
+ def controller_path
12
+ @controller_path ||= route_name #[namespace, plural_name].compact * '/')
13
+ end
14
+
12
15
  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
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 || '').end_with?('root')
24
+ end
23
25
 
24
- if routes.blank?
25
- matches = [
26
- [namespace, plural_name].compact.join('/'),
27
- [namespace, name].compact.join('/')
28
- ]
29
-
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
- matches = [
39
- [namespace, route_name.pluralize].compact.join('/'),
40
- [namespace, route_name].compact.join('/'),
41
- ['effective', namespace, route_name.pluralize].compact.join('/'),
42
- ['effective', namespace, route_name].compact.join('/'),
43
- [namespace, plural_name].compact.join('/'),
44
- [namespace, name].compact.join('/'),
45
- ['effective', namespace, plural_name].compact.join('/'),
46
- ['effective', namespace, name].compact.join('/')
47
- ]
48
-
49
- (Rails::Engine.subclasses.reverse + [Rails.application]).each do |engine|
32
+ matches = route_name_fallbacks()
33
+
34
+ engines.each do |engine|
50
35
  routes = engine.routes.routes.select do |route|
51
- (matches & [route.defaults[:controller]]).present? && !route.name.to_s.end_with?('root')
36
+ (matches & [route.defaults[:controller]]).present? && !(route.name || '').end_with?('root')
52
37
  end
53
38
 
54
39
  if routes.present?
55
- @routes_app = engine
56
- break
40
+ @routes_app = engine; break
57
41
  end
58
-
59
42
  end
60
43
  end
61
44
 
62
45
  Array(routes).inject({}) { |h, route| h[route.defaults[:action].to_sym] = route; h }
63
- )
46
+ end
64
47
  end
65
48
 
66
49
  def routes_app
@@ -137,58 +120,39 @@ module Effective
137
120
  end
138
121
 
139
122
  def crud_actions
140
- @crud_actions ||= (actions & CRUD_ACTIONS)
123
+ (actions & CRUD_ACTIONS)
141
124
  end
142
125
 
143
126
  # GET actions
144
127
  def collection_actions
145
- @collection_actions ||= (
146
- routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) }.tap(&:compact!)
147
- )
128
+ routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) } - [nil]
148
129
  end
149
130
 
150
131
  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
- )
132
+ routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) && is_get_route?(route) } - [nil]
154
133
  end
155
134
 
156
135
  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
- )
136
+ routes.map { |_, route| route.defaults[:action].to_sym if is_collection_route?(route) && is_post_route?(route) } - [nil]
160
137
  end
161
138
 
162
139
  # All actions
163
140
  def member_actions
164
- @member_actions ||= (
165
- routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) }.tap(&:compact!)
166
- )
141
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) } - [nil]
167
142
  end
168
143
 
169
144
  # GET actions
170
145
  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
- )
146
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_get_route?(route) } - [nil]
174
147
  end
175
148
 
176
149
  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
- )
150
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_delete_route?(route) } - [nil]
180
151
  end
181
152
 
182
153
  # POST/PUT/PATCH actions
183
154
  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 * '/'
155
+ routes.map { |_, route| route.defaults[:action].to_sym if is_member_route?(route) && is_post_route?(route) } - [nil]
192
156
  end
193
157
 
194
158
  private
@@ -7,6 +7,7 @@ module Effective
7
7
  case (type || sql_type(name))
8
8
  when :belongs_to
9
9
  { as: :select }.merge(search_form_field_collection(belongs_to(name)))
10
+
10
11
  when :belongs_to_polymorphic
11
12
  constant_pluralized = name.to_s.upcase
12
13
  constant = name.to_s.pluralize.upcase
@@ -18,7 +19,7 @@ module Effective
18
19
  collection ||= (klass.const_get(constant_pluralized) rescue nil) if defined?("#{klass.name}::#{constant_pluralized}")
19
20
  end
20
21
 
21
- { as: :select, polymorphic: true, collection: collection }.compact
22
+ { as: :select, polymorphic: true, collection: (collection || []) }.compact
22
23
  when :has_and_belongs_to_many
23
24
  { as: :select }.merge(search_form_field_collection(has_and_belongs_to_many(name)))
24
25
  when :has_many
@@ -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 ||
@@ -19,8 +19,27 @@ module Effective
19
19
  @initialized_name
20
20
  end
21
21
 
22
+ # There could be a few, this is the best guess.
22
23
  def route_name # 'post' initialized from the controller_path/initialized_name and not the class
23
- @route_name ||= (initialized_name.to_s.split(SPLIT).last || '').singularize.underscore
24
+ names = class_name.split('::')
25
+
26
+ if names.length > 1
27
+ Array(names[0]) + namespaces + Array(names[1..-1])
28
+ else
29
+ namespaces + names
30
+ end.compact.map(&:downcase).join('/').pluralize
31
+ end
32
+
33
+ def route_name_fallbacks
34
+ mod = class_name.split('::').first.downcase
35
+
36
+ matches = [
37
+ route_name.singularize,
38
+ [*namespace, plural_name].join('/'),
39
+ [*namespace, name].join('/'),
40
+ [mod, *namespace, plural_name].join('/'),
41
+ [mod, *namespace, name].join('/')
42
+ ]
24
43
  end
25
44
 
26
45
  def class_name # 'Effective::Post'
@@ -22,6 +22,22 @@ module Effective
22
22
  File.join('app/views', plural_name, "#{'_' if partial}#{action}.html.haml")
23
23
  end
24
24
 
25
+ # Used by render_resource_partial and render_resource_form to guess the view path
26
+ def view_paths
27
+ mod = class_name.split('::').first.downcase
28
+
29
+ [
30
+ [mod, *namespace, plural_name].join('/'),
31
+ [mod, *namespace, name].join('/'),
32
+ [*namespace, mod, plural_name].join('/'),
33
+ [*namespace, mod, name].join('/'),
34
+ [mod, plural_name].join('/'),
35
+ [mod, name].join('/'),
36
+ [*namespace, plural_name].join('/'),
37
+ [*namespace, name].join('/')
38
+ ]
39
+ end
40
+
25
41
  end
26
42
  end
27
43
  end
@@ -1,25 +1,24 @@
1
1
  require 'effective_resources/engine'
2
2
  require 'effective_resources/version'
3
+ require 'effective_resources/effective_gem'
3
4
 
4
5
  module EffectiveResources
5
6
 
6
- # The following are all valid config keys
7
- mattr_accessor :authorization_method
8
- mattr_accessor :default_submits
9
-
10
- def self.setup
11
- yield self
7
+ def self.config_keys
8
+ [:authorization_method, :default_submits]
12
9
  end
13
10
 
11
+ include EffectiveGem
12
+
14
13
  def self.authorized?(controller, action, resource)
15
- @_exceptions ||= [Effective::AccessDenied, (CanCan::AccessDenied if defined?(CanCan)), (Pundit::NotAuthorizedError if defined?(Pundit))].compact
14
+ @exceptions ||= [Effective::AccessDenied, (CanCan::AccessDenied if defined?(CanCan)), (Pundit::NotAuthorizedError if defined?(Pundit))].compact
16
15
 
17
16
  return !!authorization_method unless authorization_method.respond_to?(:call)
18
17
  controller = controller.controller if controller.respond_to?(:controller)
19
18
 
20
19
  begin
21
20
  !!(controller || self).instance_exec((controller || self), action, resource, &authorization_method)
22
- rescue *@_exceptions
21
+ rescue *@exceptions
23
22
  false
24
23
  end
25
24
  end
@@ -29,9 +28,7 @@ module EffectiveResources
29
28
  end
30
29
 
31
30
  def self.default_submits
32
- @_default_submits ||= begin
33
- (['Save', 'Continue', 'Add New'] & Array(@@default_submits)).inject({}) { |h, v| h[v] = true; h }
34
- end
31
+ (['Save', 'Continue', 'Add New'] & Array(config.default_submits)).inject({}) { |h, v| h[v] = true; h }
35
32
  end
36
33
 
37
34
  end
@@ -0,0 +1,42 @@
1
+ # Effective Engine concern
2
+
3
+ module EffectiveGem
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ raise("expected self.config_keys method") unless respond_to?(:config_keys)
8
+
9
+ config_keys.each do |key|
10
+ self.class.define_method(key) { config()[key] }
11
+ end
12
+ end
13
+
14
+ module ClassMethods
15
+ def config(namespace = nil)
16
+ namespace ||= Tenant.current if defined?(Tenant)
17
+ @config.dig(namespace) || @config
18
+ end
19
+
20
+ def setup(namespace = nil, &block)
21
+ @config ||= ActiveSupport::OrderedOptions.new
22
+ namespace ||= Tenant.current if defined?(Tenant)
23
+
24
+ if namespace
25
+ @config[namespace] ||= ActiveSupport::OrderedOptions.new
26
+ end
27
+
28
+ yield(config(namespace))
29
+
30
+ if(unsupported = (config(namespace).keys - config_keys)).present?
31
+ if unsupported.include?(:authorization_method)
32
+ raise("config.authorization_method has been removed. This gem will call EffectiveResources.authorization_method instead. Please double check the config.authorization_method setting in config/initializers/effective_resources.rb and remove it from this file.")
33
+ end
34
+
35
+ raise("unsupported config keys: #{unsupported}\n supported keys: #{config_keys}")
36
+ end
37
+
38
+ true
39
+ end
40
+ end
41
+
42
+ end
@@ -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.3'.freeze
2
+ VERSION = '1.7.8'.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.3
4
+ version: 1.7.8
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-02 00:00:00.000000000 Z
11
+ date: 2021-02-22 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
@@ -179,6 +180,7 @@ files:
179
180
  - app/views/effective/resource/_actions_glyphicons.html.haml
180
181
  - config/effective_resources.rb
181
182
  - lib/effective_resources.rb
183
+ - lib/effective_resources/effective_gem.rb
182
184
  - lib/effective_resources/engine.rb
183
185
  - lib/effective_resources/version.rb
184
186
  - lib/generators/effective_resources/install_generator.rb