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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e0162b9865a0d7b0f8792b361f46f886c905286a
4
- data.tar.gz: d3b8a389e8ff5e40860603aa22ed948bde07cc37
3
+ metadata.gz: a75f5adcc4a20f0f1a18cd2496657b439934e596
4
+ data.tar.gz: c72e11e7e702d75cafa264dab8c538f6e61962b1
5
5
  SHA512:
6
- metadata.gz: 7868a5dc8b09f78c1fd044878b9872e65a27e6698a8f52878d6a09a87dbde581c84a33b029d6d0c1869c2e362c24ab446c7b8e3d78ae95fd8e6f071d8c9786e1
7
- data.tar.gz: 63eb662a68bb241d51b441dad12c2bcbba3cceaa8a04495e2fb868c56cb1aa441a64258ce347e69ccc268d98e7c08f16d345c906f954b92fa1a3e246d44daa07
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
- REGEX = /^\W*(\w+)\W*:(\w+)/
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(REGEX).first).present?
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
- def initialize(name, type = nil, options = {})
20
- @name = name.to_s
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
- def to_s
26
- name
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 field_type
30
- @field_type ||= case type
31
- when :integer then :number_field
32
- when :float, :decimal then :text_field
33
- when :time then :time_select
34
- when :datetime, :timestamp then :datetime_select
35
- when :date then :date_select
36
- when :text then :text_area
37
- when :boolean then :check_box
38
- else :text_field
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 { |association| association.options[:autosave] }
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 { |association| association.options[:autosave] }
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).try(:type))
21
+ Effective::Attribute.new(att, klass.column_for_attribute(att).type)
22
22
  else
23
- Effective::Attribute.new(att, klass.columns_hash[att].try(:type))
23
+ Effective::Attribute.new(att, klass.columns_hash[att].type)
24
24
  end
25
25
  end
26
26
 
27
- sort(attributes)
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 sort(attributes)
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 (ActiveRecord::Reflection::MacroReflection rescue false); input.name
18
- when (ActionDispatch::Journey::Route rescue false); input.defaults[:controller]
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.parse(line).presence
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
@@ -1,3 +1,3 @@
1
1
  module EffectiveResources
2
- VERSION = '0.2.2'.freeze
2
+ VERSION = '0.2.3'.freeze
3
3
  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.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-03 00:00:00.000000000 Z
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