aenea 0.2.0
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 +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
|