aenea 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +39 -0
  3. data/Rakefile +24 -0
  4. data/app/assets/config/aenea_manifest.js +0 -0
  5. data/app/helpers/inline_boolean_input.rb +9 -0
  6. data/app/helpers/markdown_input.rb +21 -0
  7. data/app/helpers/non_input_input.rb +7 -0
  8. data/app/models/concerns/acts_as_only_flagged.rb +65 -0
  9. data/app/models/concerns/can_be_inactive.rb +8 -0
  10. data/app/models/concerns/has_external_id.rb +9 -0
  11. data/app/models/concerns/has_obfuscated_id.rb +51 -0
  12. data/app/models/concerns/model.rb +51 -0
  13. data/app/models/concerns/model/belongs_to.rb +38 -0
  14. data/app/models/concerns/model/enum.rb +24 -0
  15. data/app/models/concerns/model/has_many.rb +127 -0
  16. data/app/models/concerns/model/has_one.rb +77 -0
  17. data/app/models/concerns/sluggable.rb +7 -0
  18. data/app/models/countries.rb +278 -0
  19. data/app/models/country_validator.rb +10 -0
  20. data/app/models/database_function_helper.rb +88 -0
  21. data/app/models/google_maps_api.rb +19 -0
  22. data/app/models/google_maps_api/geocode.rb +29 -0
  23. data/app/models/google_maps_api/time_zone.rb +29 -0
  24. data/app/models/json_api.rb +16 -0
  25. data/app/models/phone_number_validator.rb +10 -0
  26. data/app/models/us_state_validator.rb +10 -0
  27. data/app/models/us_states.rb +75 -0
  28. data/app/models/zip_code_validator.rb +12 -0
  29. data/lib/aenea.rb +12 -0
  30. data/lib/aenea/active_admin.rb +23 -0
  31. data/lib/aenea/active_admin/index_as_table.rb +9 -0
  32. data/lib/aenea/active_admin/index_as_table/sortable_columns.rb +29 -0
  33. data/lib/aenea/active_admin/resource_controller.rb +9 -0
  34. data/lib/aenea/active_admin/resource_controller/find_sluggable.rb +21 -0
  35. data/lib/aenea/active_admin/resource_dsl.rb +11 -0
  36. data/lib/aenea/active_admin/resource_dsl/sortable_actions.rb +53 -0
  37. data/lib/aenea/active_admin/resource_dsl/use_parent_actions.rb +79 -0
  38. data/lib/aenea/date.rb +14 -0
  39. data/lib/aenea/engine.rb +20 -0
  40. data/lib/aenea/enum.rb +13 -0
  41. data/lib/aenea/enum/changes.rb +61 -0
  42. data/lib/aenea/enum/ext.rb +68 -0
  43. data/lib/aenea/enum/groups.rb +53 -0
  44. data/lib/aenea/markdown_attr.rb +43 -0
  45. data/lib/aenea/time.rb +9 -0
  46. data/lib/aenea/time_of_day.rb +41 -0
  47. data/lib/aenea/time_window.rb +86 -0
  48. data/lib/aenea/version.rb +3 -0
  49. data/lib/tasks/aenea_tasks.rake +4 -0
  50. metadata +203 -0
@@ -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
@@ -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).
@@ -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,9 @@
1
+ class InlineBooleanInput < Formtastic::Inputs::BooleanInput
2
+ def label_text_with_embedded_checkbox
3
+ label_text << '' << check_box_html
4
+ end
5
+
6
+ def label_html_options
7
+ super.merge(class: 'inline_boolean_label')
8
+ end
9
+ end
@@ -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,7 @@
1
+ class NonInputInput < ::Formtastic::Inputs::StringInput
2
+ def to_html
3
+ input_wrapping do
4
+ label_html << template.content_tag(:div, options[:content], class: 'non_input_content')
5
+ end
6
+ end
7
+ 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,8 @@
1
+ module CanBeInactive
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ scope :active, -> { where(inactive: false) }
6
+ scope :inactive, -> { where(inactive: true) }
7
+ end
8
+ end
@@ -0,0 +1,9 @@
1
+ module HasExternalId
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ validates :external_id, presence: true, uniqueness: true
6
+
7
+ before_validation {|record| record.external_id ||= SecureRandom.urlsafe_base64(32) }
8
+ end
9
+ 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