wallaby 5.1.5 → 5.1.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +1 -1
  3. data/app/controllers/wallaby/abstract_resources_controller.rb +171 -187
  4. data/app/controllers/wallaby/application_controller.rb +15 -4
  5. data/app/controllers/wallaby/base_controller.rb +1 -3
  6. data/app/controllers/wallaby/secure_controller.rb +10 -6
  7. data/app/security/ability.rb +1 -0
  8. data/app/views/wallaby/resources/form/_float.html.erb +1 -1
  9. data/config/locales/wallaby.en.yml +5 -0
  10. data/lib/adaptors/wallaby/active_record.rb +1 -1
  11. data/lib/adaptors/wallaby/active_record/model_decorator.rb +17 -19
  12. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder.rb +7 -0
  13. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder/association_builder.rb +11 -0
  14. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder/polymorphic_builder.rb +10 -1
  15. data/lib/adaptors/wallaby/active_record/model_decorator/fields_builder/sti_builder.rb +14 -7
  16. data/lib/adaptors/wallaby/active_record/model_decorator/title_field_finder.rb +4 -0
  17. data/lib/adaptors/wallaby/active_record/model_finder.rb +4 -3
  18. data/lib/adaptors/wallaby/active_record/model_pagination_provider.rb +3 -6
  19. data/lib/adaptors/wallaby/active_record/model_service_provider.rb +12 -3
  20. data/lib/adaptors/wallaby/active_record/model_service_provider/normalizer.rb +16 -1
  21. data/lib/adaptors/wallaby/active_record/model_service_provider/permitter.rb +14 -2
  22. data/lib/adaptors/wallaby/active_record/model_service_provider/querier.rb +46 -1
  23. data/lib/adaptors/wallaby/active_record/model_service_provider/querier/transformer.rb +11 -5
  24. data/lib/adaptors/wallaby/active_record/model_service_provider/validator.rb +6 -0
  25. data/lib/concerns/wallaby/resources_helper_methods.rb +44 -0
  26. data/lib/decorators/wallaby/abstract_resource_decorator.rb +9 -4
  27. data/lib/errors/wallaby/model_not_found.rb +1 -0
  28. data/lib/errors/wallaby/resource_not_found.rb +1 -0
  29. data/lib/forms/wallaby/form_builder.rb +5 -3
  30. data/lib/helpers/wallaby/application_helper.rb +10 -1
  31. data/lib/helpers/wallaby/base_helper.rb +12 -4
  32. data/lib/helpers/wallaby/form_helper.rb +7 -4
  33. data/lib/helpers/wallaby/links_helper.rb +10 -7
  34. data/lib/helpers/wallaby/resources_helper.rb +2 -0
  35. data/lib/helpers/wallaby/secure_helper.rb +13 -7
  36. data/lib/helpers/wallaby/styling_helper.rb +1 -1
  37. data/lib/interfaces/wallaby/mode.rb +38 -16
  38. data/lib/interfaces/wallaby/model_decorator.rb +36 -27
  39. data/lib/interfaces/wallaby/model_decorator/field_helpers.rb +12 -2
  40. data/lib/interfaces/wallaby/model_finder.rb +1 -0
  41. data/lib/interfaces/wallaby/model_pagination_provider.rb +6 -5
  42. data/lib/interfaces/wallaby/model_service_provider.rb +10 -2
  43. data/lib/paginators/wallaby/abstract_resource_paginator.rb +6 -1
  44. data/lib/parsers/wallaby/parser.rb +1 -0
  45. data/lib/responders/wallaby/abstract_responder.rb +15 -2
  46. data/lib/routes/wallaby/resources_router.rb +4 -0
  47. data/lib/servicers/wallaby/abstract_model_servicer.rb +25 -1
  48. data/lib/servicers/wallaby/model_servicer.rb +1 -0
  49. data/lib/services/wallaby/link_options_normalizer.rb +13 -9
  50. data/lib/services/wallaby/lookup_context_wrapper.rb +11 -2
  51. data/lib/services/wallaby/map.rb +107 -69
  52. data/lib/services/wallaby/map/mode_mapper.rb +2 -0
  53. data/lib/services/wallaby/map/model_class_collector.rb +7 -0
  54. data/lib/services/wallaby/map/model_class_mapper.rb +15 -7
  55. data/lib/services/wallaby/partial_renderer.rb +7 -0
  56. data/lib/services/wallaby/prefixes_builder.rb +18 -0
  57. data/lib/services/wallaby/sorting/hash_builder.rb +7 -1
  58. data/lib/services/wallaby/sorting/link_builder.rb +6 -1
  59. data/lib/services/wallaby/sorting/next_builder.rb +23 -2
  60. data/lib/tree/wallaby/node.rb +2 -0
  61. data/lib/utils/wallaby/test_utils.rb +32 -0
  62. data/lib/utils/wallaby/utils.rb +51 -5
  63. data/lib/wallaby.rb +1 -0
  64. data/lib/wallaby/configuration.rb +9 -3
  65. data/lib/wallaby/configuration/features.rb +2 -1
  66. data/lib/wallaby/configuration/mapping.rb +66 -0
  67. data/lib/wallaby/configuration/metadata.rb +1 -0
  68. data/lib/wallaby/configuration/pagination.rb +2 -1
  69. data/lib/wallaby/engine.rb +4 -0
  70. data/lib/wallaby/version.rb +1 -1
  71. metadata +5 -3
  72. data/lib/tasks/wallaby_tasks.rake +0 -4
@@ -2,7 +2,7 @@ module Wallaby
2
2
  class ActiveRecord
3
3
  # Model finder
4
4
  class ModelFinder < ::Wallaby::ModelFinder
5
- # @return [Array] a list of ActiveRecord subclasses
5
+ # @return [Array<Class>] a list of ActiveRecord subclasses
6
6
  def all
7
7
  self.class.base.descendants.reject do |model_class|
8
8
  abstract?(model_class) || anonymous?(model_class) \
@@ -10,7 +10,8 @@ module Wallaby
10
10
  end.sort_by(&:to_s)
11
11
  end
12
12
 
13
- # Base class should be either ApplicationRecord or ActiveRecord::Base
13
+ # This is only for ActiveRecord
14
+ # @return [ApplicationRecord, ActiveRecord::Base] base ActiveRecord class
14
15
  def self.base
15
16
  return ::ApplicationRecord if defined? ::ApplicationRecord
16
17
  ::ActiveRecord::Base
@@ -29,7 +30,7 @@ module Wallaby
29
30
  # @param [Class] model class
30
31
  # @return [Boolean]
31
32
  def anonymous?(model_class)
32
- model_class.to_s.start_with? '#<Class'
33
+ Utils.anonymous_class? model_class
33
34
  end
34
35
 
35
36
  # Is model class the shcema migration class?
@@ -8,20 +8,17 @@ module Wallaby
8
8
  @collection.respond_to? :total_count
9
9
  end
10
10
 
11
- # Total count for the query
12
- # @return [Integer]
11
+ # @return [Integer] total count for the query
13
12
  def total
14
13
  @collection.total_count
15
14
  end
16
15
 
17
- # Page size
18
- # @return [Integer]
16
+ # @return [Integer] page size from parameters or configuration
19
17
  def page_size
20
18
  @params[:per].try(:to_i) || Wallaby.configuration.pagination.page_size
21
19
  end
22
20
 
23
- # Page number
24
- # @return [Integer]
21
+ # @return [Integer] page number from parameters
25
22
  def page_number
26
23
  [@params[:page].to_i, 1].max
27
24
  end
@@ -4,20 +4,29 @@ module Wallaby
4
4
  # @see Wallaby::ModelServiceProvider
5
5
  class ModelServiceProvider < ::Wallaby::ModelServiceProvider
6
6
  # @see Wallaby::ModelServiceProvider#permit
7
+ # @param params [ActionController::Parameters]
8
+ # @return [ActionController::Parameters] whitelisted parameters
7
9
  def permit(params)
8
10
  params.require(param_key).permit permitted_fields
9
11
  end
10
12
 
13
+ # NOTE: pagination free here.
14
+ # Since somewhere might need the collection without any pagination
11
15
  # @see Wallaby::ModelServiceProvider#collection
16
+ # @param params [ActionController::Parameters]
17
+ # @param authorizer [Ability] for now
18
+ # @return [ActiveRecord::Relation]
12
19
  def collection(params, authorizer)
13
- # NOTE: pagination free here
14
- # since somewhere might use it without pagination
15
20
  query = querier.search params
16
21
  query = query.order params[:sort] if params[:sort].present?
17
22
  query.accessible_by authorizer
18
23
  end
19
24
 
25
+ # Paginate
20
26
  # @see Wallaby::ModelServiceProvider#paginate
27
+ # @param query [ActiveRecord::Relation]
28
+ # @param params [ActionController::Parameters]
29
+ # @param [ActiveRecord::Relation] paginated query
21
30
  def paginate(query, params)
22
31
  per = params[:per] || Wallaby.configuration.pagination.page_size
23
32
  query = query.page params[:page] if query.respond_to? :page
@@ -60,7 +69,7 @@ module Wallaby
60
69
 
61
70
  protected
62
71
 
63
- # Save the active record
72
+ # Save the ActiveRecord
64
73
  # @param action [String] `create`, `update`
65
74
  # @param resource [Object]
66
75
  # @param _params [ActionController::Parameters]
@@ -1,12 +1,15 @@
1
1
  module Wallaby
2
2
  class ActiveRecord
3
3
  class ModelServiceProvider
4
- # Normalizer
4
+ # @private
5
+ # Normalize the values for a model
5
6
  class Normalizer
7
+ # @param model_decorator [Wallaby::ModelDecorator]
6
8
  def initialize(model_decorator)
7
9
  @model_decorator = model_decorator
8
10
  end
9
11
 
12
+ # @param params [ActionController::Parameters]
10
13
  def normalize(params)
11
14
  params.each do |field_name, values|
12
15
  type = @model_decorator.metadata_of(field_name)[:type]
@@ -16,6 +19,10 @@ module Wallaby
16
19
  end
17
20
  end
18
21
 
22
+ # Turn values into range
23
+ # @param params [ActionController::Parameters]
24
+ # @param field_name [String]
25
+ # @param values [Array]
19
26
  def normalize_range_values(params, field_name, values)
20
27
  normalized = Array(values).map(&:presence).compact
21
28
  params[field_name] =
@@ -24,6 +31,10 @@ module Wallaby
24
31
  end
25
32
  end
26
33
 
34
+ # Turn values into points
35
+ # @param params [ActionController::Parameters]
36
+ # @param field_name [String]
37
+ # @param values [Array]
27
38
  def normalize_point_values(params, field_name, values)
28
39
  normalized = Array(values).map(&:presence).compact
29
40
  params[field_name] =
@@ -31,6 +42,10 @@ module Wallaby
31
42
  values.map(&:to_f) || nil
32
43
  end
33
44
 
45
+ # Turn values into binary
46
+ # @param params [ActionController::Parameters]
47
+ # @param field_name [String]
48
+ # @param values [Object]
34
49
  def normalize_binary_values(params, field_name, values)
35
50
  params[field_name] =
36
51
  values.is_a?(::ActionDispatch::Http::UploadedFile) &&
@@ -1,12 +1,15 @@
1
1
  module Wallaby
2
2
  class ActiveRecord
3
3
  class ModelServiceProvider
4
+ # @private
4
5
  # Filter the params
5
6
  class Permitter
7
+ # @param model_decorator [Wallaby::ModelDecorator]
6
8
  def initialize(model_decorator)
7
9
  @model_decorator = model_decorator
8
10
  end
9
11
 
12
+ # @return [Array<String>] a list of field names of general types
10
13
  def simple_field_names
11
14
  field_names =
12
15
  non_range_fields.keys +
@@ -17,6 +20,7 @@ module Wallaby
17
20
  field_names.reject { |field_name| fields.include? field_name }
18
21
  end
19
22
 
23
+ # @return [Array<String>] a list of field names of range and association
20
24
  def compound_hashed_fields
21
25
  field_names =
22
26
  range_fields.keys +
@@ -26,33 +30,41 @@ module Wallaby
26
30
 
27
31
  protected
28
32
 
33
+ # @return [Array<String>] a list of field names that ain't association
29
34
  def non_association_fields
30
35
  @model_decorator
31
36
  .fields.reject { |_, metadata| metadata[:is_association] }
32
37
  end
33
38
 
39
+ # @return [Array<String>] a list of field names that ain't range
34
40
  def non_range_fields
35
41
  non_association_fields
36
42
  .reject { |_, metadata| /range|point/ =~ metadata[:type] }
37
43
  end
38
44
 
45
+ # @return [Array<String>] a list of range field names
39
46
  def range_fields
40
47
  non_association_fields.select do |_, metadata|
41
48
  /range|point/ =~ metadata[:type]
42
49
  end
43
50
  end
44
51
 
52
+ # @return [Array<String>] a list of association field names
45
53
  def association_fields
46
54
  @model_decorator.fields.select do |_, metadata|
47
- metadata[:is_association] &&
48
- !metadata[:has_scope] && !metadata[:is_through]
55
+ metadata[:is_association] \
56
+ && !metadata[:has_scope] && !metadata[:is_through]
49
57
  end
50
58
  end
51
59
 
60
+ # @return [Array<String>] a list of many association field names:
61
+ # - has_many
62
+ # - has_and_belongs_to_many
52
63
  def many_association_fields
53
64
  association_fields.select { |_, metadata| /many/ =~ metadata[:type] }
54
65
  end
55
66
 
67
+ # @return [Array<String>] a list of belongs_to association field names
56
68
  def belongs_to_fields
57
69
  association_fields.select do |_field_name, metadata|
58
70
  metadata[:type] == 'belongs_to'
@@ -1,15 +1,22 @@
1
1
  module Wallaby
2
2
  class ActiveRecord
3
3
  class ModelServiceProvider
4
+ # @private
4
5
  # Query builder
5
6
  class Querier
6
7
  TEXT_FIELDS = %w(string text citext longtext tinytext mediumtext).freeze
7
8
 
9
+ # @param model_decorator [Wallaby::ModelDecorator]
8
10
  def initialize(model_decorator)
9
11
  @model_decorator = model_decorator
10
12
  @model_class = @model_decorator.model_class
11
13
  end
12
14
 
15
+ # Pull out the query expression string from the parameter `q`,
16
+ # use parser to understand the expression, then use transformer to run
17
+ # SQL arel query.
18
+ # @param params [ActionController::Parameters]
19
+ # @return [ActiveRecord::Relation]
13
20
  def search(params)
14
21
  filter_name, keywords, field_queries = extract params
15
22
  scope = filtered_by filter_name
@@ -20,18 +27,24 @@ module Wallaby
20
27
 
21
28
  private
22
29
 
30
+ # @see Wallaby::Parser
23
31
  def parser
24
32
  @parser ||= Parser.new
25
33
  end
26
34
 
35
+ # @see Wallaby::ActiveRecord::ModelServiceProvider::Querier::Transformer
27
36
  def transformer
28
37
  @transformer ||= Transformer.new
29
38
  end
30
39
 
40
+ # @return [Arel::Table] arel table
31
41
  def table
32
42
  @model_class.arel_table
33
43
  end
34
44
 
45
+ # @param params [ActionController::Parameters]
46
+ # @return [Array<String, Array, Array>] a list of object for other
47
+ # method to use.
35
48
  def extract(params)
36
49
  expressions = to_expressions params
37
50
  keywords = expressions.select { |v| v.is_a? String }
@@ -40,33 +53,52 @@ module Wallaby
40
53
  [filter_name, keywords, field_queries]
41
54
  end
42
55
 
56
+ # @param params [ActionController::Parameters]
57
+ # @return [Array] a list of transformed operations
43
58
  def to_expressions(params)
44
59
  parsed = parser.parse(params[:q] || EMPTY_STRING)
45
60
  converted = transformer.apply parsed
46
61
  converted.is_a?(Array) ? converted : [converted]
47
62
  end
48
63
 
64
+ # Use the filter name to find out the scope in the following precedents:
65
+ # - scope from metadata
66
+ # - defined scope from the model
67
+ # - unscoped
68
+ # @param filter_name [String] filter name
69
+ # @return [ActiveRecord::Relation]
49
70
  def filtered_by(filter_name)
50
71
  valid_filter_name =
51
72
  Utils.find_filter_name(filter_name, @model_decorator.filters)
52
73
  scope = find_scope(valid_filter_name)
53
74
  if scope.blank? then unscoped
54
- elsif scope.respond_to?(:call) then @model_class.instance_exec(&scope)
75
+ elsif scope.is_a?(Proc) then @model_class.instance_exec(&scope)
55
76
  elsif @model_class.respond_to?(scope)
56
77
  @model_class.public_send(scope)
57
78
  else unscoped
58
79
  end
59
80
  end
60
81
 
82
+ # Find out the scope for given filter
83
+ # - from metadata
84
+ # - filter name itself
85
+ # @param filter_name [String] filter name
86
+ # @return [String]
61
87
  def find_scope(filter_name)
62
88
  filter = @model_decorator.filters[filter_name] || {}
63
89
  filter[:scope] || filter_name
64
90
  end
65
91
 
92
+ # Unscoped query
93
+ # @return [ActiveRecord::Relation]
66
94
  def unscoped
67
95
  @model_class.where nil
68
96
  end
69
97
 
98
+ # Search text for the text columns that appear in `index_field_names`
99
+ # @param filter_name [String] filter name
100
+ # @param query [ActiveRecord::Relation, nil]
101
+ # @return [ActiveRecord::Relation]
70
102
  def text_search(keywords, query = nil)
71
103
  return query unless keywords_check? keywords
72
104
  text_fields.each do |field_name|
@@ -80,6 +112,10 @@ module Wallaby
80
112
  query
81
113
  end
82
114
 
115
+ # Perform SQL query for the colon query (e.g. data:<2000-01-01)
116
+ # @param field_queries [Array]
117
+ # @param query [ActiveRecord::Relation]
118
+ # @return [ActiveRecord::Relation]
83
119
  def field_search(field_queries, query)
84
120
  return query unless field_check? field_queries
85
121
  field_queries.each do |exp|
@@ -89,6 +125,7 @@ module Wallaby
89
125
  query
90
126
  end
91
127
 
128
+ # @return [Array<String>] a list of text fields from `index_field_names`
92
129
  def text_fields
93
130
  @text_fields ||= begin
94
131
  index_field_names = @model_decorator.index_field_names.map(&:to_s)
@@ -99,6 +136,10 @@ module Wallaby
99
136
  end
100
137
  end
101
138
 
139
+ # @param keywords [Array<String>] a list of keywords
140
+ # @return [Boolean] false when keywords are empty
141
+ # true when text fields for query exist
142
+ # otherwise, raise exception
102
143
  def keywords_check?(keywords)
103
144
  return false if keywords.blank?
104
145
  return true if text_fields.present?
@@ -106,6 +147,10 @@ module Wallaby
106
147
  raise UnprocessableEntity, message
107
148
  end
108
149
 
150
+ # @param field_queries [Array]
151
+ # @return [Boolean] false when field queries are blank
152
+ # true when the fields used are valid (exist in `fields`)
153
+ # otherwise, raise exception
109
154
  def field_check?(field_queries)
110
155
  return false if field_queries.blank?
111
156
  fields = field_queries.map { |exp| exp[:left] }
@@ -2,6 +2,7 @@ module Wallaby
2
2
  class ActiveRecord
3
3
  class ModelServiceProvider
4
4
  class Querier
5
+ # @private
5
6
  # Build up query using the results
6
7
  class Transformer < Parslet::Transform
7
8
  SIMPLE_OPERATORS = {
@@ -32,28 +33,33 @@ module Wallaby
32
33
  ':!()' => :not_between
33
34
  }.freeze
34
35
 
36
+ # For single keyword
35
37
  rule keyword: simple(:value) do
36
38
  value.try :to_str
37
39
  end
38
40
 
41
+ # For multiple keywords
39
42
  rule keyword: sequence(:value) do
40
43
  value.presence.try :map, :to_str
41
44
  end
42
45
 
46
+ # For operators
43
47
  rule left: simple(:left), op: simple(:op), right: simple(:right) do
44
48
  oped = op.try :to_str
45
49
  operator = SIMPLE_OPERATORS[oped]
46
50
  # skip if the operator is unknown
47
51
  next unless operator
48
52
  lefted = left.try :to_str
49
- convert = case oped
50
- when ':~', ':!~' then "%#{right}%"
51
- when ':^', ':!^' then "#{right}%"
52
- when ':$', ':!$' then "%#{right}"
53
- end
53
+ convert =
54
+ case oped
55
+ when ':~', ':!~' then "%#{right}%"
56
+ when ':^', ':!^' then "#{right}%"
57
+ when ':$', ':!$' then "%#{right}"
58
+ end
54
59
  { left: lefted, op: operator, right: convert || right }
55
60
  end
56
61
 
62
+ # For operators that have multiple items
57
63
  rule left: simple(:left), op: simple(:op), right: sequence(:right) do
58
64
  oped = op.try :to_str
59
65
  operator = SEQUENCE_OPERATORS[oped]
@@ -1,12 +1,16 @@
1
1
  module Wallaby
2
2
  class ActiveRecord
3
3
  class ModelServiceProvider
4
+ # @private
4
5
  # Validator
5
6
  class Validator
7
+ # @param model_decorator [Wallaby::ModelDecorator]
6
8
  def initialize(model_decorator)
7
9
  @model_decorator = model_decorator
8
10
  end
9
11
 
12
+ # @param resource [Object] resource object
13
+ # @return [Boolean] whether the resource object is valid
10
14
  def valid?(resource)
11
15
  resource.attributes.each do |field_name, values|
12
16
  metadata = @model_decorator.fields[field_name]
@@ -18,6 +22,8 @@ module Wallaby
18
22
 
19
23
  private
20
24
 
25
+ # @param values [Array]
26
+ # @return [Boolean] whether the values are valid range values
21
27
  def valid_range_type?(values, metadata)
22
28
  !metadata \
23
29
  || !%w(daterange tsrange tstzrange).include?(metadata[:type]) \
@@ -0,0 +1,44 @@
1
+ module Wallaby
2
+ # This is a collection of the helper methods used in controller and helper
3
+ module ResourcesHelperMethods
4
+ extend ActiveSupport::Concern
5
+
6
+ protected
7
+
8
+ # Shorthand of params[:id]
9
+ # @return [String, nil] ID param
10
+ def resource_id
11
+ params[:id]
12
+ end
13
+
14
+ # @return [#each] a collection of all the records
15
+ def collection
16
+ @collection ||= paginate current_model_service.collection params
17
+ end
18
+
19
+ # @return [Object] either persisted or unpersisted resource instance
20
+ def resource
21
+ @resource ||= begin
22
+ # white-listed params
23
+ whitelisted = action_name.in?(SAVE_ACTIONS) ? resource_params : {}
24
+ if resource_id.present?
25
+ current_model_service.find resource_id, whitelisted
26
+ else
27
+ current_model_service.new whitelisted
28
+ end
29
+ end
30
+ end
31
+
32
+ # @return [Wallaby::ModelDecorator, nil] current model decorator
33
+ def current_model_decorator
34
+ @current_model_decorator ||= helpers.model_decorator current_model_class
35
+ end
36
+
37
+ # A wrapper method for authorizer
38
+ # @todo to add support to pundit in the future
39
+ # @return [Ability]
40
+ def authorizer
41
+ current_ability
42
+ end
43
+ end
44
+ end