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 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