web-connect 0.1.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 +1 -0
- data/Rakefile +3 -0
- data/lib/netfira/web_connect/components/checksum.rb +19 -0
- data/lib/netfira/web_connect/components/guid.rb +11 -0
- data/lib/netfira/web_connect/components/octa_word.rb +73 -0
- data/lib/netfira/web_connect/components.rb +1 -0
- data/lib/netfira/web_connect/configuration.rb +33 -0
- data/lib/netfira/web_connect/db_schema/20140514_support.rb +38 -0
- data/lib/netfira/web_connect/db_schema/20140515_alpha.rb +106 -0
- data/lib/netfira/web_connect/events.rb +14 -0
- data/lib/netfira/web_connect/migration/base_methods.rb +59 -0
- data/lib/netfira/web_connect/migration/refinements.rb +16 -0
- data/lib/netfira/web_connect/migration.rb +101 -0
- data/lib/netfira/web_connect/model/record/digests.rb +24 -0
- data/lib/netfira/web_connect/model/record/events.rb +35 -0
- data/lib/netfira/web_connect/model/record/file_record.rb +79 -0
- data/lib/netfira/web_connect/model/record/materialization.rb +59 -0
- data/lib/netfira/web_connect/model/record/relations.rb +51 -0
- data/lib/netfira/web_connect/model/record/sendable.rb +29 -0
- data/lib/netfira/web_connect/model/record/serializer.rb +56 -0
- data/lib/netfira/web_connect/model/record/translated_string.rb +42 -0
- data/lib/netfira/web_connect/model/record/translation.rb +58 -0
- data/lib/netfira/web_connect/model/record/translations.rb +63 -0
- data/lib/netfira/web_connect/model/record/tree.rb +13 -0
- data/lib/netfira/web_connect/model/record.rb +132 -0
- data/lib/netfira/web_connect/model/relation/events.rb +34 -0
- data/lib/netfira/web_connect/model/relation.rb +67 -0
- data/lib/netfira/web_connect/model/support.rb +13 -0
- data/lib/netfira/web_connect/model.rb +33 -0
- data/lib/netfira/web_connect/models/image.rb +48 -0
- data/lib/netfira/web_connect/models/order.rb +5 -0
- data/lib/netfira/web_connect/models/order_line.rb +13 -0
- data/lib/netfira/web_connect/models/support/setting.rb +31 -0
- data/lib/netfira/web_connect/models/support/shop/settings.rb +42 -0
- data/lib/netfira/web_connect/models/support/shop.rb +22 -0
- data/lib/netfira/web_connect/models/support/table.rb +5 -0
- data/lib/netfira/web_connect/models.rb +73 -0
- data/lib/netfira/web_connect/rack_app/action.rb +66 -0
- data/lib/netfira/web_connect/rack_app/action_helpers/data_types.rb +22 -0
- data/lib/netfira/web_connect/rack_app/action_helpers/env_importer.rb +59 -0
- data/lib/netfira/web_connect/rack_app/action_helpers/env_methods.rb +23 -0
- data/lib/netfira/web_connect/rack_app/actions/version_1/not_supported.rb +11 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/checksums.rb +14 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/commit/records.rb +45 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/commit/relations.rb +48 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/commit.rb +33 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/files.rb +21 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/info.rb +18 -0
- data/lib/netfira/web_connect/rack_app/actions/version_8/settings.rb +79 -0
- data/lib/netfira/web_connect/rack_app/exceptions/http_exception.rb +44 -0
- data/lib/netfira/web_connect/rack_app/exceptions.rb +6 -0
- data/lib/netfira/web_connect/rack_app.rb +56 -0
- data/lib/netfira/web_connect/request_filter.rb +66 -0
- data/lib/netfira/web_connect/schema/table.rb +58 -0
- data/lib/netfira/web_connect/schema.rb +22 -0
- data/lib/netfira/web_connect/version.rb +5 -0
- data/lib/netfira/web_connect.rb +82 -0
- data/lib/netfira.rb +10 -0
- data/lib/web_connect.rb +1 -0
- metadata +246 -0
@@ -0,0 +1,51 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Model::Record
|
3
|
+
|
4
|
+
# These methods help relate and unrelate records
|
5
|
+
module Relations
|
6
|
+
|
7
|
+
def relate(record)
|
8
|
+
error_messages = {
|
9
|
+
no_relation: 'You cannot add a %s to a %s',
|
10
|
+
wrong_shop: 'Tried to add a record from shop %s to shop %s'
|
11
|
+
}
|
12
|
+
prepare_relation(record, error_messages) do |relation_class, attrs|
|
13
|
+
relation_class.find_or_create_by attrs
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :<<, :relate
|
18
|
+
|
19
|
+
def unrelate(record)
|
20
|
+
error_messages = {
|
21
|
+
no_relation: 'You cannot remove a %s from a %s',
|
22
|
+
wrong_shop: 'Tried to remove a record of shop %s from shop %s'
|
23
|
+
}
|
24
|
+
prepare_relation(record, error_messages) do |relation_class, attrs|
|
25
|
+
relation_class.where(attrs).destroy_all
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# This method assists #relate and #unrelate
|
32
|
+
def prepare_relation(record, error_messages)
|
33
|
+
|
34
|
+
# The class of the relation between self and record
|
35
|
+
relation_class = Model::Record::Relation.for(self, record)
|
36
|
+
|
37
|
+
# Error for unrelatable classes
|
38
|
+
raise error_messages[:no_relation] % [record.class.name.demodulize, self.class.name.demodulize] unless relation_class
|
39
|
+
|
40
|
+
# Error for shop mismatches
|
41
|
+
raise error_messages[:wrong_shop] % [record.shop_id, shop_id] unless shop_id == record.shop_id
|
42
|
+
|
43
|
+
# The attributes to use for create or delete, e.g. {product_id: 1, category_id: 2}
|
44
|
+
attrs = [self, record].map{ |r| ["#{r.class.name.demodulize.underscore}_id", r.id]}.to_h
|
45
|
+
|
46
|
+
yield relation_class, attrs
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Model::Record
|
3
|
+
|
4
|
+
module Sendable
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
after_initialize :create_guid
|
9
|
+
enum delivery_status: [:unsent, :sent, :delivered]
|
10
|
+
end
|
11
|
+
|
12
|
+
def guid
|
13
|
+
@guid ||= Guid.from(self[:guid])
|
14
|
+
end
|
15
|
+
|
16
|
+
def guid=(value)
|
17
|
+
raise 'GUIDs are read-only'
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_guid
|
23
|
+
self[:guid] ||= (@guid = Guid.create).b
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'bigdecimal'
|
2
|
+
|
3
|
+
module Netfira::WebConnect
|
4
|
+
class Model::Record
|
5
|
+
|
6
|
+
module Serializer
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
EXCLUDE_FROM_SERIALIZE = %w[id shop_id digest delivery_status created_at updated_at]
|
10
|
+
|
11
|
+
included do
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Returns an array of three arrays of keys, e.g.
|
16
|
+
# [ ['product_id', 'unit_price'], ['description'], [] ]
|
17
|
+
def self.attributes_to_serialize
|
18
|
+
@attributes_to_serialize ||= [
|
19
|
+
attribute_names - EXCLUDE_FROM_SERIALIZE,
|
20
|
+
has_languages? ? self::Translation.translated_attribute_names : [],
|
21
|
+
[] # Custom attributes (not implemented)
|
22
|
+
]
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
|
28
|
+
# Map each array of keys from the above method into a sorted hash of key/value pairs
|
29
|
+
data = self.class.attributes_to_serialize.map do |keys|
|
30
|
+
pairs = keys.sort.map do |key|
|
31
|
+
[key.camelize(:lower), cast_for_serialization(__send__ key)]
|
32
|
+
end
|
33
|
+
pairs.reject!{ |v| v[1].nil? }.to_h
|
34
|
+
end
|
35
|
+
|
36
|
+
# Return the class name and the sets above, e.g. Product{...},{...},{...}
|
37
|
+
self.class.name.demodulize << JSON.generate(data)[1..-2]
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def cast_for_serialization(value)
|
43
|
+
if TranslatedString === value
|
44
|
+
value = value.sort
|
45
|
+
value = value.empty? ? nil : value.to_h
|
46
|
+
elsif Numeric === value
|
47
|
+
value = (value == value.floor) ? value.to_i : value.to_f
|
48
|
+
elsif OctaWord === value
|
49
|
+
value = value.to_s
|
50
|
+
end
|
51
|
+
value
|
52
|
+
end
|
53
|
+
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
|
3
|
+
module Netfira::WebConnect
|
4
|
+
|
5
|
+
# This class represents a translated string on a model, e.g. product.description.
|
6
|
+
# Callbacks are supplied by the Model::Record::Translations mixin
|
7
|
+
class Model::Record::TranslatedString
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@callbacks = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def [](lang)
|
15
|
+
@callbacks[:get].call lang
|
16
|
+
end
|
17
|
+
|
18
|
+
def []=(lang, value)
|
19
|
+
@callbacks[:set].call lang, value
|
20
|
+
end
|
21
|
+
|
22
|
+
def on(action, &block)
|
23
|
+
@callbacks[action] = block
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
self[nil]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_h
|
31
|
+
@callbacks[:all].call
|
32
|
+
end
|
33
|
+
|
34
|
+
def merge!(values)
|
35
|
+
values.each{ |key, value| self[key] = value }
|
36
|
+
self
|
37
|
+
end
|
38
|
+
|
39
|
+
delegate %i[count length size each map sort select reject] => :to_h
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
|
3
|
+
# Subclasses of this model represents rows from translation tables, e.g. nf_products_l10n.
|
4
|
+
# Subclasses are materialized when their parent record models are materialized.
|
5
|
+
class Model::Record::Translation
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
def materialize(owner_class)
|
10
|
+
|
11
|
+
# Make a translation model, e.g. Product::Translation
|
12
|
+
klass = Class.new(self)
|
13
|
+
owner_class.const_set :Translation, klass
|
14
|
+
|
15
|
+
# Keep a reference to the owner class
|
16
|
+
klass.instance_variable_set :@owner_class, owner_class
|
17
|
+
|
18
|
+
# Give translations references back to their parent records
|
19
|
+
klass.belongs_to :owner,
|
20
|
+
class_name: owner_class.name,
|
21
|
+
foreign_key: "#{owner_class.name.demodulize.underscore}_id",
|
22
|
+
inverse_of: :translation_models
|
23
|
+
|
24
|
+
# Add a translations relation, e.g. product.translations
|
25
|
+
owner_class.has_many :translation_models,
|
26
|
+
class_name: klass.name,
|
27
|
+
inverse_of: :owner,
|
28
|
+
autosave: true
|
29
|
+
|
30
|
+
# Add translations to the default scope, as they will almost
|
31
|
+
# always be needed for lookups
|
32
|
+
owner_class.default_scope { includes :translation_models }
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def translated_attribute_names
|
37
|
+
@translated_attribute_names ||= attribute_names - ['id', 'language', "#{@owner_class.single_name}_id"]
|
38
|
+
end
|
39
|
+
|
40
|
+
def table_name
|
41
|
+
@table_name ||= if self == Model::Record::Translation
|
42
|
+
Models::Table.table_name
|
43
|
+
else
|
44
|
+
Netfira::WebConnect.db_table_prefix(plural_name).to_s
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def single_name
|
49
|
+
@single_name ||= Netfira::WebConnect.db_table_l10n_suffix @owner_class.single_name
|
50
|
+
end
|
51
|
+
|
52
|
+
def plural_name
|
53
|
+
@plural_name ||= Netfira::WebConnect.db_table_l10n_suffix @owner_class.plural_name
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Model::Record
|
3
|
+
|
4
|
+
# This module is mixed into records that have translations.
|
5
|
+
module Translations
|
6
|
+
|
7
|
+
# Called when getting a translated string, e.g. product.description
|
8
|
+
def get_translated_string(key)
|
9
|
+
translated_string_for key
|
10
|
+
end
|
11
|
+
|
12
|
+
# Called when assigning a translated string, e.g. product.description = 'Stuff'
|
13
|
+
def set_translated_string(key, value)
|
14
|
+
if Hash === value
|
15
|
+
replace_translations_for key, value
|
16
|
+
else
|
17
|
+
translated_string_for(key)[nil] = value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def locale
|
22
|
+
shop.locale
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def translations
|
28
|
+
@translations ||= translation_models.map{ |model| [model.language, model]}.to_h
|
29
|
+
end
|
30
|
+
|
31
|
+
def translated_strings
|
32
|
+
@translated_strings ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def translation_for_language(lang)
|
36
|
+
lang = lang.to_s
|
37
|
+
translations[lang] ||= translation_models.build(language: lang)
|
38
|
+
end
|
39
|
+
|
40
|
+
def replace_translations_for(key, value)
|
41
|
+
translations.each { |lang, model| model[key] = value[lang] }
|
42
|
+
translated_string_for(key).merge! value
|
43
|
+
end
|
44
|
+
|
45
|
+
def translated_string_for(key)
|
46
|
+
translated_strings[key] ||= begin
|
47
|
+
t = Model::Record::TranslatedString.new
|
48
|
+
t.on(:get) { |lang| translation_for_language(lang || locale).__send__ key }
|
49
|
+
t.on(:set) { |lang, value| translation_for_language(lang || locale).__send__ :"#{key}=", value }
|
50
|
+
t.on(:all) { translations.map{ |lang, model| [lang, model[key]] }.reject{ |x| x[1].nil? }.to_h }
|
51
|
+
t
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def clear_translations
|
56
|
+
@translations = nil
|
57
|
+
@translated_strings = nil
|
58
|
+
self
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
require_relative 'record/relations'
|
2
|
+
require_relative 'record/materialization'
|
3
|
+
require_relative 'record/serializer'
|
4
|
+
require_relative 'record/digests'
|
5
|
+
require_relative 'record/sendable'
|
6
|
+
require_relative 'record/events'
|
7
|
+
|
8
|
+
module Netfira::WebConnect
|
9
|
+
class Model::Record
|
10
|
+
extend Materialization
|
11
|
+
include Relations
|
12
|
+
include Serializer
|
13
|
+
include Digests
|
14
|
+
include Events
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
def table_name
|
19
|
+
@table_name ||= if self == Model::Record
|
20
|
+
Models::Table.table_name
|
21
|
+
else
|
22
|
+
Netfira::WebConnect.db_table_prefix(plural_name).to_s
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def has_languages?
|
27
|
+
const_defined? :Translation, false
|
28
|
+
end
|
29
|
+
|
30
|
+
def tree?
|
31
|
+
@is_tree ||= attribute_names.include? 'parent_id'
|
32
|
+
end
|
33
|
+
|
34
|
+
def origin_key
|
35
|
+
@origin_key ||= if sendable?
|
36
|
+
:guid
|
37
|
+
elsif writable?
|
38
|
+
nil
|
39
|
+
else
|
40
|
+
(table_props.origin_key || "#{single_name}_id").to_sym
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def writable?
|
45
|
+
@writable ||= table_props.writable
|
46
|
+
end
|
47
|
+
|
48
|
+
def sendable?
|
49
|
+
@sendable ||= table_props.sendable
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_by_origin_id(shop, id)
|
53
|
+
id = id.b if OctaWord === id
|
54
|
+
find_by origin_key => id, shop_id: shop.id
|
55
|
+
end
|
56
|
+
|
57
|
+
def find_or_initialize_by_origin_id(shop, id)
|
58
|
+
find_by_origin_id(shop, id) || new(origin_key => id, shop_id: shop.id)
|
59
|
+
end
|
60
|
+
|
61
|
+
def find_or_create_by_origin_id(shop, id)
|
62
|
+
record = find_or_initialize_by_origin_id(shop, id)
|
63
|
+
record.save unless record.persisted?
|
64
|
+
record
|
65
|
+
end
|
66
|
+
|
67
|
+
def has_file?
|
68
|
+
@has_file ||= self < FileRecord
|
69
|
+
end
|
70
|
+
|
71
|
+
def schema
|
72
|
+
@schema ||= Netfira::WebConnect.schema[self]
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
after_initialize :init
|
78
|
+
|
79
|
+
%i[has_languages? tree? origin_key writable? sendable? has_file?].each do |method|
|
80
|
+
define_method(method) { self.class.__send__ method }
|
81
|
+
end
|
82
|
+
|
83
|
+
def origin_id
|
84
|
+
__send__ origin_key
|
85
|
+
end
|
86
|
+
|
87
|
+
def as_readonly
|
88
|
+
readonly!
|
89
|
+
yield
|
90
|
+
instance_variable_set :@readonly, false
|
91
|
+
end
|
92
|
+
|
93
|
+
def reload
|
94
|
+
clear_translations if has_languages?
|
95
|
+
super
|
96
|
+
end
|
97
|
+
|
98
|
+
def write_with_type_casting(key, value)
|
99
|
+
schema = self.class.schema
|
100
|
+
if schema.localize.include? key
|
101
|
+
set_translated_string key, value
|
102
|
+
else
|
103
|
+
type = schema.columns[key]
|
104
|
+
return false unless type
|
105
|
+
value = value.to_s if type == 'string'
|
106
|
+
write_attribute key, value
|
107
|
+
end
|
108
|
+
true
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
|
113
|
+
def self.table_props
|
114
|
+
@table_props
|
115
|
+
end
|
116
|
+
|
117
|
+
def init
|
118
|
+
self.shop_id ||= 0
|
119
|
+
end
|
120
|
+
|
121
|
+
def after_add_relation(model)
|
122
|
+
model.shop_id = shop_id
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
require_relative 'record/file_record'
|
129
|
+
require_relative 'record/translated_string'
|
130
|
+
require_relative 'record/translation'
|
131
|
+
require_relative 'record/translations'
|
132
|
+
require_relative 'record/tree'
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Model::Relation
|
3
|
+
module Events
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
after_save :dispatch_create
|
8
|
+
after_destroy :dispatch_destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
def dispatch_create
|
14
|
+
dispatch_relation_event 'add_%s_to_%s'
|
15
|
+
end
|
16
|
+
|
17
|
+
def dispatch_destroy
|
18
|
+
dispatch_relation_event 'remove_%s_from_%s'
|
19
|
+
end
|
20
|
+
|
21
|
+
def dispatch_relation_event(event_name_template)
|
22
|
+
[records, records.reverse].each do |records|
|
23
|
+
event_name = (event_name_template % records.map{ |record| record.class.single_name }).to_sym
|
24
|
+
records[0].as_readonly do
|
25
|
+
records[1].as_readonly do
|
26
|
+
Netfira::WebConnect.dispatch_event event_name, *records
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require_relative 'relation/events'
|
2
|
+
|
3
|
+
module Netfira::WebConnect
|
4
|
+
class Model::Relation
|
5
|
+
include Events
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
attr_reader :related_classes
|
10
|
+
|
11
|
+
def materialize(name_a, name_b)
|
12
|
+
klass = Class.new(self)
|
13
|
+
Models.const_set "#{name_a}To#{name_b}", klass
|
14
|
+
klass.related_classes = [name_a, name_b].map{ |n| Models.const_get n.camelize.singularize }
|
15
|
+
end
|
16
|
+
|
17
|
+
def related_classes=(classes)
|
18
|
+
raise 'Related classes have already been set' if related_classes
|
19
|
+
@related_classes = classes
|
20
|
+
classes.each_with_index do |related_class, index|
|
21
|
+
|
22
|
+
# The class on the opposite side of the relation. For example:
|
23
|
+
# related_class => Product
|
24
|
+
# opposite_class => Category
|
25
|
+
opposite_class = related_classes[1 - index]
|
26
|
+
|
27
|
+
# Name of the reference to add to the related class, e.g. Product#category_relations
|
28
|
+
relation_name = :"#{opposite_class.single_name}_relations"
|
29
|
+
|
30
|
+
# The relation class's reference, e.g. CategoryToProduct#product
|
31
|
+
belongs_to related_class.single_name.to_sym
|
32
|
+
|
33
|
+
# The related class's reference to the relation, e.g. Product#category_relations
|
34
|
+
related_class.has_many relation_name,
|
35
|
+
class_name: name,
|
36
|
+
inverse_of: related_class.single_name.to_sym
|
37
|
+
|
38
|
+
# The related class's reference to the other related class,
|
39
|
+
# e.g. Product#categories (at last!)
|
40
|
+
related_class.has_many opposite_class.plural_name.to_sym,
|
41
|
+
through: relation_name,
|
42
|
+
after_add: :after_add_relation
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def table_name
|
48
|
+
@table_name ||= if self == Model::Relation
|
49
|
+
Models::Table.table_name
|
50
|
+
else
|
51
|
+
Netfira::WebConnect.db_table_prefix(related_classes.map(&:plural_name).join '_to_').to_s
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def for(class_a, class_b)
|
56
|
+
name = [class_a, class_b].map{ |c| (c.is_a?(Class) ? c : c.class).name.demodulize }.sort.join 'To'
|
57
|
+
Models.const_get name if Models.const_defined? name
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
|
62
|
+
def records
|
63
|
+
self.class.related_classes.map{ |klass| __send__ klass.single_name.to_sym }
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Model::Support
|
3
|
+
|
4
|
+
def self.table_name
|
5
|
+
@table_name ||= if self == Model::Support
|
6
|
+
Models::Table.table_name
|
7
|
+
else
|
8
|
+
Netfira::WebConnect.db_table_prefix('_' << name.demodulize.underscore.pluralize).to_s
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Model < ActiveRecord::Base
|
3
|
+
class Record < self
|
4
|
+
class FileRecord < self
|
5
|
+
end
|
6
|
+
class Translation < Model
|
7
|
+
end
|
8
|
+
end
|
9
|
+
class Relation < self
|
10
|
+
end
|
11
|
+
class Support < self
|
12
|
+
end
|
13
|
+
|
14
|
+
class << self
|
15
|
+
def table_name
|
16
|
+
Models::Table.table_name
|
17
|
+
end
|
18
|
+
|
19
|
+
def single_name
|
20
|
+
@single_name ||= name.demodulize.underscore
|
21
|
+
end
|
22
|
+
|
23
|
+
def plural_name
|
24
|
+
@plural_name ||= single_name.pluralize
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
require_relative 'model/record'
|
32
|
+
require_relative 'model/relation'
|
33
|
+
require_relative 'model/support'
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'fastimage'
|
3
|
+
|
4
|
+
module Netfira::WebConnect
|
5
|
+
class Models::Image < Model::Record::FileRecord
|
6
|
+
|
7
|
+
def focus_point=(point)
|
8
|
+
self.focus_point_top = point[:top]
|
9
|
+
self.focus_point_left = point[:left]
|
10
|
+
self.focus_point
|
11
|
+
end
|
12
|
+
|
13
|
+
def focus_point
|
14
|
+
@focus_point ||= FocusPoint.new(self)
|
15
|
+
end
|
16
|
+
|
17
|
+
def data=(value)
|
18
|
+
super value
|
19
|
+
self.width, self.height = FastImage.size(attachment.staged_path)
|
20
|
+
end
|
21
|
+
|
22
|
+
def dimensions
|
23
|
+
[width, height]
|
24
|
+
end
|
25
|
+
|
26
|
+
def dimensions=(value)
|
27
|
+
self.width, self.height = value
|
28
|
+
end
|
29
|
+
|
30
|
+
class FocusPoint
|
31
|
+
|
32
|
+
def initialize(image)
|
33
|
+
@image = image
|
34
|
+
end
|
35
|
+
|
36
|
+
%w[top left].each do |attr|
|
37
|
+
define_method :"#{attr}" do
|
38
|
+
@image[:"focus_point_#{attr}"]
|
39
|
+
end
|
40
|
+
define_method :"#{attr}=" do |value|
|
41
|
+
@image[:"focus_point_#{attr}"] = value
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Netfira::WebConnect
|
2
|
+
class Models::Setting < Model::Support
|
3
|
+
|
4
|
+
belongs_to :shop, inverse_of: :setting_models
|
5
|
+
|
6
|
+
def typed_value
|
7
|
+
case content_type
|
8
|
+
when 'application/json' then JSON.parse(value, quirks_mode: true)
|
9
|
+
when 'application/octet-stream' then value.b
|
10
|
+
when /^text\/plain;charset=(.+)$/ then value.force_encoding($1)
|
11
|
+
else value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def typed_value=(value)
|
16
|
+
if String === value
|
17
|
+
if value.encoding.name == 'ASCII-8BIT'
|
18
|
+
self.value = value.b
|
19
|
+
self.content_type = 'application/octet-stream'
|
20
|
+
else
|
21
|
+
self.value = value.force_encoding('UTF-8')
|
22
|
+
self.content_type = 'text/plain;charset=UTF-8'
|
23
|
+
end
|
24
|
+
else
|
25
|
+
self.value = JSON.generate(value, quirks_mode: true)
|
26
|
+
self.content_type = 'application/json'
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|