effective_resources 0.9.5 → 0.10.0

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.
Files changed (38) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/concerns/effective/crud_controller.rb +23 -546
  3. data/app/controllers/concerns/effective/crud_controller/actions.rb +289 -0
  4. data/app/controllers/concerns/effective/crud_controller/dsl.rb +81 -0
  5. data/app/controllers/concerns/effective/crud_controller/paths.rb +87 -0
  6. data/app/controllers/concerns/effective/crud_controller/permitted_params.rb +66 -0
  7. data/app/controllers/concerns/effective/crud_controller/save.rb +77 -0
  8. data/app/controllers/concerns/effective/crud_controller/submits.rb +122 -0
  9. data/app/helpers/effective_resources_helper.rb +51 -52
  10. data/app/helpers/effective_resources_private_helper.rb +69 -0
  11. data/app/models/concerns/effective_resource.rb +24 -0
  12. data/app/models/effective/model_reader.rb +29 -0
  13. data/app/models/effective/resource.rb +6 -2
  14. data/app/models/effective/resources/actions.rb +10 -10
  15. data/app/models/effective/resources/associations.rb +5 -0
  16. data/app/models/effective/resources/attributes.rb +40 -27
  17. data/app/models/effective/resources/controller.rb +81 -0
  18. data/app/models/effective/resources/forms.rb +0 -51
  19. data/app/models/effective/resources/init.rb +19 -17
  20. data/app/models/effective/resources/klass.rb +6 -8
  21. data/app/models/effective/resources/model.rb +23 -0
  22. data/app/models/effective/resources/naming.rb +7 -3
  23. data/app/models/effective/resources/paths.rb +4 -63
  24. data/app/models/effective/resources/relation.rb +4 -1
  25. data/app/models/effective/resources/sql.rb +1 -1
  26. data/app/views/application/create.js.erb +1 -1
  27. data/app/views/application/edit.html.haml +2 -2
  28. data/app/views/application/index.html.haml +1 -1
  29. data/app/views/application/member_action.js.erb +1 -1
  30. data/app/views/application/new.html.haml +3 -1
  31. data/app/views/application/show.html.haml +1 -1
  32. data/app/views/application/update.js.erb +1 -1
  33. data/app/views/effective/resource/_actions.html.haml +3 -32
  34. data/app/views/effective/resource/_actions_dropleft.html.haml +4 -26
  35. data/app/views/effective/resource/_actions_glyphicons.html.haml +4 -10
  36. data/lib/effective_resources/engine.rb +1 -0
  37. data/lib/effective_resources/version.rb +1 -1
  38. metadata +14 -3
@@ -0,0 +1,24 @@
1
+ # EffectiveResource
2
+ #
3
+ # Mark your model with 'effective_resource'
4
+
5
+ module EffectiveResource
6
+ extend ActiveSupport::Concern
7
+
8
+ module ActiveRecord
9
+ def effective_resource(options = nil, &block)
10
+ return @_effective_resource unless block_given?
11
+
12
+ include ::EffectiveResource
13
+ @_effective_resource = Effective::Resource.new(self, &block)
14
+ end
15
+ end
16
+
17
+ included do
18
+ end
19
+
20
+ module ClassMethods
21
+ end
22
+
23
+ end
24
+
@@ -0,0 +1,29 @@
1
+ module Effective
2
+ class ModelReader
3
+ DATATYPES = [:binary, :boolean, :date, :datetime, :decimal, :float, :hstore, :inet, :integer, :string, :text]
4
+
5
+ attr_reader :attributes
6
+
7
+ def initialize(&block)
8
+ @attributes = {}
9
+ instance_exec(&block)
10
+ end
11
+
12
+ def method_missing(m, *args, &block)
13
+ raise "#{m} has already been defined" if attributes[m]
14
+
15
+ if m == :timestamps
16
+ attributes[:created_at] = [:datetime]
17
+ attributes[:updated_at] = [:datetime]
18
+ return
19
+ end
20
+
21
+ unless DATATYPES.include?(args.first)
22
+ raise "expected first argument to be a datatype. Try name :string"
23
+ end
24
+
25
+ attributes[m] = args
26
+ end
27
+
28
+ end
29
+ end
@@ -3,18 +3,22 @@ module Effective
3
3
  include Effective::Resources::Actions
4
4
  include Effective::Resources::Associations
5
5
  include Effective::Resources::Attributes
6
+ include Effective::Resources::Controller
6
7
  include Effective::Resources::Init
7
8
  include Effective::Resources::Instance
8
9
  include Effective::Resources::Forms
9
10
  include Effective::Resources::Klass
11
+ include Effective::Resources::Model
10
12
  include Effective::Resources::Naming
11
13
  include Effective::Resources::Paths
12
14
  include Effective::Resources::Relation
13
15
  include Effective::Resources::Sql
14
16
 
15
17
  # post, Post, Admin::Post, admin::Post, admin/posts, admin/post, admin/effective::post
16
- def initialize(input, namespace: nil)
17
- _initialize(input, namespace: namespace)
18
+ def initialize(input, namespace: nil, &block)
19
+ _initialize_input(input, namespace: namespace)
20
+ _initialize_model(&block) if block_given?
21
+ self
18
22
  end
19
23
 
20
24
  def to_s
@@ -5,8 +5,8 @@ module Effective
5
5
  # This was written for the Edit actions fallback templates and Datatables
6
6
  # Effective::Resource.new('admin/posts').routes[:index]
7
7
  def routes
8
- @_routes ||= (
9
- matches = [[namespace, plural_name].compact.join('/'), [namespace, name].compact.join('/')]
8
+ @routes ||= (
9
+ matches = [[namespace, plural_name].compact.join('/'.freeze), [namespace, name].compact.join('/'.freeze)]
10
10
 
11
11
  routes_engine.routes.routes.select do |route|
12
12
  matches.any? { |match| match == route.defaults[:controller] }
@@ -19,7 +19,7 @@ module Effective
19
19
  # Effective::Resource.new('effective/order', namespace: :admin)
20
20
  def routes_engine
21
21
  case class_name
22
- when 'Effective::Order'
22
+ when 'Effective::Order'.freeze
23
23
  EffectiveOrders::Engine
24
24
  else
25
25
  Rails.application
@@ -30,7 +30,7 @@ module Effective
30
30
  # This will return empty for create, update and destroy
31
31
  def action_path_helper(action)
32
32
  return unless routes[action]
33
- return (routes[action].name + '_path') if routes[action].name.present?
33
+ return (routes[action].name + '_path'.freeze) if routes[action].name.present?
34
34
  end
35
35
 
36
36
  # Effective::Resource.new('admin/posts').action_path(:edit, Post.last) => '/admin/posts/3/edit'
@@ -107,29 +107,29 @@ module Effective
107
107
 
108
108
  # Same as controller_path in the view
109
109
  def controller_path
110
- [namespace, plural_name].compact * '/'
110
+ [namespace, plural_name].compact * '/'.freeze
111
111
  end
112
112
 
113
113
  private
114
114
 
115
115
  def is_member_route?(route)
116
- (route.path.required_names || []).include?('id')
116
+ (route.path.required_names || []).include?('id'.freeze)
117
117
  end
118
118
 
119
119
  def is_collection_route?(route)
120
- (route.path.required_names || []).include?('id') == false
120
+ (route.path.required_names || []).include?('id'.freeze) == false
121
121
  end
122
122
 
123
123
  def is_get_route?(route)
124
- route.verb.to_s.include?('GET')
124
+ route.verb.to_s.include?('GET'.freeze)
125
125
  end
126
126
 
127
127
  def is_delete_route?(route)
128
- route.verb.to_s.include?('DELETE')
128
+ route.verb.to_s.include?('DELETE'.freeze)
129
129
  end
130
130
 
131
131
  def is_post_route?(route)
132
- ['POST', 'PUT', 'PATCH'].any? { |verb| route.verb == verb }
132
+ ['POST', 'PUT', 'PATCH'].freeze.any? { |verb| route.verb == verb }
133
133
  end
134
134
  end
135
135
  end
@@ -11,6 +11,11 @@ module Effective
11
11
  @belong_tos ||= klass.reflect_on_all_associations(:belongs_to)
12
12
  end
13
13
 
14
+ # author_id, post_id
15
+ def belong_tos_ids
16
+ belong_tos.map { |ass| (ass.options[:foreign_key] || "#{ass.name}_id").to_sym }
17
+ end
18
+
14
19
  def has_ones
15
20
  return [] unless klass.respond_to?(:reflect_on_all_associations)
16
21
  @has_ones ||= klass.reflect_on_all_associations(:has_one)
@@ -2,12 +2,34 @@ module Effective
2
2
  module Resources
3
3
  module Attributes
4
4
 
5
+ # This is the attributes as defined by ActiveRecord table
6
+ # { :name => [:string], ... }
5
7
  def attributes
6
- (klass_attributes.presence || written_attributes.presence)
8
+ (klass_attributes.presence || model_attributes.presence)
7
9
  end
8
10
 
9
- def attribute_names
10
- attributes.map { |attribute| attribute.name }
11
+ # The attributes for each belongs_to
12
+ # { :client_id => [:integer], ... }
13
+ def belong_tos_attributes
14
+ belong_tos.inject({}) do |h, ass|
15
+ unless ass.foreign_key == 'site_id' && ass.respond_to?(:acts_as_site_specific)
16
+ h[ass.foreign_key.to_sym] = [:integer]
17
+ end; h
18
+ end
19
+ end
20
+
21
+ # This is the attributes as defined by the effective_resources do .. end block
22
+ # { :name => [:string, { permitted: false }], ... }
23
+ def model_attributes
24
+ model ? model.attributes : {}
25
+ end
26
+
27
+ # Used by effective_crud_controller to generate the permitted params
28
+ def permitted_attributes
29
+ id = {klass.primary_key.to_sym => [:integer]}
30
+ bts = belong_tos_ids.inject({}) { |h, ass| h[ass] = [:integer]; h }
31
+
32
+ id.merge(bts).merge(model_attributes)
11
33
  end
12
34
 
13
35
  # All attributes from the klass, sorted as per attributes block.
@@ -19,46 +41,37 @@ module Effective
19
41
  names = attributes.keys - belong_tos.map { |reference| reference.foreign_key }
20
42
  names = names - [klass.primary_key, 'created_at', 'updated_at'] unless all
21
43
 
22
- attributes = names.map do |name|
44
+ attributes = names.inject({}) do |h, name|
23
45
  if klass.respond_to?(:column_for_attribute) # Rails 4+
24
- Effective::Attribute.new(name, klass.column_for_attribute(name).type)
46
+ h[name.to_sym] = [klass.column_for_attribute(name).type]
25
47
  else
26
- Effective::Attribute.new(name, klass.columns_hash[name].type)
27
- end
48
+ h[name.to_sym] = [klass.columns_hash[name].type]
49
+ end; h
28
50
  end
29
51
 
30
- sort_by_written_attributes(attributes)
52
+ sort_by_model_attributes(attributes)
31
53
  end
32
54
 
33
- def belong_tos_attributes
34
- belong_tos.map do |reference|
35
- unless reference.foreign_key == 'site_id' && klass.respond_to?(:acts_as_site_specific)
36
- Effective::Attribute.new(reference.foreign_key, :integer)
37
- end
38
- end.compact.sort
39
- end
55
+ private
40
56
 
41
- def written_attributes
42
- _initialize_written if @written_attributes.nil?
43
- @written_attributes
44
- end
57
+ def sort_by_model_attributes(attributes)
58
+ return attributes unless model_attributes.present?
45
59
 
46
- private
60
+ keys = model_attributes.keys
47
61
 
48
- def sort_by_written_attributes(attributes)
49
- attributes.sort do |a, b|
62
+ attributes.sort do |(a, _), (b, _)|
50
63
  index = nil
51
64
 
52
- index ||= if written_attributes.include?(a) && written_attributes.include?(b)
53
- written_attributes.index(a) <=> written_attributes.index(b)
54
- elsif written_attributes.include?(a) && !written_attributes.include?(b)
65
+ index ||= if model_attributes.key?(a) && model_attributes.key?(b)
66
+ keys.index(a) <=> keys.index(b)
67
+ elsif model_attributes.key?(a) && !model_attributes.include?(b)
55
68
  -1
56
- elsif !written_attributes.include?(a) && written_attributes.include?(b)
69
+ elsif !model_attributes.key?(a) && model_attributes.key?(b)
57
70
  1
58
71
  end
59
72
 
60
73
  index || a <=> b
61
- end
74
+ end.to_h
62
75
  end
63
76
 
64
77
  end
@@ -0,0 +1,81 @@
1
+ module Effective
2
+ module Resources
3
+ module Controller
4
+
5
+ # Used by effective_form_submit
6
+ # The actions we would use to commit. For link_to
7
+ # { 'Save': { action: :save }, 'Continue': { action: :save }, 'Add New': { action: :save }, 'Approve': { action: :approve } }
8
+ # Saves a list of commit actions...
9
+ def submits
10
+ {}.tap do |submits|
11
+ if (actions.find { |a| a == :create } || actions.find { |a| a == :update })
12
+ submits['Save'] = { action: :save, default: true }
13
+ end
14
+
15
+ if actions.find { |a| a == :index }
16
+ submits['Continue'] = { action: :save, redirect: :index, default: true }
17
+ end
18
+
19
+ if actions.find { |a| a == :new }
20
+ submits['Add New'] = { action: :save, redirect: :new, default: true }
21
+ end
22
+ end
23
+ end
24
+
25
+ def buttons
26
+ {}.tap do |buttons|
27
+ member_get_actions.each do |action| # default true means it will be overwritten by dsl methods
28
+ buttons[action.to_s.titleize] = { action: action, default: true }
29
+ end
30
+
31
+ (member_post_actions - crud_actions).each do |action| # default true means it will be overwritten by dsl methods
32
+ buttons[action.to_s.titleize] = { action: action, default: true }
33
+ end
34
+
35
+ member_delete_actions.each do |action|
36
+ buttons[action == :destroy ? 'Delete' : action.to_s.titleize] = { action: action, default: true }
37
+ end
38
+
39
+ if collection_get_actions.find { |a| a == :index }
40
+ buttons["All #{human_plural_name}".titleize] = { action: :index, default: true }
41
+ end
42
+
43
+ if collection_get_actions.find { |a| a == :new }
44
+ buttons["New #{human_name}".titleize] = { action: :new, default: true }
45
+ end
46
+
47
+ (collection_get_actions - crud_actions).each do |action|
48
+ buttons[action.to_s.titleize] = { action: action, default: true }
49
+ end
50
+ end
51
+ end
52
+
53
+ # This is the fallback for render_resource_actions when no actions are specified
54
+ # It is used by datatables
55
+ def resource_actions
56
+ {}.tap do |actions|
57
+ (member_get_actions & crud_actions).each do |action|
58
+ actions[action.to_s.titleize] = { action: action, default: true }
59
+ end
60
+
61
+ (member_get_actions - crud_actions).each do |action|
62
+ actions[action.to_s.titleize] = { action: action, default: true }
63
+ end
64
+
65
+ (member_post_actions - crud_actions).each do |action|
66
+ actions[action.to_s.titleize] = { action: action, default: true }
67
+ end
68
+
69
+ member_delete_actions.each do |action|
70
+ actions[action == :destroy ? 'Delete' : action.to_s.titleize] = { action: action, default: true }
71
+ end
72
+ end
73
+ end
74
+
75
+ def ons
76
+ {}
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -2,57 +2,6 @@ module Effective
2
2
  module Resources
3
3
  module Forms
4
4
 
5
- # Used by effective_form_submit
6
- # The actions we would use to commit. For link_to
7
- # { 'Save': { action: :save }, 'Continue': { action: :save }, 'Add New': { action: :save }, 'Approve': { action: :approve } }
8
- # Saves a list of commit actions...
9
- def submits
10
- @submits ||= {}.tap do |submits|
11
- if (actions.find { |a| a == :create } || actions.find { |a| a == :update })
12
- submits['Save'] = { action: :save, default: true, class: 'btn btn-primary' }
13
- end
14
-
15
- (member_post_actions - crud_actions).each do |action| # default true means it will be overwritten by dsl methods
16
- submits[action.to_s.titleize] = { action: action, default: true, class: 'btn btn-primary' }
17
- end
18
-
19
- if actions.find { |a| a == :index }
20
- submits['Continue'] = { action: :save, default: true, redirect: :index }
21
- end
22
-
23
- if actions.find { |a| a == :new }
24
- submits['Add New'] = { action: :save, default: true, redirect: :new }
25
- end
26
- end
27
- end
28
-
29
- # Here we look at all available (class level) member actions, see which ones apply to the current resource
30
- # This feeds into the helper simple_form_submit(f)
31
- # Returns a Hash of {'Save': {class: 'btn btn-primary'}, 'Approve': {class: 'btn btn-secondary'}}
32
- def submits_for(obj, controller:)
33
- submits.select do |commit, args|
34
- args[:class] = args[:class].to_s
35
-
36
- action = (args[:action] == :save ? (obj.new_record? ? :create : :update) : args[:action])
37
-
38
- (args.key?(:if) ? obj.instance_exec(&args[:if]) : true) &&
39
- (args.key?(:unless) ? !obj.instance_exec(&args[:unless]) : true) &&
40
- EffectiveResources.authorized?(controller, action, obj)
41
- end.transform_values.with_index do |opts, index|
42
- if opts[:class].blank?
43
- if index == 0
44
- opts[:class] = 'btn btn-primary'
45
- elsif defined?(EffectiveBootstrap)
46
- opts[:class] = 'btn btn-secondary'
47
- else
48
- opts[:class] = 'btn btn-default'
49
- end
50
- end
51
-
52
- opts.except(:action, :default, :if, :unless, :redirect)
53
- end
54
- end
55
-
56
5
  # Used by datatables
57
6
  def search_form_field(name, type = nil)
58
7
  case (type || sql_type(name))
@@ -4,12 +4,14 @@ module Effective
4
4
 
5
5
  private
6
6
 
7
- def _initialize(input, namespace: nil)
8
- @namespaces = (namespace.kind_of?(String) ? namespace.split('/') : Array(namespace)) if namespace
7
+ def _initialize_input(input, namespace: nil)
8
+ @initialized_name = input
9
9
 
10
10
  @model_klass = case input
11
11
  when String, Symbol
12
12
  _klass_by_name(input)
13
+ when Class
14
+ input
13
15
  when ActiveRecord::Relation
14
16
  input.klass
15
17
  when (ActiveRecord::Reflection::AbstractReflection rescue :nil)
@@ -19,14 +21,25 @@ module Effective
19
21
  when ActionDispatch::Journey::Route
20
22
  @initialized_name = input.defaults[:controller]
21
23
  _klass_by_name(input.defaults[:controller])
22
- when Class
23
- input
24
24
  when nil ; raise 'expected a string or class'
25
25
  else ; _klass_by_name(input.class.name)
26
26
  end
27
27
 
28
- @relation = _initialize_relation(input)
29
- @instance = input if (klass && input.instance_of?(klass))
28
+ if namespace
29
+ @namespaces = (namespace.kind_of?(String) ? namespace.split('/') : Array(namespace))
30
+ end
31
+
32
+ if input.kind_of?(ActiveRecord::Relation)
33
+ @relation = input
34
+ end
35
+
36
+ if input.kind_of?(ActiveRecord::Reflection::MacroReflection) && input.scope
37
+ @relation = klass.where(nil).merge(input.scope)
38
+ end
39
+
40
+ if klass && input.instance_of?(klass)
41
+ @instance = input
42
+ end
30
43
  end
31
44
 
32
45
  def _klass_by_name(input)
@@ -50,17 +63,6 @@ module Effective
50
63
  nil
51
64
  end
52
65
 
53
- def _initialize_relation(input)
54
- return nil unless klass && klass.respond_to?(:where)
55
-
56
- case input
57
- when ActiveRecord::Relation
58
- input
59
- when ActiveRecord::Reflection::MacroReflection
60
- klass.where(nil).merge(input.scope) if input.scope
61
- end || klass.where(nil)
62
- end
63
-
64
66
  # Lazy initialized
65
67
  def _initialize_written
66
68
  @written_attributes = []