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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1a6c10dd3f1bfe6878023fcd755f6a773debcee6ccdbc291431959cb9e3896fe
|
4
|
+
data.tar.gz: 93350dc6788176465e3ada046b17239a09669528ce3fc3973cd98b400b5270ca
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 4a85e94f01136b1c95dfc3e6a251e781fa587fb84601ac7f6019374b73b12ef97ca81a881933a11184aeba10ef1c87c057d65bd006edf5b9afccac73f1ed6a3d
|
7
|
+
data.tar.gz: dc0e8c8152cdf8737ec8a12bce9ae529f63d1c20c29241d9374e5ccd79d517ccde09db6069720458d72a8b27f4d6230e2119db060f7d42b6b44d18067af5019c
|
data/README.md
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# Aenea
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/aenea`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'aenea'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install aenea
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/wangthony/aenea. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
36
|
+
|
37
|
+
## Code of Conduct
|
38
|
+
|
39
|
+
Everyone interacting in the Aenea project’s codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/wangthony/aenea/blob/master/CODE_OF_CONDUCT.md).
|
data/Rakefile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'bundler/gem_tasks'
|
8
|
+
|
9
|
+
require 'rdoc/task'
|
10
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
11
|
+
rdoc.rdoc_dir = 'rdoc'
|
12
|
+
rdoc.title = 'Aenea'
|
13
|
+
rdoc.options << '--line-numbers'
|
14
|
+
rdoc.rdoc_files.include('README.md')
|
15
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
16
|
+
end
|
17
|
+
|
18
|
+
APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__)
|
19
|
+
load 'rails/tasks/engine.rake'
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'rspec/core/rake_task'
|
23
|
+
RSpec::Core::RakeTask.new(:spec)
|
24
|
+
task default: :spec
|
File without changes
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class MarkdownInput < ::Formtastic::Inputs::TextInput
|
2
|
+
def to_html
|
3
|
+
input_wrapping do
|
4
|
+
label_html <<
|
5
|
+
builder.text_area(method, input_html_options) <<
|
6
|
+
template.content_tag(:div, '', class: 'markdown-input__preview', 'data-images': images_json) <<
|
7
|
+
template.render(partial: '/admin/shared/markdown_input/images', object: options[:images]) <<
|
8
|
+
template.render(partial: '/admin/shared/markdown_input/guide')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def images_json
|
15
|
+
return '' unless options[:images].present?
|
16
|
+
|
17
|
+
data = options[:images].map {|ci| [ ci.link_def_key, ci.image.url ] }
|
18
|
+
|
19
|
+
template.send :html_escape, data.to_json
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module ActsAsOnlyFlagged
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
module ClassMethods
|
5
|
+
def acts_as_only_flagged(parent_association, child_association, flag_attr)
|
6
|
+
children_name = child_association.to_s
|
7
|
+
child_name = children_name.singularize
|
8
|
+
|
9
|
+
set_first_child_method = "set_#{flag_attr}_on_first_#{child_name}".to_sym
|
10
|
+
update_siblings_method = "update_#{flag_attr}_on_sibling_#{children_name}".to_sym
|
11
|
+
find_siblings_method = "find_#{parent_association}_other_#{children_name}".to_sym
|
12
|
+
|
13
|
+
before_save set_first_child_method
|
14
|
+
|
15
|
+
define_method set_first_child_method do
|
16
|
+
siblings = send(find_siblings_method)
|
17
|
+
send("#{flag_attr}=", true) if siblings.empty?
|
18
|
+
end
|
19
|
+
|
20
|
+
after_save update_siblings_method
|
21
|
+
|
22
|
+
define_method update_siblings_method do
|
23
|
+
if send(flag_attr)
|
24
|
+
siblings = send(find_siblings_method)
|
25
|
+
siblings.update_all(flag_attr => false) unless siblings.empty?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
define_method find_siblings_method do
|
30
|
+
# handle nil parent
|
31
|
+
return [] unless parent = send(parent_association)
|
32
|
+
|
33
|
+
parent.send(child_association).where.not(id: id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def acts_as_only_flagged_enum(enum_name, flag_attr)
|
38
|
+
set_first_of_same_enum_method = "set_#{flag_attr}_on_first_of_#{enum_name}".to_sym
|
39
|
+
update_others_of_same_enum_method = "update_#{flag_attr}_on_others_of_#{enum_name}".to_sym
|
40
|
+
find_others_of_same_enum_method = "find_others_of_#{enum_name}".to_sym
|
41
|
+
|
42
|
+
before_save set_first_of_same_enum_method
|
43
|
+
|
44
|
+
define_method set_first_of_same_enum_method do
|
45
|
+
others = send(find_others_of_same_enum_method)
|
46
|
+
send("#{flag_attr}=", true) if others.empty?
|
47
|
+
end
|
48
|
+
|
49
|
+
after_save update_others_of_same_enum_method
|
50
|
+
|
51
|
+
define_method update_others_of_same_enum_method do
|
52
|
+
if send(flag_attr)
|
53
|
+
others = send(find_others_of_same_enum_method)
|
54
|
+
others.update_all(flag_attr => false) unless others.empty?
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
define_method find_others_of_same_enum_method do
|
59
|
+
enum_value = send(enum_name)
|
60
|
+
|
61
|
+
self.class.send(enum_value).where.not(id: id)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module HasObfuscatedId
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
cattr_accessor :obsfucator
|
6
|
+
self.obsfucator = Obfuscator.new(name) # IDs change when class name changes
|
7
|
+
end
|
8
|
+
|
9
|
+
def obfuscated_id
|
10
|
+
return nil unless id.present?
|
11
|
+
|
12
|
+
@obfuscated_id ||= self.class.obsfucator.encrypt(id)
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_param
|
16
|
+
obfuscated_id
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
def find_by_obfuscated_id!(obfuscated_id)
|
21
|
+
find(obsfucator.decrypt(obfuscated_id))
|
22
|
+
end
|
23
|
+
|
24
|
+
def find_by_obfuscated_id(obfuscated_id)
|
25
|
+
begin
|
26
|
+
find_by_obfuscated_id!(obfuscated_id)
|
27
|
+
rescue ActiveRecord::RecordNotFound
|
28
|
+
nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Obfuscator
|
34
|
+
def initialize(key)
|
35
|
+
@cipher = OpenSSL::Cipher::Cipher.new('aes-256-cbc')
|
36
|
+
@cipher_key = Digest::SHA256.digest(key)
|
37
|
+
end
|
38
|
+
|
39
|
+
def encrypt(value)
|
40
|
+
c = @cipher.encrypt
|
41
|
+
c.key = @cipher_key
|
42
|
+
Base64.urlsafe_encode64(c.update(value.to_s) + c.final) rescue nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def decrypt(value)
|
46
|
+
c = @cipher.decrypt
|
47
|
+
c.key = @cipher_key
|
48
|
+
c.update(Base64.urlsafe_decode64(value.to_s)) + c.final rescue nil
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module Model
|
2
|
+
extend ActiveSupport::Concern
|
3
|
+
|
4
|
+
included do
|
5
|
+
cattr_accessor :associations_registry
|
6
|
+
|
7
|
+
include ActiveModel::Model
|
8
|
+
include ActiveModelAttributes
|
9
|
+
include ActiveModel::Validations::Callbacks
|
10
|
+
|
11
|
+
include BelongsTo
|
12
|
+
include HasOne
|
13
|
+
include HasMany
|
14
|
+
include Enum
|
15
|
+
end
|
16
|
+
|
17
|
+
def base_attributes
|
18
|
+
Hash[self.class.attributes_registry.map {|attr_name, attr_args| [ attr_name.to_s, send(attr_name).to_s ] }]
|
19
|
+
end
|
20
|
+
|
21
|
+
def attributes(options = {})
|
22
|
+
attrs = base_attributes
|
23
|
+
|
24
|
+
(self.class.associations_registry || {}).each do |assoc, type|
|
25
|
+
next unless (value = send(assoc)).present?
|
26
|
+
|
27
|
+
attrs["#{assoc}_attributes"] = case type
|
28
|
+
when :has_many
|
29
|
+
if options[:as_params]
|
30
|
+
Hash[value.each_with_index.map {|obj, i| [ i.to_s, obj.attributes(options) ] }]
|
31
|
+
else
|
32
|
+
value.map {|obj| obj.attributes(options) }
|
33
|
+
end
|
34
|
+
when :has_one
|
35
|
+
value.attributes(options)
|
36
|
+
else
|
37
|
+
raise "invalid association type #{type} for #{assoc}"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
attrs
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_params
|
45
|
+
attributes(as_params: true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def marked_for_destruction?
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Model
|
2
|
+
module BelongsTo
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def belongs_to(name, options = {})
|
7
|
+
id_attr = "#{name}_id"
|
8
|
+
|
9
|
+
belongs_to_reader id_attr, name, options
|
10
|
+
belongs_to_writer id_attr, name, options
|
11
|
+
|
12
|
+
validates name, presence: true unless options[:optional]
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def belongs_to_reader(id_attr, name, options)
|
18
|
+
define_method name do
|
19
|
+
class_name = options[:polymorphic] ? send("#{name}_type") : name.to_s.camelize
|
20
|
+
klass = class_name.blank? ? nil : class_name.constantize
|
21
|
+
id = send(id_attr)
|
22
|
+
|
23
|
+
klass && id ? klass.find(id) : nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def belongs_to_writer(id_attr, name, options)
|
28
|
+
define_method "#{name}=" do |object|
|
29
|
+
value = object.try(:id)
|
30
|
+
send "#{id_attr}=", value
|
31
|
+
send("#{name}_type=", value ? object.class.name : nil) if options[:polymorphic]
|
32
|
+
|
33
|
+
value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Model
|
2
|
+
module Enum
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
module ClassMethods
|
6
|
+
def enum(attr_name, enum_class_name)
|
7
|
+
klass = enum_class_name.constantize
|
8
|
+
keys = klass.send(attr_name.to_s.pluralize).keys
|
9
|
+
|
10
|
+
keys.each do |key|
|
11
|
+
define_method "#{key}?" do
|
12
|
+
send(attr_name) == key
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
validates attr_name, inclusion: { in: keys } # allow nil values
|
17
|
+
|
18
|
+
define_method "formatted_#{attr_name}" do
|
19
|
+
send(attr_name).titleize
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module Model
|
2
|
+
module HasMany
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
class Association
|
6
|
+
attr_reader :target, :assoc, :singular_name
|
7
|
+
|
8
|
+
def initialize(target, assoc, options = {})
|
9
|
+
@target = target
|
10
|
+
@assoc = assoc.to_sym
|
11
|
+
|
12
|
+
@singular_name = assoc.to_s.singularize
|
13
|
+
raise 'assoc must be plural' if singular_name == assoc.to_s
|
14
|
+
|
15
|
+
class_name = options[:class_name] || singular_name.to_s.camelize
|
16
|
+
klass = class_name.constantize
|
17
|
+
|
18
|
+
var_name = "@#{assoc}".to_sym
|
19
|
+
builder_method_name = "build_#{singular_name}".to_sym
|
20
|
+
selector_method_name = "#{assoc}_with".to_sym
|
21
|
+
finder_method_name = "find_#{singular_name}_with".to_sym
|
22
|
+
validate_method_name = "validate_associated_#{assoc}".to_sym
|
23
|
+
|
24
|
+
define_reader var_name
|
25
|
+
define_attributes_writer var_name, builder_method_name
|
26
|
+
define_builder builder_method_name, klass, assoc
|
27
|
+
define_selector selector_method_name, assoc
|
28
|
+
define_finder finder_method_name, selector_method_name
|
29
|
+
define_find_or_build_method finder_method_name, builder_method_name
|
30
|
+
define_present_method assoc
|
31
|
+
define_empty_method assoc
|
32
|
+
define_validate_method validate_method_name, assoc
|
33
|
+
|
34
|
+
if validate_options = options[:validate]
|
35
|
+
target.validate validate_method_name, validate_options.is_a?(Hash) ? validate_options : {}
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def define_reader(var_name)
|
42
|
+
# def things
|
43
|
+
target.send :define_method, assoc do
|
44
|
+
instance_variable_get(var_name) || instance_variable_set(var_name, []) # always return non-nil value
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def define_attributes_writer(var_name, builder_method_name)
|
49
|
+
# def things_attributes=(params)
|
50
|
+
target.send :define_method, "#{assoc}_attributes=" do |params|
|
51
|
+
values = params.is_a?(Array) ? params : params.values # Hash or ActiveSupport::HashWithIndifferentAccess
|
52
|
+
instance_variable_set var_name, values.map {|attrs| send(builder_method_name, attrs) }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def define_builder(builder_method_name, klass, assoc)
|
57
|
+
# def build_thing(attrs = {})
|
58
|
+
target.send :define_method, builder_method_name do |attrs = {}|
|
59
|
+
obj = klass.new(attrs)
|
60
|
+
send(assoc) << obj
|
61
|
+
|
62
|
+
obj
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def define_selector(selector_method_name, assoc)
|
67
|
+
# def things_with(attrs)
|
68
|
+
target.send :define_method, selector_method_name do |attrs|
|
69
|
+
send(assoc).select {|obj|
|
70
|
+
found = true
|
71
|
+
attrs.each do |key, value|
|
72
|
+
unless obj.send(key) == value
|
73
|
+
found = false
|
74
|
+
break
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
found
|
79
|
+
}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def define_finder(finder_method_name, selector_method_name)
|
84
|
+
# def find_thing_with(attrs)
|
85
|
+
target.send :define_method, finder_method_name do |attrs|
|
86
|
+
send(selector_method_name, attrs).first
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def define_find_or_build_method(finder_method_name, builder_method_name)
|
91
|
+
# def find_or_build_thing_with(attrs)
|
92
|
+
target.send :define_method, "find_or_build_#{singular_name}_with" do |attrs|
|
93
|
+
send(finder_method_name, attrs) || send(builder_method_name, attrs)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def define_present_method(assoc)
|
98
|
+
# def things_present?
|
99
|
+
target.send :define_method, "#{assoc}_present?" do
|
100
|
+
send(assoc).present?
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def define_empty_method(assoc)
|
105
|
+
# def things_empty?
|
106
|
+
target.send :define_method, "#{assoc}_empty?" do
|
107
|
+
send(assoc).empty?
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def define_validate_method(validate_method_name, assoc)
|
112
|
+
# def validate_associated_things
|
113
|
+
target.send :define_method, validate_method_name do
|
114
|
+
errors.add assoc, 'is invalid' unless send(assoc).inject(true) {|valid, obj| valid = valid && obj.valid? }
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
module ClassMethods
|
120
|
+
def has_many(name, options = {})
|
121
|
+
(self.associations_registry ||= {})[name] = :has_many
|
122
|
+
|
123
|
+
Association.new self, name, options
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|