aenea 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +39 -0
- data/Rakefile +24 -0
- data/app/assets/config/aenea_manifest.js +0 -0
- data/app/helpers/inline_boolean_input.rb +9 -0
- data/app/helpers/markdown_input.rb +21 -0
- data/app/helpers/non_input_input.rb +7 -0
- data/app/models/concerns/acts_as_only_flagged.rb +65 -0
- data/app/models/concerns/can_be_inactive.rb +8 -0
- data/app/models/concerns/has_external_id.rb +9 -0
- data/app/models/concerns/has_obfuscated_id.rb +51 -0
- data/app/models/concerns/model.rb +51 -0
- data/app/models/concerns/model/belongs_to.rb +38 -0
- data/app/models/concerns/model/enum.rb +24 -0
- data/app/models/concerns/model/has_many.rb +127 -0
- data/app/models/concerns/model/has_one.rb +77 -0
- data/app/models/concerns/sluggable.rb +7 -0
- data/app/models/countries.rb +278 -0
- data/app/models/country_validator.rb +10 -0
- data/app/models/database_function_helper.rb +88 -0
- data/app/models/google_maps_api.rb +19 -0
- data/app/models/google_maps_api/geocode.rb +29 -0
- data/app/models/google_maps_api/time_zone.rb +29 -0
- data/app/models/json_api.rb +16 -0
- data/app/models/phone_number_validator.rb +10 -0
- data/app/models/us_state_validator.rb +10 -0
- data/app/models/us_states.rb +75 -0
- data/app/models/zip_code_validator.rb +12 -0
- data/lib/aenea.rb +12 -0
- data/lib/aenea/active_admin.rb +23 -0
- data/lib/aenea/active_admin/index_as_table.rb +9 -0
- data/lib/aenea/active_admin/index_as_table/sortable_columns.rb +29 -0
- data/lib/aenea/active_admin/resource_controller.rb +9 -0
- data/lib/aenea/active_admin/resource_controller/find_sluggable.rb +21 -0
- data/lib/aenea/active_admin/resource_dsl.rb +11 -0
- data/lib/aenea/active_admin/resource_dsl/sortable_actions.rb +53 -0
- data/lib/aenea/active_admin/resource_dsl/use_parent_actions.rb +79 -0
- data/lib/aenea/date.rb +14 -0
- data/lib/aenea/engine.rb +20 -0
- data/lib/aenea/enum.rb +13 -0
- data/lib/aenea/enum/changes.rb +61 -0
- data/lib/aenea/enum/ext.rb +68 -0
- data/lib/aenea/enum/groups.rb +53 -0
- data/lib/aenea/markdown_attr.rb +43 -0
- data/lib/aenea/time.rb +9 -0
- data/lib/aenea/time_of_day.rb +41 -0
- data/lib/aenea/time_window.rb +86 -0
- data/lib/aenea/version.rb +3 -0
- data/lib/tasks/aenea_tasks.rake +4 -0
- metadata +203 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module GoogleMapsApi
|
2
|
+
include Geocode
|
3
|
+
include TimeZone
|
4
|
+
|
5
|
+
protected
|
6
|
+
|
7
|
+
def self.send_request(uri_string)
|
8
|
+
json = JsonApi.send_request("#{uri_string}&key=#{GOOGLE_MAPS_API_SERVER_KEY}")
|
9
|
+
|
10
|
+
return nil unless json
|
11
|
+
|
12
|
+
if json['status'] != 'OK'
|
13
|
+
Rails.logger.error "#{uri_string} failed: #{json}"
|
14
|
+
return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
json
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GoogleMapsApi
|
2
|
+
module Geocode
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
BASE_URI = 'https://maps.googleapis.com/maps/api/geocode/json'
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
include ActionView::Helpers::JavaScriptHelper
|
9
|
+
|
10
|
+
def geocode(address_string)
|
11
|
+
raise ArgumentError.new('address_string cannot be blank') if address_string.blank?
|
12
|
+
|
13
|
+
begin
|
14
|
+
return nil unless result = send_request("#{BASE_URI}?address=#{escape_javascript address_string}")
|
15
|
+
rescue SocketError => e
|
16
|
+
if Rails.env.development? || Rails.env.test?
|
17
|
+
Rails.logger.warn 'not connected to the Internet... faking geocode API call'
|
18
|
+
return 37.792855, -122.403166
|
19
|
+
end
|
20
|
+
|
21
|
+
raise e
|
22
|
+
end
|
23
|
+
|
24
|
+
location_result = result['results'][0]['geometry']['location']
|
25
|
+
[ location_result['lat'], location_result['lng'] ]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module GoogleMapsApi
|
2
|
+
module TimeZone
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
BASE_URI = 'https://maps.googleapis.com/maps/api/timezone/json'
|
6
|
+
|
7
|
+
module ClassMethods
|
8
|
+
def time_zone_id(latitude, longitude, timestamp = nil)
|
9
|
+
raise ArgumentError.new("missing latitude and/or longitude: #{latitude}, #{longitude}") if latitude.blank? || longitude.blank?
|
10
|
+
|
11
|
+
timestamp ||= Time.now.to_i
|
12
|
+
location = "#{latitude},#{longitude}"
|
13
|
+
|
14
|
+
begin
|
15
|
+
return nil unless result = send_request("#{BASE_URI}?location=#{location}×tamp=#{timestamp}")
|
16
|
+
rescue SocketError => e
|
17
|
+
if Rails.env.development? || Rails.env.test?
|
18
|
+
Rails.logger.warn 'not connected to the Internet... faking time zone API call'
|
19
|
+
return 'America/Los_Angeles'
|
20
|
+
end
|
21
|
+
|
22
|
+
raise e
|
23
|
+
end
|
24
|
+
|
25
|
+
result['timeZoneId']
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module JsonApi
|
2
|
+
def self.send_request(uri_string, options = {})
|
3
|
+
uri = URI.parse(URI.escape(uri_string))
|
4
|
+
Rails.logger.debug uri
|
5
|
+
|
6
|
+
# handle Ruby 2 OpenSSL cert issue: https://gist.github.com/mislav/5026283
|
7
|
+
options[:ssl_verify_mode] = OpenSSL::SSL::VERIFY_NONE if Rails.env.development? || Rails.env.test?
|
8
|
+
|
9
|
+
response_body = open(uri, options).read
|
10
|
+
Rails.logger.info "#{uri} => #{response_body}"
|
11
|
+
|
12
|
+
json = JSON.parse(response_body)
|
13
|
+
|
14
|
+
json
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class PhoneNumberValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(record, attribute, value)
|
3
|
+
if value.blank?
|
4
|
+
record.errors[attribute] << 'is required' unless options[:allow_blank] || options[:allow_nil]
|
5
|
+
return
|
6
|
+
end
|
7
|
+
|
8
|
+
record.errors[attribute] << (options[:message] || 'is not a valid phone number') if value.gsub(/[^\d]/, '').length < 10
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
class UsStateValidator < ActiveModel::EachValidator
|
2
|
+
def validate_each(record, attribute, value)
|
3
|
+
if value.blank?
|
4
|
+
record.errors[attribute] << 'is required' unless options[:allow_blank] || options[:allow_nil]
|
5
|
+
return
|
6
|
+
end
|
7
|
+
|
8
|
+
record.errors[attribute] << (options[:message] || 'is not a valid US state') unless UsStates.valid?(value)
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module UsStates
|
2
|
+
SELECT_VALUES = [
|
3
|
+
['Alabama', 'AL'],
|
4
|
+
['Alaska', 'AK'],
|
5
|
+
['American Samoa', 'AS'],
|
6
|
+
['Arizona', 'AZ'],
|
7
|
+
['Arkansas', 'AR'],
|
8
|
+
['California', 'CA'],
|
9
|
+
['Colorado', 'CO'],
|
10
|
+
['Connecticut', 'CT'],
|
11
|
+
['Delaware', 'DE'],
|
12
|
+
['District of Columbia', 'DC'],
|
13
|
+
['Florida', 'FL'],
|
14
|
+
['Georgia', 'GA'],
|
15
|
+
['Guam', 'GU'],
|
16
|
+
['Hawaii', 'HI'],
|
17
|
+
['Idaho', 'ID'],
|
18
|
+
['Illinois', 'IL'],
|
19
|
+
['Indiana', 'IN'],
|
20
|
+
['Iowa', 'IA'],
|
21
|
+
['Kansas', 'KS'],
|
22
|
+
['Kentucky', 'KY'],
|
23
|
+
['Louisiana', 'LA'],
|
24
|
+
['Maine', 'ME'],
|
25
|
+
['Maryland', 'MD'],
|
26
|
+
['Massachusetts', 'MA'],
|
27
|
+
['Michigan', 'MI'],
|
28
|
+
['Minnesota', 'MN'],
|
29
|
+
['Mississippi', 'MS'],
|
30
|
+
['Missouri', 'MO'],
|
31
|
+
['Montana', 'MT'],
|
32
|
+
['Nebraska', 'NE'],
|
33
|
+
['Nevada', 'NV'],
|
34
|
+
['New Hampshire', 'NH'],
|
35
|
+
['New Jersey', 'NJ'],
|
36
|
+
['New Mexico', 'NM'],
|
37
|
+
['New York', 'NY'],
|
38
|
+
['North Carolina', 'NC'],
|
39
|
+
['North Dakota', 'ND'],
|
40
|
+
['Ohio', 'OH'],
|
41
|
+
['Oklahoma', 'OK'],
|
42
|
+
['Oregon', 'OR'],
|
43
|
+
['Pennsylvania', 'PA'],
|
44
|
+
['Puerto Rico', 'PR'],
|
45
|
+
['Rhode Island', 'RI'],
|
46
|
+
['South Carolina', 'SC'],
|
47
|
+
['South Dakota', 'SD'],
|
48
|
+
['Tennessee', 'TN'],
|
49
|
+
['Texas', 'TX'],
|
50
|
+
['Utah', 'UT'],
|
51
|
+
['Vermont', 'VT'],
|
52
|
+
['Virgin Islands', 'VI'],
|
53
|
+
['Virginia', 'VA'],
|
54
|
+
['Washington', 'WA'],
|
55
|
+
['West Virginia', 'WV'],
|
56
|
+
['Wisconsin', 'WI'],
|
57
|
+
['Wyoming', 'WY'],
|
58
|
+
]
|
59
|
+
|
60
|
+
def self.select_values
|
61
|
+
SELECT_VALUES
|
62
|
+
end
|
63
|
+
|
64
|
+
def self.valid?(code)
|
65
|
+
(@codes ||= select_values.map(&:last)).include?(code)
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.[](code)
|
69
|
+
(@names_by_code ||= Hash[select_values.map(&:reverse)])[code]
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.community_property?(code)
|
73
|
+
%w(AZ CA ID LA NV NM PR TX WA WI).include?(code)
|
74
|
+
end
|
75
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class ZipCodeValidator < ActiveModel::EachValidator
|
2
|
+
FORMAT = /\A\d{5}\z/
|
3
|
+
|
4
|
+
def validate_each(record, attribute, value)
|
5
|
+
if value.blank?
|
6
|
+
record.errors[attribute] << 'is required' unless options[:allow_blank] || options[:allow_nil]
|
7
|
+
return
|
8
|
+
end
|
9
|
+
|
10
|
+
record.errors[attribute] << (options[:message] || 'must be a 5-digit ZIP code') unless value =~ FORMAT
|
11
|
+
end
|
12
|
+
end
|
data/lib/aenea.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'aenea/active_admin'
|
2
|
+
require 'aenea/date'
|
3
|
+
require 'aenea/engine'
|
4
|
+
require 'aenea/enum'
|
5
|
+
require 'aenea/markdown_attr'
|
6
|
+
require 'aenea/time'
|
7
|
+
require 'aenea/time_of_day'
|
8
|
+
require 'aenea/time_window'
|
9
|
+
require 'aenea/version'
|
10
|
+
|
11
|
+
module Aenea
|
12
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Aenea
|
2
|
+
module ActiveAdmin
|
3
|
+
extend ActiveSupport::Autoload
|
4
|
+
|
5
|
+
autoload :IndexAsTable
|
6
|
+
autoload :ResourceController
|
7
|
+
autoload :ResourceDsl
|
8
|
+
|
9
|
+
cattr_accessor :installed
|
10
|
+
|
11
|
+
def self.install
|
12
|
+
return if installed
|
13
|
+
|
14
|
+
if defined? ::ActiveAdmin
|
15
|
+
::ActiveAdmin::Views::IndexAsTable.include Aenea::ActiveAdmin::IndexAsTable
|
16
|
+
::ActiveAdmin::ResourceController.include Aenea::ActiveAdmin::ResourceController
|
17
|
+
::ActiveAdmin::ResourceDSL.include Aenea::ActiveAdmin::ResourceDsl
|
18
|
+
|
19
|
+
self.installed = true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Aenea
|
2
|
+
module ActiveAdmin
|
3
|
+
module IndexAsTable
|
4
|
+
module SortableColumns
|
5
|
+
def sortable_columns(options = {})
|
6
|
+
unless options[:up_down_only]
|
7
|
+
column ''.html_safe do |resource|
|
8
|
+
link_to('▲▲'.html_safe, self.send(:"move_to_top_admin_#{resource.class.name.underscore.gsub("/", "_")}_path", resource), :class => 'arrow', title: 'Move to Top') unless resource.first?
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
column ''.html_safe do |resource|
|
13
|
+
link_to('▲'.html_safe, self.send(:"move_up_admin_#{resource.class.name.underscore.gsub("/", "_")}_path", resource), :class => 'arrow', title: 'Move Up') unless resource.first?
|
14
|
+
end
|
15
|
+
|
16
|
+
column ''.html_safe do |resource|
|
17
|
+
link_to('▼'.html_safe, self.send(:"move_down_admin_#{resource.class.name.underscore.gsub("/", "_")}_path", resource), :class => 'arrow', title: 'Move Down') unless resource.last?
|
18
|
+
end
|
19
|
+
|
20
|
+
unless options[:up_down_only]
|
21
|
+
column ''.html_safe do |resource|
|
22
|
+
link_to('▼▼'.html_safe, self.send(:"move_to_bottom_admin_#{resource.class.name.underscore.gsub("/", "_")}_path", resource), :class => 'arrow', title: 'Move to Bottom') unless resource.last?
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Aenea
|
2
|
+
module ActiveAdmin
|
3
|
+
module ResourceController
|
4
|
+
module FindSluggable
|
5
|
+
def find_resource
|
6
|
+
if resource_class < Sluggable
|
7
|
+
if params[:id] =~ /\A\d+\z/
|
8
|
+
return scoped_collection.find params[:id]
|
9
|
+
else
|
10
|
+
return scoped_collection.find_by_url! params[:id]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
return scoped_collection if scoped_collection.class < ActiveRecord::Base # has_one
|
15
|
+
|
16
|
+
scoped_collection.find(params[:id])
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module Aenea
|
2
|
+
module ActiveAdmin
|
3
|
+
module ResourceDsl
|
4
|
+
module SortableActions
|
5
|
+
def sortable_member_actions(fallback_route)
|
6
|
+
member_action :move_to_top do
|
7
|
+
if resource.first?
|
8
|
+
flash[:warning] = 'Already at top'
|
9
|
+
else
|
10
|
+
resource.move_to_top
|
11
|
+
flash[:notice] = 'Moved to top'
|
12
|
+
end
|
13
|
+
|
14
|
+
redirect_back fallback_location: Rails.application.routes.url_helpers.send(fallback_route)
|
15
|
+
end
|
16
|
+
|
17
|
+
member_action :move_to_bottom do
|
18
|
+
if resource.last?
|
19
|
+
flash[:warning] = 'Already at bottom'
|
20
|
+
else
|
21
|
+
resource.move_to_bottom
|
22
|
+
flash[:notice] = 'Moved to bottom'
|
23
|
+
end
|
24
|
+
|
25
|
+
redirect_back fallback_location: Rails.application.routes.url_helpers.send(fallback_route)
|
26
|
+
end
|
27
|
+
|
28
|
+
member_action :move_up do
|
29
|
+
if resource.first?
|
30
|
+
flash[:warning] = 'Already at top'
|
31
|
+
else
|
32
|
+
resource.move_higher
|
33
|
+
flash[:notice] = 'Moved up'
|
34
|
+
end
|
35
|
+
|
36
|
+
redirect_back fallback_location: Rails.application.routes.url_helpers.send(fallback_route)
|
37
|
+
end
|
38
|
+
|
39
|
+
member_action :move_down do
|
40
|
+
if resource.last?
|
41
|
+
flash[:warning] = 'Already at bottom'
|
42
|
+
else
|
43
|
+
resource.move_lower
|
44
|
+
flash[:notice] = 'Moved down'
|
45
|
+
end
|
46
|
+
|
47
|
+
redirect_back fallback_location: Rails.application.routes.url_helpers.send(fallback_route)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
module Aenea
|
2
|
+
module ActiveAdmin
|
3
|
+
module ResourceDsl
|
4
|
+
module UseParentActions
|
5
|
+
def use_parent_index(options = {})
|
6
|
+
grandparent_type = options[:grandparent]
|
7
|
+
|
8
|
+
controller do
|
9
|
+
define_method :index do # don't use def so we can access 'options'
|
10
|
+
parent_id = params[:"#{parent_type}_id"]
|
11
|
+
url = if grandparent_type
|
12
|
+
parent = parent_type.to_s.camelize.constantize.find(parent_id)
|
13
|
+
grandparent = parent.send grandparent_type
|
14
|
+
route = :"admin_#{grandparent_type}_#{parent_type}_path"
|
15
|
+
send route, grandparent, parent
|
16
|
+
else
|
17
|
+
parent_path parent_id
|
18
|
+
end
|
19
|
+
|
20
|
+
redirect_to url
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def use_parent_action(action, options = {})
|
26
|
+
raise 'invalid action' unless %i(create update destroy).include?(action)
|
27
|
+
|
28
|
+
grandparent_type = options[:grandparent]
|
29
|
+
verb = action == :destroy ? 'destroyed' : "#{action}d"
|
30
|
+
|
31
|
+
controller do
|
32
|
+
define_method action do
|
33
|
+
super() do |success, failure|
|
34
|
+
parent_name = parent_type.to_s rescue
|
35
|
+
(options[:parent] or raise 'must specify parent for controller without belongs_to')
|
36
|
+
|
37
|
+
success.html do
|
38
|
+
flash[:notice] = "Succesfully #{verb} #{parent_name.titleize}"
|
39
|
+
end
|
40
|
+
|
41
|
+
failure.html do
|
42
|
+
flash[:error] = "Could not #{action} #{parent_name.titleize}: #{resource.errors.full_messages.join(', ')}"
|
43
|
+
end
|
44
|
+
|
45
|
+
parent_id = params[:"#{parent_type}_id"]
|
46
|
+
url = if grandparent_type
|
47
|
+
parent = parent_type.to_s.camelize.constantize.find(parent_id)
|
48
|
+
grandparent = parent.send grandparent_type
|
49
|
+
route = :"admin_#{grandparent_type}_#{parent_type}_path"
|
50
|
+
send route, grandparent, parent
|
51
|
+
else
|
52
|
+
parent_path parent_id
|
53
|
+
end
|
54
|
+
|
55
|
+
if options[:redirect_back]
|
56
|
+
redirect_back fallback_location: url
|
57
|
+
else
|
58
|
+
redirect_to url
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def use_parent_create(options = {})
|
66
|
+
use_parent_action :create, options
|
67
|
+
end
|
68
|
+
|
69
|
+
def use_parent_update(options = {})
|
70
|
+
use_parent_action :update, options
|
71
|
+
end
|
72
|
+
|
73
|
+
def use_parent_destroy(options = {})
|
74
|
+
use_parent_action :destroy, options
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|