effective_resources 0.2.2 → 0.2.3
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/app/models/effective/attribute.rb +93 -23
- data/app/models/effective/resource.rb +3 -0
- data/app/models/effective/resources/associations.rb +46 -2
- data/app/models/effective/resources/attributes.rb +4 -4
- data/app/models/effective/resources/forms.rb +59 -0
- data/app/models/effective/resources/init.rb +16 -3
- data/app/models/effective/resources/relation.rb +215 -0
- data/app/models/effective/resources/sql.rb +100 -0
- data/lib/effective_resources/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a75f5adcc4a20f0f1a18cd2496657b439934e596
|
4
|
+
data.tar.gz: c72e11e7e702d75cafa264dab8c538f6e61962b1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5027571db1c5f1ebad0870f99a1606dc7bd8cc4e5282d472656f2aaf4d9d010a71d26ff7dadb55fd7386906d8eedb67fd1ee24e08e8133c07b8ec18045caf2cb
|
7
|
+
data.tar.gz: 96ed7d36303ad72394afa8dc2c7c6533500abca4e85fbdb56d3868b91897446f1fb36f9af4653bb6ca610c78f159a2568cb95a76cfb79248a07c027255d27808
|
@@ -1,13 +1,12 @@
|
|
1
1
|
module Effective
|
2
2
|
class Attribute
|
3
|
-
attr_accessor :name, :type
|
3
|
+
attr_accessor :name, :type, :klass
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def self.parse(input)
|
5
|
+
# This parses the written attributes
|
6
|
+
def self.parse_written(input)
|
8
7
|
input = input.to_s
|
9
8
|
|
10
|
-
if (scanned = input.scan(
|
9
|
+
if (scanned = input.scan(/^\W*(\w+)\W*:(\w+)/).first).present?
|
11
10
|
new(*scanned)
|
12
11
|
elsif input.start_with?('#')
|
13
12
|
new(nil)
|
@@ -16,31 +15,102 @@ module Effective
|
|
16
15
|
end
|
17
16
|
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
@type = (type.presence || :string).to_sym
|
22
|
-
@options = options
|
23
|
-
end
|
18
|
+
# This kind of follows the rails GeneratedAttribute method.
|
19
|
+
# In that case it will be initialized with a name and a type.
|
24
20
|
|
25
|
-
|
26
|
-
|
21
|
+
# We also use this class to do value parsing in Datatables.
|
22
|
+
# In that case it will be initialized with just a 'name'
|
23
|
+
def initialize(obj, type = nil, klass: nil)
|
24
|
+
@klass = klass
|
25
|
+
|
26
|
+
if obj.present? && type.present?
|
27
|
+
@name = obj.to_s
|
28
|
+
@type = type.to_sym
|
29
|
+
end
|
30
|
+
|
31
|
+
@type ||= (
|
32
|
+
case obj
|
33
|
+
when :boolean ; :boolean
|
34
|
+
when :date ; :date
|
35
|
+
when :datetime ; :datetime
|
36
|
+
when :decimal ; :decimal
|
37
|
+
when :integer ; :integer
|
38
|
+
when :price ; :price
|
39
|
+
when :nil ; :nil
|
40
|
+
when :string ; :string
|
41
|
+
when FalseClass ; :boolean
|
42
|
+
when Fixnum ; :integer
|
43
|
+
when Float ; :decimal
|
44
|
+
when NilClass ; :nil
|
45
|
+
when String ; :string
|
46
|
+
when TrueClass ; :boolean
|
47
|
+
when ActiveSupport::TimeWithZone ; :datetime
|
48
|
+
when :belongs_to ; :belongs_to
|
49
|
+
when :belongs_to_polymorphic ; :belongs_to_polymorphic
|
50
|
+
when :has_many ; :has_many
|
51
|
+
when :has_and_belongs_to_many ; :has_and_belongs_to_many
|
52
|
+
when :has_one ; :has_one
|
53
|
+
when :effective_addresses ; :effective_addresses
|
54
|
+
when :effective_obfuscation ; :effective_obfuscation
|
55
|
+
when :effective_roles ; :effective_roles
|
56
|
+
else
|
57
|
+
raise 'unsupported type'
|
58
|
+
end
|
59
|
+
)
|
27
60
|
end
|
28
61
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
62
|
+
def parse(value, name: nil)
|
63
|
+
case type
|
64
|
+
when :boolean
|
65
|
+
[true, 'true', 't', '1'].include?(value)
|
66
|
+
when :date, :datetime
|
67
|
+
date = Time.zone.local(*value.to_s.scan(/(\d+)/).flatten)
|
68
|
+
name.to_s.start_with?('end_') ? date.end_of_day : date
|
69
|
+
when :decimal
|
70
|
+
(value.kind_of?(String) ? value.gsub(/[^0-9|\.]/, '') : value).to_f
|
71
|
+
when :effective_obfuscation
|
72
|
+
klass.respond_to?(:deobfuscate) ? klass.deobfuscate(value) : value.to_s
|
73
|
+
when :effective_roles
|
74
|
+
EffectiveRoles.roles_for(value)
|
75
|
+
when :integer
|
76
|
+
(value.kind_of?(String) ? value.gsub(/\D/, '') : value).to_i
|
77
|
+
when :nil
|
78
|
+
value.presence
|
79
|
+
when :price
|
80
|
+
(value.kind_of?(Integer) ? value : (value.to_s.gsub(/[^0-9|\.]/, '').to_f * 100.0)).to_i
|
81
|
+
when :string
|
82
|
+
value.to_s
|
83
|
+
when :belongs_to_polymorphic
|
84
|
+
value.to_s
|
85
|
+
when :belongs_to, :has_many, :has_and_belongs_to_many, :has_one # Returns an Array of ints, an Int or String
|
86
|
+
if value.kind_of?(Integer) || value.kind_of?(Array)
|
87
|
+
value
|
88
|
+
else
|
89
|
+
digits = value.to_s.gsub(/[^0-9|,]/, '') # '87' or '87,254,300' or 'something'
|
90
|
+
|
91
|
+
if digits == value && digits.index(',').present?
|
92
|
+
if klass.respond_to?(:deobfuscate)
|
93
|
+
digits.split(',').map { |str| klass.deobfuscate(str).to_i }
|
94
|
+
else
|
95
|
+
digits.split(',').map { |str| str.to_i }
|
96
|
+
end
|
97
|
+
elsif digits == value
|
98
|
+
klass.respond_to?(:deobfuscate) ? klass.deobfuscate(digits).to_i : digits.to_i
|
99
|
+
else
|
100
|
+
value.to_s
|
101
|
+
end
|
102
|
+
end
|
103
|
+
else
|
104
|
+
raise 'unsupported type'
|
39
105
|
end
|
40
106
|
end
|
41
107
|
|
108
|
+
def to_s
|
109
|
+
name
|
110
|
+
end
|
111
|
+
|
42
112
|
def present?
|
43
|
-
name.present?
|
113
|
+
name.present? || type.present?
|
44
114
|
end
|
45
115
|
|
46
116
|
def human_name
|
@@ -4,9 +4,12 @@ module Effective
|
|
4
4
|
include Effective::Resources::Attributes
|
5
5
|
include Effective::Resources::Init
|
6
6
|
include Effective::Resources::Instance
|
7
|
+
include Effective::Resources::Forms
|
7
8
|
include Effective::Resources::Klass
|
8
9
|
include Effective::Resources::Naming
|
9
10
|
include Effective::Resources::Paths
|
11
|
+
include Effective::Resources::Relation
|
12
|
+
include Effective::Resources::Sql
|
10
13
|
|
11
14
|
# post, Post, Admin::Post, admin::Post, admin/posts, admin/post, admin/effective::post
|
12
15
|
def initialize(input)
|
@@ -2,6 +2,10 @@ module Effective
|
|
2
2
|
module Resources
|
3
3
|
module Associations
|
4
4
|
|
5
|
+
def macros
|
6
|
+
[:belongs_to, :belongs_to_polymorphic, :has_many, :has_and_belongs_to_many, :has_one]
|
7
|
+
end
|
8
|
+
|
5
9
|
def belong_tos
|
6
10
|
return [] unless klass.respond_to?(:reflect_on_all_associations)
|
7
11
|
@belong_tos ||= klass.reflect_on_all_associations(:belongs_to)
|
@@ -14,17 +18,57 @@ module Effective
|
|
14
18
|
|
15
19
|
def has_manys
|
16
20
|
return [] unless klass.respond_to?(:reflect_on_all_associations)
|
17
|
-
@has_manys ||= klass.reflect_on_all_associations(:has_many).reject { |
|
21
|
+
@has_manys ||= klass.reflect_on_all_associations(:has_many).reject { |ass| ass.options[:autosave] }
|
22
|
+
end
|
23
|
+
|
24
|
+
def has_and_belongs_to_manys
|
25
|
+
return [] unless klass.respond_to?(:reflect_on_all_associations)
|
26
|
+
@has_and_belongs_to_manys ||= klass.reflect_on_all_associations(:has_and_belongs_to_many)
|
18
27
|
end
|
19
28
|
|
20
29
|
def nested_resources
|
21
30
|
return [] unless klass.respond_to?(:reflect_on_all_associations)
|
22
|
-
@nested_resources ||= klass.reflect_on_all_associations(:has_many).select { |
|
31
|
+
@nested_resources ||= klass.reflect_on_all_associations(:has_many).select { |ass| ass.options[:autosave] }
|
23
32
|
end
|
24
33
|
|
25
34
|
def scopes
|
26
35
|
end
|
27
36
|
|
37
|
+
def associated(name)
|
38
|
+
name = (name.to_s.end_with?('_id') ? name.to_s[0...-3] : name).to_sym
|
39
|
+
klass.reflect_on_all_associations.find { |ass| ass.name == name }
|
40
|
+
end
|
41
|
+
|
42
|
+
def belongs_to(name)
|
43
|
+
name = (name.to_s.end_with?('_id') ? name.to_s[0...-3] : name).to_sym
|
44
|
+
belong_tos.find { |ass| ass.name == name }
|
45
|
+
end
|
46
|
+
|
47
|
+
def belongs_to_polymorphic(name)
|
48
|
+
name = (name.to_s.end_with?('_id') ? name.to_s[0...-3] : name).to_sym
|
49
|
+
belong_tos.find { |ass| ass.name == name && ass.options[:polymorphic] }
|
50
|
+
end
|
51
|
+
|
52
|
+
def has_and_belongs_to_many(name)
|
53
|
+
name = name.to_sym
|
54
|
+
has_and_belongs_to_manys.find { |ass| ass.name == name }
|
55
|
+
end
|
56
|
+
|
57
|
+
def has_many(name)
|
58
|
+
name = name.to_sym
|
59
|
+
(has_manys + nested_resources).find { |ass| ass.name == name }
|
60
|
+
end
|
61
|
+
|
62
|
+
def has_one(name)
|
63
|
+
name = name.to_sym
|
64
|
+
has_ones.find { |ho| ass.name == name }
|
65
|
+
end
|
66
|
+
|
67
|
+
def nested_resource(name)
|
68
|
+
name = name.to_sym
|
69
|
+
nested_resources.find { |ass| ass.name == name }
|
70
|
+
end
|
71
|
+
|
28
72
|
end
|
29
73
|
end
|
30
74
|
end
|
@@ -18,13 +18,13 @@ module Effective
|
|
18
18
|
|
19
19
|
attributes = (attributes.keys - [klass.primary_key, 'created_at', 'updated_at'] - belong_tos.map { |reference| reference.foreign_key }).map do |att|
|
20
20
|
if klass.respond_to?(:column_for_attribute) # Rails 4+
|
21
|
-
Effective::Attribute.new(att, klass.column_for_attribute(att).
|
21
|
+
Effective::Attribute.new(att, klass.column_for_attribute(att).type)
|
22
22
|
else
|
23
|
-
Effective::Attribute.new(att, klass.columns_hash[att].
|
23
|
+
Effective::Attribute.new(att, klass.columns_hash[att].type)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
|
27
|
-
|
27
|
+
sort_by_written_attributes(attributes)
|
28
28
|
end
|
29
29
|
|
30
30
|
def belong_tos_attributes
|
@@ -42,7 +42,7 @@ module Effective
|
|
42
42
|
|
43
43
|
private
|
44
44
|
|
45
|
-
def
|
45
|
+
def sort_by_written_attributes(attributes)
|
46
46
|
attributes.sort do |a, b|
|
47
47
|
index = nil
|
48
48
|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Effective
|
2
|
+
module Resources
|
3
|
+
module Forms
|
4
|
+
|
5
|
+
# Used by datatables
|
6
|
+
def search_form_field(name, type = nil)
|
7
|
+
case (type || sql_type(name))
|
8
|
+
when :belongs_to
|
9
|
+
{ as: :select }.merge(associated_search_collection(belongs_to(name)))
|
10
|
+
when :belongs_to_polymorphic
|
11
|
+
{ as: :grouped_select, polymorphic: true, collection: nil}
|
12
|
+
when :has_and_belongs_to_many
|
13
|
+
{ as: :select }.merge(associated_search_collection(has_and_belongs_to_many(name)))
|
14
|
+
when :has_many
|
15
|
+
{ as: :select, multiple: true }.merge(associated_search_collection(has_many(name)))
|
16
|
+
when :has_one
|
17
|
+
{ as: :select, multiple: true }.merge(associated_search_collection(has_one(name)))
|
18
|
+
when :effective_addresses
|
19
|
+
{ as: :string }
|
20
|
+
when :effective_roles
|
21
|
+
{ as: :select, collection: EffectiveRoles.roles }
|
22
|
+
when :effective_obfuscation
|
23
|
+
{ as: :effective_obfuscation }
|
24
|
+
when :boolean
|
25
|
+
{ as: :boolean, collection: [['true', true], ['false', false]] }
|
26
|
+
when :datetime
|
27
|
+
{ as: :datetime }
|
28
|
+
when :date
|
29
|
+
{ as: :date }
|
30
|
+
when :integer
|
31
|
+
{ as: :number }
|
32
|
+
when :text
|
33
|
+
{ as: :text }
|
34
|
+
else
|
35
|
+
{ as: :string }
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def associated_search_collection(association, max_id = 1000)
|
42
|
+
res = Effective::Resource.new(association)
|
43
|
+
|
44
|
+
if res.max_id > max_id
|
45
|
+
{ as: :string }
|
46
|
+
else
|
47
|
+
if res.klass.unscoped.respond_to?(:datatables_filter)
|
48
|
+
{ collection: res.klass.datatables_filter.map { |obj| [obj.to_s, obj.to_param] } }
|
49
|
+
elsif res.klass.unscoped.respond_to?(:sorted)
|
50
|
+
{ collection: res.klass.sorted.map { |obj| [obj.to_s, obj.to_param] } }
|
51
|
+
else
|
52
|
+
{ collection: res.klass.all.map { |obj| [obj.to_s, obj.to_param] }.sort { |x, y| x[0] <=> y[0] } }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -6,6 +6,7 @@ module Effective
|
|
6
6
|
|
7
7
|
def _initialize(obj)
|
8
8
|
@input_name = _initialize_input_name(obj)
|
9
|
+
@relation = _initialize_relation(obj)
|
9
10
|
@instance = obj if (klass && obj.instance_of?(klass))
|
10
11
|
end
|
11
12
|
|
@@ -14,13 +15,25 @@ module Effective
|
|
14
15
|
when String ; input
|
15
16
|
when Symbol ; input
|
16
17
|
when Class ; input.name
|
17
|
-
when
|
18
|
-
when
|
18
|
+
when ActiveRecord::Relation ; input.klass
|
19
|
+
when ActiveRecord::Reflection::AbstractReflection ; input.name
|
20
|
+
when ActionDispatch::Journey::Route ; input.defaults[:controller]
|
19
21
|
when nil ; raise 'expected a string or class'
|
20
22
|
else ; input.class.name
|
21
23
|
end.to_s.underscore
|
22
24
|
end
|
23
25
|
|
26
|
+
def _initialize_relation(input)
|
27
|
+
return nil unless klass && klass.respond_to?(:where)
|
28
|
+
|
29
|
+
case input
|
30
|
+
when ActiveRecord::Relation
|
31
|
+
input
|
32
|
+
when ActiveRecord::Reflection::AbstractReflection
|
33
|
+
klass.where(nil).merge(input.scope) if input.scope
|
34
|
+
end || klass.where(nil)
|
35
|
+
end
|
36
|
+
|
24
37
|
# Lazy initialized
|
25
38
|
def _initialize_written
|
26
39
|
@written_attributes = []
|
@@ -35,7 +48,7 @@ module Effective
|
|
35
48
|
|
36
49
|
if first && last
|
37
50
|
@written_attributes = reader.select(from: first+1, to: last-1).map do |line|
|
38
|
-
Effective::Attribute.
|
51
|
+
Effective::Attribute.parse_written(line).presence
|
39
52
|
end.compact
|
40
53
|
end
|
41
54
|
|
@@ -0,0 +1,215 @@
|
|
1
|
+
module Effective
|
2
|
+
module Resources
|
3
|
+
module Relation
|
4
|
+
attr_reader :relation
|
5
|
+
|
6
|
+
# When Effective::Resource is initialized with an ActiveRecord relation, the following
|
7
|
+
# methods will be available to operate on that relation, and be chainable and such
|
8
|
+
|
9
|
+
# name: sort by this column, or this relation
|
10
|
+
# sort: when a symbol or boolean, this is the relation's column to sort by
|
11
|
+
|
12
|
+
def order(name, direction = :asc, as: nil, sort: nil, sql_column: nil)
|
13
|
+
raise 'expected relation to be present' unless relation
|
14
|
+
|
15
|
+
sql_column ||= sql_column(name)
|
16
|
+
sql_type = (as || sql_type(name))
|
17
|
+
|
18
|
+
association = associated(name)
|
19
|
+
sql_direction = sql_direction(direction)
|
20
|
+
|
21
|
+
case sql_type
|
22
|
+
when :belongs_to
|
23
|
+
relation
|
24
|
+
.order(postgres? ? "#{sql_column} IS NULL ASC" : "ISNULL(#{sql_column}) ASC")
|
25
|
+
.order(order_by_associated_conditions(association, sort: sort, direction: direction))
|
26
|
+
when :belongs_to_polymorphic
|
27
|
+
relation
|
28
|
+
.order("#{sql_column.sub('_id', '_type')} #{sql_direction}")
|
29
|
+
.order("#{sql_column} #{sql_direction}")
|
30
|
+
when :has_and_belongs_to_many, :has_many, :has_one
|
31
|
+
relation
|
32
|
+
.order(order_by_associated_conditions(association, sort: sort, direction: direction))
|
33
|
+
.order("#{sql_column(klass.primary_key)} #{sql_direction}")
|
34
|
+
when :effective_roles
|
35
|
+
relation.order("#{sql_column(:roles_mask)} #{sql_direction}")
|
36
|
+
else
|
37
|
+
relation.order("#{sql_column} #{sql_direction}")
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def search(name, value, as: nil, fuzzy: true, sql_column: nil)
|
42
|
+
raise 'expected relation to be present' unless relation
|
43
|
+
|
44
|
+
sql_column ||= sql_column(name)
|
45
|
+
sql_type = (as || sql_type(name))
|
46
|
+
fuzzy = true unless fuzzy == false
|
47
|
+
|
48
|
+
if ['SUM(', 'COUNT(', 'MAX(', 'MIN(', 'AVG('].any? { |str| sql_column.to_s.include?(str) }
|
49
|
+
return relation.having("#{sql_column} = ?", value)
|
50
|
+
end
|
51
|
+
|
52
|
+
association = associated(name)
|
53
|
+
term = Effective::Attribute.new(sql_type, klass: association.try(:klass) || klass).parse(value, name: name)
|
54
|
+
|
55
|
+
case sql_type
|
56
|
+
when :belongs_to, :has_and_belongs_to_many, :has_many, :has_one
|
57
|
+
relation.where(search_by_associated_conditions(association, term, fuzzy: fuzzy))
|
58
|
+
when :belongs_to_polymorphic
|
59
|
+
(type, id) = term.split('_')
|
60
|
+
relation.where("#{sql_column} = ?", id).where("#{sql_column.sub('_id', '_type')} = ?", type)
|
61
|
+
when :effective_addresses
|
62
|
+
raise 'not yet implemented'
|
63
|
+
when :effective_obfuscation
|
64
|
+
# If value == term, it's an invalid deobfuscated id
|
65
|
+
relation.where("#{sql_column} = ?", (value == term ? 0 : term))
|
66
|
+
when :effective_roles
|
67
|
+
relation.with_role(term)
|
68
|
+
when :boolean
|
69
|
+
relation.where("#{sql_column} = ?", term)
|
70
|
+
when :datetime, :date
|
71
|
+
end_at = (
|
72
|
+
case (value.to_s.scan(/(\d+)/).flatten).length
|
73
|
+
when 1 ; term.end_of_year # Year
|
74
|
+
when 2 ; term.end_of_month # Year-Month
|
75
|
+
when 3 ; term.end_of_day # Year-Month-Day
|
76
|
+
when 4 ; term.end_of_hour # Year-Month-Day Hour
|
77
|
+
when 5 ; term.end_of_minute # Year-Month-Day Hour-Minute
|
78
|
+
when 6 ; term + 1.second # Year-Month-Day Hour-Minute-Second
|
79
|
+
else term
|
80
|
+
end
|
81
|
+
)
|
82
|
+
relation.where("#{sql_column} >= ? AND #{sql_column} <= ?", term, end_at)
|
83
|
+
when :decimal
|
84
|
+
relation.where("#{sql_column} = ?", term)
|
85
|
+
when :integer
|
86
|
+
relation.where("#{sql_column} = ?", term)
|
87
|
+
when :price
|
88
|
+
relation.where("#{sql_column} = ?", term)
|
89
|
+
when :string, :text
|
90
|
+
if fuzzy
|
91
|
+
relation.where("#{sql_column} #{ilike} ?", "%#{term}%")
|
92
|
+
else
|
93
|
+
relation.where("#{sql_column} = ?", term)
|
94
|
+
end
|
95
|
+
else
|
96
|
+
raise 'unsupported sql type'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def search_any(value, columns: nil, fuzzy: nil)
|
101
|
+
raise 'expected relation to be present' unless relation
|
102
|
+
|
103
|
+
# Assume this is a set of IDs
|
104
|
+
if value.kind_of?(Integer) || value.kind_of?(Array)
|
105
|
+
return relation.where(klass.primary_key => value)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Otherwise, we fall back to a string/text search of all columns
|
109
|
+
columns = Array(columns || search_columns)
|
110
|
+
fuzzy = true unless fuzzy == false
|
111
|
+
|
112
|
+
conditions = (
|
113
|
+
if fuzzy
|
114
|
+
columns.map { |name| "#{sql_column(name)} #{ilike} :fuzzy" }
|
115
|
+
else
|
116
|
+
columns.map { |name| "#{sql_column(name)} = :value" }
|
117
|
+
end
|
118
|
+
).join(' OR ')
|
119
|
+
|
120
|
+
relation.where(conditions, fuzzy: "%#{value}%", value: value)
|
121
|
+
end
|
122
|
+
|
123
|
+
private
|
124
|
+
|
125
|
+
def search_by_associated_conditions(association, value, fuzzy: nil)
|
126
|
+
resource = Effective::Resource.new(association)
|
127
|
+
|
128
|
+
# Search the target model for its matching records / keys
|
129
|
+
relation = resource.search_any(value, fuzzy: fuzzy)
|
130
|
+
|
131
|
+
if association.options[:as] # polymorphic
|
132
|
+
relation = relation.where(association.type => klass.name)
|
133
|
+
end
|
134
|
+
|
135
|
+
# key: the id, or associated_id on my table
|
136
|
+
# keys: the ids themselves as per the target table
|
137
|
+
|
138
|
+
if association.macro == :belongs_to
|
139
|
+
key = sql_column(association.foreign_key)
|
140
|
+
keys = relation.pluck(association.klass.primary_key)
|
141
|
+
elsif association.macro == :has_and_belongs_to_many
|
142
|
+
key = sql_column(klass.primary_key)
|
143
|
+
values = relation.pluck(association.source_reflection.klass.primary_key).uniq.compact
|
144
|
+
|
145
|
+
keys = klass.joins(association.name)
|
146
|
+
.where(association.name => { association.source_reflection.klass.primary_key => values })
|
147
|
+
.pluck(klass.primary_key)
|
148
|
+
elsif association.macro == :has_many && association.options[:through].present?
|
149
|
+
key = sql_column(klass.primary_key)
|
150
|
+
values = relation.pluck(association.source_reflection.klass.primary_key).uniq.compact
|
151
|
+
|
152
|
+
keys = association.through_reflection.klass
|
153
|
+
.where(association.source_reflection.foreign_key => values)
|
154
|
+
.pluck(association.through_reflection.foreign_key)
|
155
|
+
elsif association.macro == :has_many
|
156
|
+
key = sql_column(klass.primary_key)
|
157
|
+
keys = relation.pluck(association.foreign_key)
|
158
|
+
elsif association.macro == :has_one
|
159
|
+
key = sql_column(klass.primary_key)
|
160
|
+
keys = relation.pluck(association.foreign_key)
|
161
|
+
end
|
162
|
+
|
163
|
+
"#{key} IN (#{(keys.uniq.compact.presence || [0]).join(',')})"
|
164
|
+
end
|
165
|
+
|
166
|
+
def order_by_associated_conditions(association, sort: nil, direction: :asc)
|
167
|
+
resource = Effective::Resource.new(association)
|
168
|
+
|
169
|
+
# Order the target model for its matching records / keys
|
170
|
+
sort_column = (sort unless sort == true) || resource.sort_column
|
171
|
+
|
172
|
+
relation = resource.relation.order("#{resource.sql_column(sort_column)} #{sql_direction(direction)}")
|
173
|
+
|
174
|
+
if association.options[:as] # polymorphic
|
175
|
+
relation = relation.where(association.type => klass.name)
|
176
|
+
end
|
177
|
+
|
178
|
+
# key: the id, or associated_id on my table
|
179
|
+
# keys: the ids themselves as per the target table
|
180
|
+
|
181
|
+
if association.macro == :belongs_to
|
182
|
+
key = sql_column(association.foreign_key)
|
183
|
+
keys = relation.pluck(association.klass.primary_key)
|
184
|
+
elsif association.macro == :has_and_belongs_to_many
|
185
|
+
key = sql_column(klass.primary_key)
|
186
|
+
|
187
|
+
source = "#{association.join_table}.#{association.source_reflection.association_foreign_key}"
|
188
|
+
values = relation.pluck(association.source_reflection.klass.primary_key).uniq.compact # The searched keys
|
189
|
+
|
190
|
+
keys = klass.joins(association.name)
|
191
|
+
.order(values.uniq.compact.map { |value| "#{source}=#{value} DESC" }.join(','))
|
192
|
+
.pluck(klass.primary_key)
|
193
|
+
elsif association.macro == :has_many && association.options[:through].present?
|
194
|
+
key = sql_column(klass.primary_key)
|
195
|
+
|
196
|
+
source = association.source_reflection.foreign_key
|
197
|
+
values = relation.pluck(association.source_reflection.klass.primary_key).uniq.compact # The searched keys
|
198
|
+
|
199
|
+
keys = association.through_reflection.klass
|
200
|
+
.order(values.uniq.compact.map { |value| "#{source}=#{value} DESC" }.join(','))
|
201
|
+
.pluck(association.through_reflection.foreign_key)
|
202
|
+
elsif association.macro == :has_many
|
203
|
+
key = sql_column(klass.primary_key)
|
204
|
+
keys = relation.pluck(association.foreign_key)
|
205
|
+
elsif association.macro == :has_one
|
206
|
+
key = sql_column(klass.primary_key)
|
207
|
+
keys = relation.pluck(association.foreign_key)
|
208
|
+
end
|
209
|
+
|
210
|
+
keys.uniq.compact.map { |value| "#{key}=#{value} DESC" }.join(',')
|
211
|
+
end
|
212
|
+
|
213
|
+
end
|
214
|
+
end
|
215
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Effective
|
2
|
+
module Resources
|
3
|
+
module Sql
|
4
|
+
|
5
|
+
def column(name)
|
6
|
+
name = name.to_s
|
7
|
+
columns.find { |col| col.name == name || (belongs_to(name) && col.name == belongs_to(name).foreign_key) }
|
8
|
+
end
|
9
|
+
|
10
|
+
def columns
|
11
|
+
klass.columns
|
12
|
+
end
|
13
|
+
|
14
|
+
def column_names
|
15
|
+
@column_names ||= columns.map { |col| col.name }
|
16
|
+
end
|
17
|
+
|
18
|
+
def table
|
19
|
+
klass.unscoped.table
|
20
|
+
end
|
21
|
+
|
22
|
+
def max_id
|
23
|
+
@max_id ||= klass.maximum(klass.primary_key).to_i
|
24
|
+
end
|
25
|
+
|
26
|
+
def sql_column(name)
|
27
|
+
column = column(name)
|
28
|
+
return nil unless table && column
|
29
|
+
|
30
|
+
[klass.connection.quote_table_name(table.name), klass.connection.quote_column_name(column.name)].join('.')
|
31
|
+
end
|
32
|
+
|
33
|
+
def sql_direction(name)
|
34
|
+
name.to_s.downcase == 'desc' ? 'DESC' : 'ASC'
|
35
|
+
end
|
36
|
+
|
37
|
+
# This is for EffectiveDatatables (col as:)
|
38
|
+
def sql_type(name)
|
39
|
+
name = name.to_s
|
40
|
+
|
41
|
+
if belongs_to_polymorphic(name)
|
42
|
+
:belongs_to_polymorphic
|
43
|
+
elsif belongs_to(name)
|
44
|
+
:belongs_to
|
45
|
+
elsif has_and_belongs_to_many(name)
|
46
|
+
:has_and_belongs_to_many
|
47
|
+
elsif has_many(name)
|
48
|
+
:has_many
|
49
|
+
elsif has_one(name)
|
50
|
+
:has_one
|
51
|
+
elsif name.end_with?('_address') && defined?(EffectiveAddresses) && instance.respond_to?(:effective_addresses)
|
52
|
+
:effective_addresses
|
53
|
+
elsif name == 'id' && defined?(EffectiveObfuscation) && klass.respond_to?(:deobfuscate)
|
54
|
+
:effective_obfuscation
|
55
|
+
elsif name == 'roles' && defined?(EffectiveRoles) && klass.respond_to?(:with_role)
|
56
|
+
:effective_roles
|
57
|
+
elsif (column = column(name))
|
58
|
+
column.type
|
59
|
+
elsif name.ends_with?('_id')
|
60
|
+
:integer
|
61
|
+
else
|
62
|
+
:string
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# This tries to figure out the column we should order this collection by.
|
67
|
+
# Whatever would match up with the .to_s
|
68
|
+
def sort_column
|
69
|
+
['name', 'title', 'label', 'first_name', 'subject', 'description', 'email'].each do |name|
|
70
|
+
return name if column_names.include?(name)
|
71
|
+
end
|
72
|
+
|
73
|
+
klass.primary_key
|
74
|
+
end
|
75
|
+
|
76
|
+
# Any string or text columns
|
77
|
+
# TODO: filter out _type columns for polymorphic
|
78
|
+
def search_columns
|
79
|
+
columns.map { |column| column.name if [:string, :text].include?(column.type) }.compact
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def postgres?
|
85
|
+
return @postgres unless @postgres.nil?
|
86
|
+
@postgres ||= (klass.connection.kind_of?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) rescue false)
|
87
|
+
end
|
88
|
+
|
89
|
+
def mysql?
|
90
|
+
return @mysql unless @mysql.nil?
|
91
|
+
@mysql ||= (klass.connection.kind_of?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) rescue false)
|
92
|
+
end
|
93
|
+
|
94
|
+
def ilike
|
95
|
+
@ilike ||= (postgres? ? 'ILIKE' : 'LIKE') # Only Postgres supports ILIKE, Mysql and Sqlite3 use LIKE
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
100
|
+
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: 0.2.
|
4
|
+
version: 0.2.3
|
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: 2017-02-
|
11
|
+
date: 2017-02-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -43,11 +43,14 @@ files:
|
|
43
43
|
- app/models/effective/resource.rb
|
44
44
|
- app/models/effective/resources/associations.rb
|
45
45
|
- app/models/effective/resources/attributes.rb
|
46
|
+
- app/models/effective/resources/forms.rb
|
46
47
|
- app/models/effective/resources/init.rb
|
47
48
|
- app/models/effective/resources/instance.rb
|
48
49
|
- app/models/effective/resources/klass.rb
|
49
50
|
- app/models/effective/resources/naming.rb
|
50
51
|
- app/models/effective/resources/paths.rb
|
52
|
+
- app/models/effective/resources/relation.rb
|
53
|
+
- app/models/effective/resources/sql.rb
|
51
54
|
- config/effective_resources.rb
|
52
55
|
- lib/effective_resources.rb
|
53
56
|
- lib/effective_resources/engine.rb
|