effective_resources 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|