mno-enterprise-core 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +1 -0
- data/Rakefile +12 -0
- data/app/assets/images/mno_enterprise/main-logo.png +0 -0
- data/app/controllers/mno_enterprise/application_controller.rb +116 -0
- data/app/helpers/mno_enterprise/application_helper.rb +67 -0
- data/app/helpers/mno_enterprise/impersonate_helper.rb +27 -0
- data/app/models/mno_enterprise/ability.rb +6 -0
- data/app/models/mno_enterprise/app.rb +72 -0
- data/app/models/mno_enterprise/app_instance.rb +36 -0
- data/app/models/mno_enterprise/app_instances_sync.rb +6 -0
- data/app/models/mno_enterprise/arrears_situation.rb +6 -0
- data/app/models/mno_enterprise/audit_event.rb +21 -0
- data/app/models/mno_enterprise/base_resource.rb +228 -0
- data/app/models/mno_enterprise/credit_card.rb +40 -0
- data/app/models/mno_enterprise/deletion_request.rb +35 -0
- data/app/models/mno_enterprise/impac/dashboard.rb +36 -0
- data/app/models/mno_enterprise/impac/dashboard_provisioner.rb +5 -0
- data/app/models/mno_enterprise/impac/kpi.rb +9 -0
- data/app/models/mno_enterprise/impac/widget.rb +13 -0
- data/app/models/mno_enterprise/invoice.rb +53 -0
- data/app/models/mno_enterprise/org_invite.rb +50 -0
- data/app/models/mno_enterprise/organization.rb +33 -0
- data/app/models/mno_enterprise/team.rb +50 -0
- data/app/models/mno_enterprise/tenant.rb +5 -0
- data/app/models/mno_enterprise/tenant_invoice.rb +5 -0
- data/app/models/mno_enterprise/user.rb +183 -0
- data/app/pdf/mno_enterprise/invoice_pdf.rb +516 -0
- data/config/initializers/audit_log.rb +5 -0
- data/config/locales/devise.en.yml +60 -0
- data/config/routes.rb +2 -0
- data/config/styleguide.yml +106 -0
- data/lib/accountingjs_serializer.rb +51 -0
- data/lib/devise/controllers/extension_helpers.rb +52 -0
- data/lib/devise/extension_routes.rb +11 -0
- data/lib/devise/hooks/password_expirable.rb +5 -0
- data/lib/devise/models/password_expirable.rb +28 -0
- data/lib/devise/models/remote_authenticatable.rb +48 -0
- data/lib/devise/strategies/remote_authenticatable.rb +44 -0
- data/lib/devise_extension.rb +36 -0
- data/lib/faraday/adapter/net_http_no_proxy.rb +19 -0
- data/lib/generators/mno_enterprise/database_extension/USAGE +11 -0
- data/lib/generators/mno_enterprise/database_extension/database_extension_generator.rb +36 -0
- data/lib/generators/mno_enterprise/database_extension/templates/model.rb +9 -0
- data/lib/generators/mno_enterprise/dummy/dummy_generator.rb +98 -0
- data/lib/generators/mno_enterprise/dummy/templates/rails/application.rb.erb +9 -0
- data/lib/generators/mno_enterprise/dummy/templates/rails/boot.rb.erb +6 -0
- data/lib/generators/mno_enterprise/dummy/templates/rails/database.yml +22 -0
- data/lib/generators/mno_enterprise/dummy/templates/rails/routes.rb +8 -0
- data/lib/generators/mno_enterprise/dummy/templates/rails/test-env.rb +45 -0
- data/lib/generators/mno_enterprise/install/install_generator.rb +140 -0
- data/lib/generators/mno_enterprise/install/templates/Procfile +1 -0
- data/lib/generators/mno_enterprise/install/templates/config/initializers/mno_enterprise.rb +135 -0
- data/lib/generators/mno_enterprise/install/templates/config/mno_enterprise_styleguide.yml +104 -0
- data/lib/generators/mno_enterprise/install/templates/javascripts/mno_enterprise_extensions.js +7 -0
- data/lib/generators/mno_enterprise/install/templates/stylesheets/main.less_erb +25 -0
- data/lib/generators/mno_enterprise/install/templates/stylesheets/theme.less_erb +59 -0
- data/lib/generators/mno_enterprise/install/templates/stylesheets/variables.less +337 -0
- data/lib/generators/mno_enterprise/install/templates/tasks/sprites.rake +14 -0
- data/lib/generators/mno_enterprise/puma_stack/puma_stack_generator.rb +58 -0
- data/lib/generators/mno_enterprise/templates/scripts/monit/app-server.conf +8 -0
- data/lib/generators/mno_enterprise/templates/scripts/nginx/app +51 -0
- data/lib/generators/mno_enterprise/templates/scripts/puma.rb +25 -0
- data/lib/generators/mno_enterprise/templates/scripts/setup.sh +27 -0
- data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-hotrestart.conf +26 -0
- data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web-server.conf +34 -0
- data/lib/generators/mno_enterprise/templates/scripts/upstart/app-web.conf +2 -0
- data/lib/generators/mno_enterprise/templates/scripts/upstart/app.conf +11 -0
- data/lib/her_extension/her_orm_adapter.rb +54 -0
- data/lib/her_extension/middleware/mnoe_api_v1_parse_json.rb +54 -0
- data/lib/her_extension/model/associations/association.rb +61 -0
- data/lib/her_extension/model/associations/association_proxy.rb +34 -0
- data/lib/her_extension/model/associations/has_many_association.rb +115 -0
- data/lib/her_extension/model/attributes.rb +43 -0
- data/lib/her_extension/model/orm.rb +59 -0
- data/lib/her_extension/model/parse.rb +40 -0
- data/lib/her_extension/model/relation.rb +92 -0
- data/lib/her_extension/validations/remote_uniqueness_validation.rb +33 -0
- data/lib/html_processor.rb +106 -0
- data/lib/mandrill_client.rb +58 -0
- data/lib/mno-enterprise-core.rb +1 -0
- data/lib/mno_enterprise/concerns.rb +4 -0
- data/lib/mno_enterprise/concerns/controllers.rb +6 -0
- data/lib/mno_enterprise/concerns/controllers/angular_csrf.rb +59 -0
- data/lib/mno_enterprise/concerns/controllers/auth.rb +9 -0
- data/lib/mno_enterprise/concerns/controllers/auth/confirmations_controller.rb +187 -0
- data/lib/mno_enterprise/concerns/controllers/auth/passwords_controller.rb +54 -0
- data/lib/mno_enterprise/concerns/controllers/auth/registrations_controller.rb +136 -0
- data/lib/mno_enterprise/concerns/controllers/auth/sessions_controller.rb +54 -0
- data/lib/mno_enterprise/concerns/controllers/auth/unlocks_controller.rb +50 -0
- data/lib/mno_enterprise/concerns/models.rb +6 -0
- data/lib/mno_enterprise/concerns/models/ability.rb +108 -0
- data/lib/mno_enterprise/concerns/models/app_instance.rb +100 -0
- data/lib/mno_enterprise/concerns/models/organization.rb +102 -0
- data/lib/mno_enterprise/core.rb +279 -0
- data/lib/mno_enterprise/database_extendable.rb +57 -0
- data/lib/mno_enterprise/engine.rb +33 -0
- data/lib/mno_enterprise/testing_support/ability_test_helper.rb +10 -0
- data/lib/mno_enterprise/testing_support/common_rake.rb +19 -0
- data/lib/mno_enterprise/testing_support/factories.rb +13 -0
- data/lib/mno_enterprise/testing_support/factories/app_instances.rb +30 -0
- data/lib/mno_enterprise/testing_support/factories/apps.rb +45 -0
- data/lib/mno_enterprise/testing_support/factories/arrears_situation.rb +14 -0
- data/lib/mno_enterprise/testing_support/factories/audit_event.rb +15 -0
- data/lib/mno_enterprise/testing_support/factories/credit_card.rb +33 -0
- data/lib/mno_enterprise/testing_support/factories/deletion_request.rb +17 -0
- data/lib/mno_enterprise/testing_support/factories/impac/dashboards.rb +15 -0
- data/lib/mno_enterprise/testing_support/factories/impac/kpis.rb +20 -0
- data/lib/mno_enterprise/testing_support/factories/impac/widgets.rb +15 -0
- data/lib/mno_enterprise/testing_support/factories/invoices.rb +51 -0
- data/lib/mno_enterprise/testing_support/factories/org_invite.rb +24 -0
- data/lib/mno_enterprise/testing_support/factories/organizations.rb +25 -0
- data/lib/mno_enterprise/testing_support/factories/team.rb +17 -0
- data/lib/mno_enterprise/testing_support/factories/tenant.rb +12 -0
- data/lib/mno_enterprise/testing_support/factories/tenant_invoice.rb +29 -0
- data/lib/mno_enterprise/testing_support/factories/users.rb +48 -0
- data/lib/mno_enterprise/testing_support/jpi_v1_test_helper.rb +49 -0
- data/lib/mno_enterprise/testing_support/mno_enterprise_api_test_helper.rb +167 -0
- data/lib/mno_enterprise/testing_support/mnoe_faraday_test_adapter.rb +173 -0
- data/lib/mno_enterprise/testing_support/organizations_shared_helpers.rb +175 -0
- data/lib/mno_enterprise/testing_support/user_action_shared.rb +47 -0
- data/lib/mno_enterprise/version.rb +3 -0
- data/lib/tasks/mno_enterprise_tasks.rake +22 -0
- data/spec/controllers/mno_enterprise/angular_csrf_spec.rb +42 -0
- data/spec/lib/her_extension/her_orm_adapter.rb +7 -0
- data/spec/lib/her_extension/model/relation_spec.rb +7 -0
- data/spec/lib/mandrill_client_spec.rb +64 -0
- data/spec/mno_enterprise_spec.rb +79 -0
- data/spec/models/mno_enterprise/app_instance_spec.rb +7 -0
- data/spec/models/mno_enterprise/app_spec.rb +62 -0
- data/spec/models/mno_enterprise/base_resource_spec.rb +28 -0
- data/spec/models/mno_enterprise/deletion_request_spec.rb +26 -0
- data/spec/models/mno_enterprise/invoice_spec.rb +7 -0
- data/spec/models/mno_enterprise/organization_spec.rb +7 -0
- data/spec/models/mno_enterprise/user_spec.rb +44 -0
- data/spec/rails_helper.rb +73 -0
- data/spec/spec_helper.rb +78 -0
- metadata +421 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
# PR:
|
2
|
+
# fixed options loading in dynamically defined method
|
3
|
+
# changed build to not add the parent key automatically
|
4
|
+
# changed create to send raw parameters instead of doing smart stuff
|
5
|
+
module Her
|
6
|
+
module Model
|
7
|
+
module Associations
|
8
|
+
class HasManyAssociation < Association
|
9
|
+
|
10
|
+
# @private
|
11
|
+
def self.attach(klass, name, opts)
|
12
|
+
opts = {
|
13
|
+
:class_name => name.to_s.classify,
|
14
|
+
:name => name,
|
15
|
+
:data_key => name,
|
16
|
+
:default => Her::Collection.new,
|
17
|
+
:path => "/#{name}",
|
18
|
+
:inverse_of => nil
|
19
|
+
}.merge(opts)
|
20
|
+
klass.associations[:has_many] << opts
|
21
|
+
|
22
|
+
klass.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
23
|
+
def #{name}
|
24
|
+
cached_name = :"@_her_association_#{name}"
|
25
|
+
cached_data = (instance_variable_defined?(cached_name) && instance_variable_get(cached_name))
|
26
|
+
opts = Marshal.load(#{Marshal.dump(opts).inspect})
|
27
|
+
cached_data || instance_variable_set(cached_name, Her::Model::Associations::HasManyAssociation.proxy(self, opts))
|
28
|
+
end
|
29
|
+
RUBY
|
30
|
+
end
|
31
|
+
|
32
|
+
# Initialize a new object with a foreign key to the parent
|
33
|
+
#
|
34
|
+
# @example
|
35
|
+
# class User
|
36
|
+
# include Her::Model
|
37
|
+
# has_many :comments
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# class Comment
|
41
|
+
# include Her::Model
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
# user = User.find(1)
|
45
|
+
# new_comment = user.comments.build(:body => "Hello!")
|
46
|
+
# new_comment # => #<Comment body="Hello!">
|
47
|
+
#
|
48
|
+
def build(attributes = {})
|
49
|
+
@klass.build(attributes)
|
50
|
+
end
|
51
|
+
|
52
|
+
# Post an object to the nested resource collection endpoint then
|
53
|
+
# refetch the nested collection
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# class User
|
57
|
+
# include Her::Model
|
58
|
+
# has_many :comments
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# class Comment
|
62
|
+
# include Her::Model
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# user = User.find(1)
|
66
|
+
# user.comments.create(:body => "Hello!")
|
67
|
+
# user.comments # => [#<Comment id=2 user_id=1 body="Hello!">]
|
68
|
+
def create(attributes = {})
|
69
|
+
resp = self.execute_request(:create,attributes)
|
70
|
+
@klass.build(resp)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Consider removing - not sure this method on a has_many collection has any meaning
|
74
|
+
def update(attributes = {})
|
75
|
+
self.execute_request(:update,attributes)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Consider removing - not sure this method on a has_many collection has any meaning
|
79
|
+
def destroy(attributes = {})
|
80
|
+
self.execute_request(:destroy,attributes)
|
81
|
+
end
|
82
|
+
|
83
|
+
def execute_request(action, attrs)
|
84
|
+
attributes = HashWithIndifferentAccess.new(attrs)
|
85
|
+
|
86
|
+
# Post data to the collection endpoint
|
87
|
+
resource = nil
|
88
|
+
path = self.build_request_path
|
89
|
+
method = self.method_for(action.to_sym)
|
90
|
+
|
91
|
+
# Add ID to path if resource method
|
92
|
+
if [:put, :patch, :delete].include?(method.to_sym)
|
93
|
+
path += "/#{attributes[@klass.primary_key]}"
|
94
|
+
end
|
95
|
+
|
96
|
+
params = self.to_params(attributes).merge(:_method => method, :_path => path)
|
97
|
+
self.request(params) do |parsed_data, response|
|
98
|
+
resource = parsed_data if response.success?
|
99
|
+
end
|
100
|
+
|
101
|
+
# Reload nested collection
|
102
|
+
self.reload if resource
|
103
|
+
|
104
|
+
resource
|
105
|
+
end
|
106
|
+
|
107
|
+
# Override fetch to not do any smart stuff...
|
108
|
+
def fetch
|
109
|
+
super
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Her
|
2
|
+
module Model
|
3
|
+
# TODO: make PR to Her project
|
4
|
+
# This patch fixes the detection of changed attributes
|
5
|
+
module Attributes
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Define the attributes that will be used to track dirty attributes and validations
|
11
|
+
#
|
12
|
+
# @param [Array] attributes
|
13
|
+
# @example
|
14
|
+
# class User
|
15
|
+
# include Her::Model
|
16
|
+
# attributes :name, :email
|
17
|
+
# end
|
18
|
+
def attributes(*attributes)
|
19
|
+
define_attribute_methods attributes
|
20
|
+
|
21
|
+
attributes.each do |attribute|
|
22
|
+
attribute = attribute.to_sym
|
23
|
+
|
24
|
+
unless instance_methods.include?(:"#{attribute}=")
|
25
|
+
define_method("#{attribute}=") do |value|
|
26
|
+
@attributes[:"#{attribute}"] = nil unless @attributes.include?(:"#{attribute}")
|
27
|
+
self.send(:"#{attribute}_will_change!") if @attributes[:"#{attribute}"] != value
|
28
|
+
@attributes[:"#{attribute}"] = value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
unless instance_methods.include?(:"#{attribute}?")
|
33
|
+
define_method("#{attribute}?") do
|
34
|
+
@attributes.include?(:"#{attribute}") && @attributes[:"#{attribute}"].present?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# FIX: Reset params when blank_relation is called so that query statement (like where)
|
2
|
+
# automatically start fresh
|
3
|
+
#
|
4
|
+
# FIX: scope to create real isolated scopes for each Her::Model
|
5
|
+
#
|
6
|
+
# FIX: add put method on model
|
7
|
+
module Her
|
8
|
+
module Model
|
9
|
+
module ORM
|
10
|
+
|
11
|
+
# Send raw PUT request to model - no data encapsulation performed
|
12
|
+
def put(attrs)
|
13
|
+
method = :put
|
14
|
+
self.class.request(attrs.merge(:_method => method, :_path => request_path)) do |parsed_data, response|
|
15
|
+
return parsed_data
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
module ClassMethods
|
20
|
+
# Create a new chainable scope
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# class User
|
24
|
+
# include Her::Model
|
25
|
+
#
|
26
|
+
# scope :admins, lambda { where(:admin => 1) }
|
27
|
+
# scope :page, lambda { |page| where(:page => page) }
|
28
|
+
# enc
|
29
|
+
#
|
30
|
+
# User.admins # Called via GET "/users?admin=1"
|
31
|
+
# User.page(2).all # Called via GET "/users?page=2"
|
32
|
+
def scope(name, code)
|
33
|
+
|
34
|
+
# Add the scope method to the class
|
35
|
+
metaclass = (class << self; self end)
|
36
|
+
metaclass.send(:define_method, name) do |*args|
|
37
|
+
instance_exec(*args, &code)
|
38
|
+
end
|
39
|
+
Relation.scopes["#{self.to_s}.#{name}"] = code
|
40
|
+
|
41
|
+
# Add the scope method to the Relation class
|
42
|
+
Relation.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
43
|
+
def #{name}(*args)
|
44
|
+
parent_klass = @parent.instance_variable_get("@klass") || @parent.to_s
|
45
|
+
instance_exec(*args,&self.class.scopes["\#{parent_klass}.#{name}"])
|
46
|
+
end
|
47
|
+
RUBY
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
def blank_relation
|
52
|
+
@blank_relation ||= Relation.new(self)
|
53
|
+
@blank_relation.params = {}
|
54
|
+
@blank_relation
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# TODO: PR to HER
|
2
|
+
# Fix to_params when embeded_params is nil
|
3
|
+
# Fix to_params when changes is nil
|
4
|
+
# --> allow all params - this is required to be able to make class level
|
5
|
+
# requests like MyModel.post(path,{some: 'data'})
|
6
|
+
#
|
7
|
+
module Her
|
8
|
+
module Model
|
9
|
+
# This module handles resource data parsing at the model level (after the parsing middleware)
|
10
|
+
module Parse
|
11
|
+
extend ActiveSupport::Concern
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
|
15
|
+
# @private
|
16
|
+
def to_params(attributes, changes = nil)
|
17
|
+
filtered_attributes = attributes.dup.symbolize_keys
|
18
|
+
filtered_attributes.merge!(embeded_params(attributes) || {})
|
19
|
+
if her_api && her_api.options[:send_only_modified_attributes] && !changes.nil?
|
20
|
+
filtered_attributes = changes.symbolize_keys.keys.inject({}) do |hash, attribute|
|
21
|
+
hash[attribute] = filtered_attributes[attribute]
|
22
|
+
hash
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
if include_root_in_json?
|
27
|
+
if json_api_format?
|
28
|
+
{ included_root_element => [filtered_attributes] }
|
29
|
+
else
|
30
|
+
{ included_root_element => filtered_attributes }
|
31
|
+
end
|
32
|
+
else
|
33
|
+
filtered_attributes
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
# TODO: we should do a PR to the HER project which includes
|
4
|
+
# smarter filtering and more reliable caching
|
5
|
+
# Before doing so, we make the filtering behavior customizable
|
6
|
+
# (E.g.: configure name of filter/sort/limit keys)
|
7
|
+
module Her
|
8
|
+
module Model
|
9
|
+
class Relation
|
10
|
+
def initialize(parent)
|
11
|
+
@parent = parent
|
12
|
+
@params = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
# Fetch a collection of resources
|
16
|
+
def fetch
|
17
|
+
path = @parent.build_request_path(@params)
|
18
|
+
method = @parent.method_for(:find)
|
19
|
+
@parent.request(@params.merge(:_method => method, :_path => path)) do |parsed_data, response|
|
20
|
+
@parent.new_collection(parsed_data)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Override Her::Model::Relation#where
|
25
|
+
# to follow jsonapi.org standards
|
26
|
+
# Use filter instead of raw parameters
|
27
|
+
def where(params = {})
|
28
|
+
return self if !params || params.empty?
|
29
|
+
self.params[:filter] ||= {}
|
30
|
+
self.params[:filter].merge!(params)
|
31
|
+
self
|
32
|
+
end
|
33
|
+
alias all where
|
34
|
+
|
35
|
+
# E.g:
|
36
|
+
# Product.order_by('created_at.desc','name.asc')
|
37
|
+
def order_by(*args)
|
38
|
+
return self if args.empty?
|
39
|
+
self.params[:sort] = [self.params[:sort],args].flatten.compact.uniq
|
40
|
+
self
|
41
|
+
end
|
42
|
+
alias sort_by order_by
|
43
|
+
|
44
|
+
# ActiveRecord-like order
|
45
|
+
# Product.order("created_at DESC, name ASC")
|
46
|
+
def order(string_query)
|
47
|
+
return self if !string_query || string_query.empty?
|
48
|
+
args = string_query.split(',').map do |q|
|
49
|
+
field, direction = q.strip.split(/\s+/).compact
|
50
|
+
[field, direction ? direction.downcase : nil].join('.')
|
51
|
+
end
|
52
|
+
self.order_by(*args)
|
53
|
+
end
|
54
|
+
alias sort order
|
55
|
+
|
56
|
+
# Limit the number of results returned
|
57
|
+
def limit(max = nil)
|
58
|
+
return self if !max
|
59
|
+
self.params[:limit] = max
|
60
|
+
self
|
61
|
+
end
|
62
|
+
|
63
|
+
# Skip result rows
|
64
|
+
def skip(nrows = nil)
|
65
|
+
return self if !nrows
|
66
|
+
self.params[:skip] = nrows
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
# Reset the query parameters
|
71
|
+
def reset_params
|
72
|
+
@params.clear
|
73
|
+
end
|
74
|
+
|
75
|
+
# Refetch the relation
|
76
|
+
def reload
|
77
|
+
fetch
|
78
|
+
end
|
79
|
+
|
80
|
+
# Hold the scopes defined on Her::Model classes
|
81
|
+
# E.g.: scope :admin, -> { where(admin: 1) }
|
82
|
+
def self.scopes
|
83
|
+
@scopes ||= {}
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
def query_checkum
|
88
|
+
Digest::MD5.hexdigest(Marshal.dump(@params))
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# TODO:
|
2
|
+
# - spec module
|
3
|
+
# - spec that validator ignores records with the same id
|
4
|
+
module HerExtension
|
5
|
+
module Validations
|
6
|
+
|
7
|
+
# Validate the uniqueness of a field by performing a remote API call
|
8
|
+
class RemoteUniquenessValidator < ::ActiveModel::EachValidator
|
9
|
+
def validate_each(record,attribute,value)
|
10
|
+
list = record.class.where({ attribute => value }).limit(1)
|
11
|
+
|
12
|
+
if list.reject { |e| e.id == record.id }.any?
|
13
|
+
error_options = options.except(:case_sensitive, :scope, :conditions)
|
14
|
+
error_options[:value] = value
|
15
|
+
record.errors.add(attribute, :taken, error_options)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# This module overrides validates_uniqueness_of
|
21
|
+
module RemoteUniquenessValidation
|
22
|
+
def self.included(base)
|
23
|
+
base.extend(ClassMethods)
|
24
|
+
end
|
25
|
+
|
26
|
+
module ClassMethods
|
27
|
+
def validates_uniqueness_of(*attr_names)
|
28
|
+
validates_with RemoteUniquenessValidator, _merge_attributes(attr_names)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'redcarpet'
|
2
|
+
require 'nokogiri'
|
3
|
+
require 'sanitize'
|
4
|
+
|
5
|
+
# This utility class is used to
|
6
|
+
# work on html text
|
7
|
+
#
|
8
|
+
# You can initialize it with html or markdown text
|
9
|
+
class HtmlProcessor
|
10
|
+
attr_reader :html, :original
|
11
|
+
|
12
|
+
#======================================
|
13
|
+
# Constants
|
14
|
+
#======================================
|
15
|
+
DESCRIPTION_PROCESSING_ORDER = %w( p h1 h2 h3 h4 h5 h6 )
|
16
|
+
|
17
|
+
|
18
|
+
# Define Youtube transformer for Sanitize
|
19
|
+
YOUTUBE_TRANSFORMER = lambda do |env|
|
20
|
+
node = env[:node]
|
21
|
+
node_name = env[:node_name]
|
22
|
+
|
23
|
+
# Don't continue if this node is already whitelisted or is not an element.
|
24
|
+
return if env[:is_whitelisted] || !node.element?
|
25
|
+
|
26
|
+
# Don't continue unless the node is an iframe.
|
27
|
+
return unless node_name == 'iframe'
|
28
|
+
|
29
|
+
# Verify that the video URL is actually a valid YouTube video URL.
|
30
|
+
return unless node['src'] =~ %r|\A(?:https?:)?//(?:www\.)?youtube(?:-nocookie)?\.com/|
|
31
|
+
|
32
|
+
# We're now certain that this is a YouTube embed, but we still need to run
|
33
|
+
# it through a special Sanitize step to ensure that no unwanted elements or
|
34
|
+
# attributes that don't belong in a YouTube embed can sneak in.
|
35
|
+
Sanitize.node!(node, {
|
36
|
+
:elements => %w[iframe],
|
37
|
+
|
38
|
+
:attributes => {
|
39
|
+
'iframe' => %w[allowfullscreen frameborder height src width]
|
40
|
+
}
|
41
|
+
})
|
42
|
+
|
43
|
+
# Now that we're sure that this is a valid YouTube embed and that there are
|
44
|
+
# no unwanted elements or attributes hidden inside it, we can tell Sanitize
|
45
|
+
# to whitelist the current node.
|
46
|
+
{:node_whitelist => [node]}
|
47
|
+
end
|
48
|
+
|
49
|
+
# Default options for Sanitize
|
50
|
+
SANITIZER_OPTS = Sanitize::Config::RELAXED.merge(
|
51
|
+
attributes: Sanitize::Config::RELAXED[:attributes].merge(
|
52
|
+
'a' => %w[href hreflang name rel target],
|
53
|
+
),
|
54
|
+
transformers: YOUTUBE_TRANSFORMER
|
55
|
+
)
|
56
|
+
|
57
|
+
#======================================
|
58
|
+
# Methods
|
59
|
+
#======================================
|
60
|
+
def initialize(text, options = { })
|
61
|
+
@original = text
|
62
|
+
|
63
|
+
# Process markdown or leave original
|
64
|
+
if options[:format].to_s == 'markdown' && text
|
65
|
+
html_options = { :safe_links_only => true, :hard_wrap => true, :filter_html => false }
|
66
|
+
renderer_options = { :autolink => true, :no_intraemphasis => true, :fenced_code_blocks => true, :superscript => true }
|
67
|
+
|
68
|
+
renderer = Redcarpet::Markdown.new(Redcarpet::Render::HTML.new(html_options), renderer_options)
|
69
|
+
raw_html = renderer.render(text)
|
70
|
+
@html = Sanitize.fragment(raw_html, SANITIZER_OPTS)
|
71
|
+
else
|
72
|
+
@html = text
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Return a Nokogiri document based
|
77
|
+
# on processor html
|
78
|
+
def document
|
79
|
+
@document ||= Nokogiri::HTML(@html)
|
80
|
+
end
|
81
|
+
|
82
|
+
# Return a description of the document
|
83
|
+
# by returning the first sentence of the
|
84
|
+
# first DESCRIPTION_PROCESSING_ORDER found
|
85
|
+
def description
|
86
|
+
# Return cached value if one
|
87
|
+
return @description if @description
|
88
|
+
|
89
|
+
# Parse the html document to try to find
|
90
|
+
# a description
|
91
|
+
@description = ''
|
92
|
+
DESCRIPTION_PROCESSING_ORDER.each do |selector|
|
93
|
+
elem = self.document.css(selector).select { |e| e && !e.content.blank? }.first
|
94
|
+
next if elem.blank? #skip if nil or empty
|
95
|
+
|
96
|
+
# Try to get the first two sentences
|
97
|
+
match = elem.content.match(/([^.!?]+[.!?]?)([^.!?]+[.!?]?)?/)
|
98
|
+
if match && match.captures.any?
|
99
|
+
@description = match.captures.compact.join('')
|
100
|
+
end
|
101
|
+
break if !@description.empty?
|
102
|
+
end
|
103
|
+
|
104
|
+
return @description
|
105
|
+
end
|
106
|
+
end
|