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 +4 -4
- data/README.md +1 -1
- data/app/controllers/concerns/effective/crud_controller.rb +7 -0
- data/app/controllers/concerns/effective/crud_controller/actions.rb +7 -4
- data/app/controllers/concerns/effective/crud_controller/permitted_params.rb +8 -5
- data/app/controllers/concerns/effective/crud_controller/respond.rb +6 -1
- data/app/controllers/concerns/effective/crud_controller/save.rb +4 -0
- data/app/controllers/concerns/effective/flash_messages.rb +16 -3
- data/app/helpers/effective_resources_helper.rb +37 -19
- data/app/models/concerns/acts_as_slugged.rb +4 -2
- data/app/models/concerns/effective_devise_user.rb +167 -0
- data/app/models/effective/attribute.rb +13 -0
- data/app/models/effective/resource.rb +8 -0
- data/app/models/effective/resources/actions.rb +30 -66
- data/app/models/effective/resources/forms.rb +2 -1
- data/app/models/effective/resources/init.rb +20 -15
- data/app/models/effective/resources/klass.rb +2 -0
- data/app/models/effective/resources/naming.rb +20 -1
- data/app/models/effective/resources/paths.rb +16 -0
- data/lib/effective_resources.rb +8 -11
- data/lib/effective_resources/effective_gem.rb +42 -0
- data/lib/effective_resources/engine.rb +2 -0
- data/lib/effective_resources/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a4d2d6652da6f85e884ceb383ff012fda17863ad4b84c7cfaf5fec76a8632af
|
4
|
+
data.tar.gz: 155b5a31cc14908ea944978d940d550227bcb823e14bbc42e6f34108ece06c35
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4227d0379585019dafae032ac3300cc98ceed87f81bfcb45a35ab9b10c5e15f18dd7e6420b1973bef3e1567dd8ac71d78049b6ec6711153899568ed0d3398fc3
|
7
|
+
data.tar.gz: 700279f4126eefafe9b14d06b4dea16b9cf49c1f2b21fd472a17a80e3e2863d2f5d6318f481d3f862653d95f753960f5a0fbc79802301e3879422cec89866df8
|
data/README.md
CHANGED
@@ -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
|
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
|
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
|
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
|
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(:#{
|
17
|
+
Rails.logger.info "params.require(:#{permitted_name}).permit(#{permitted_params.to_s[1...-1]})"
|
17
18
|
end
|
18
19
|
|
19
|
-
params.require(
|
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(:#{
|
30
|
+
Rails.logger.info "params.require(:#{permitted_name}).permit!"
|
28
31
|
end
|
29
32
|
|
30
|
-
if params[
|
31
|
-
params.require(
|
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
|
-
|
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 ||=
|
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 |
|
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}
|
110
|
+
"effective/resource/actions_#{partial}"
|
111
111
|
else
|
112
|
-
|
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(
|
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
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
render
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
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
|
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 =
|
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
|
11
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
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
|
-
|
25
|
-
|
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
|
-
|
40
|
-
|
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.
|
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
|
-
|
123
|
+
(actions & CRUD_ACTIONS)
|
141
124
|
end
|
142
125
|
|
143
126
|
# GET actions
|
144
127
|
def collection_actions
|
145
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
#
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
klass
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
-
|
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
|
data/lib/effective_resources.rb
CHANGED
@@ -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
|
-
|
7
|
-
|
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
|
-
@
|
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 *@
|
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
|
-
|
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
|
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
|
+
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-
|
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
|