bitrix_on_rails 0.1.8 → 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.
- data/Gemfile +4 -1
- data/Gemfile.lock +9 -4
- data/Guardfile +1 -0
- data/README.rdoc +8 -2
- data/VERSION +1 -1
- data/app/models/iblock.rb +1 -42
- data/app/models/iblock_element.rb +21 -45
- data/bitrix_on_rails.gemspec +19 -13
- data/lib/bitrix_on_rails.rb +12 -6
- data/lib/bitrix_on_rails/active_record.rb +19 -11
- data/lib/bitrix_on_rails/configuration.rb +26 -0
- data/lib/bitrix_on_rails/engine.rb +0 -3
- data/lib/bitrix_on_rails/iblock_element.rb +112 -0
- data/lib/bitrix_on_rails/iblock_element_prop_m.rb +1 -27
- data/lib/bitrix_on_rails/iblock_element_prop_s.rb +111 -102
- data/test/factories.rb +105 -0
- data/test/helper.rb +23 -30
- data/test/schema.rb +5 -1158
- data/test/test_active_record.rb +36 -0
- data/test/test_bitrix_on_rails.rb +14 -9
- data/test/test_iblock_element.rb +77 -0
- data/test/test_iblock_element_prop_s.rb +111 -0
- metadata +45 -41
@@ -0,0 +1,112 @@
|
|
1
|
+
module BitrixOnRails
|
2
|
+
|
3
|
+
# Хранит список созданных классов в виде хеша: iblock_id -> class_name
|
4
|
+
@@infoblocks = {}
|
5
|
+
|
6
|
+
def self.infoblocks
|
7
|
+
@@infoblocks
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.define_iblock_class(iblock_id, options = {})
|
11
|
+
iblock_element_class = create_iblock_class(iblock_id, options[:extended_class], options[:extended_by])
|
12
|
+
|
13
|
+
unless options[:extended_class]
|
14
|
+
# Определяем имя класса, который нужно создать, а также namespace, в котором
|
15
|
+
# его нужно создать.
|
16
|
+
if class_name = options[:class_name]
|
17
|
+
a = class_name.split('::')
|
18
|
+
|
19
|
+
class_name = a.last
|
20
|
+
|
21
|
+
namespace = a[0..-2].join('::')
|
22
|
+
namespace = namespace.empty? ? Object : Object.const_get(namespace)
|
23
|
+
else
|
24
|
+
class_name = "IblockElement#{iblock_id}"
|
25
|
+
namespace = Object
|
26
|
+
end
|
27
|
+
|
28
|
+
namespace.const_set(class_name, iblock_element_class)
|
29
|
+
end
|
30
|
+
|
31
|
+
create_prop_classes(iblock_id, iblock_element_class)
|
32
|
+
end
|
33
|
+
|
34
|
+
protected
|
35
|
+
|
36
|
+
def self.create_iblock_class(iblock_id, extended_class = nil, extended_by = nil)
|
37
|
+
iblock_element_class = extended_class || Class.new(::IblockElement) {}
|
38
|
+
|
39
|
+
iblock_element_class.instance_eval do
|
40
|
+
@iblock_id = iblock_id
|
41
|
+
@iblock_properties = Iblock.get_properties(iblock_id).inject({}){ |a,e| a[e[1].code] = {:id => e[1].id, :multiple => e[1].multiple == 'Y'}; a}
|
42
|
+
|
43
|
+
has_one :property_set, :class_name => "IblockElementPropS#{iblock_id}", :foreign_key => 'iblock_element_id', :autosave => true
|
44
|
+
has_many :m_props, :class_name => "IblockElementPropM#{iblock_id}", :foreign_key => 'iblock_element_id', :readonly => true
|
45
|
+
|
46
|
+
default_scope where(:iblock_id => iblock_id)
|
47
|
+
|
48
|
+
@iblock_properties.each { |m, property|
|
49
|
+
delegate m, :to => :property_set
|
50
|
+
delegate "#{m}=", :to => :property_set unless property[:multiple]
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
iblock_element_class.extend Object.const_get(extended_by) if extended_by
|
55
|
+
|
56
|
+
@@infoblocks[iblock_id] = iblock_element_class
|
57
|
+
|
58
|
+
iblock_element_class
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.create_prop_classes(iblock_id, iblock_element_class)
|
62
|
+
const_name = "IblockElementPropS#{iblock_id}"
|
63
|
+
unless Object.const_defined? const_name
|
64
|
+
c = Class.new(::ActiveRecord::Base) do
|
65
|
+
extend BitrixOnRails::IblockElementPropS
|
66
|
+
|
67
|
+
class << self
|
68
|
+
# Имя класса, хранящего значения для множественных свойств
|
69
|
+
@m_prop_class = nil
|
70
|
+
|
71
|
+
# Список множественных свойств
|
72
|
+
@m_props = nil
|
73
|
+
|
74
|
+
# Список одиночных свойств
|
75
|
+
@s_props = nil
|
76
|
+
|
77
|
+
def m_prop_class
|
78
|
+
Object.const_get(@m_prop_class)
|
79
|
+
end
|
80
|
+
|
81
|
+
def m_props
|
82
|
+
@m_props
|
83
|
+
end
|
84
|
+
|
85
|
+
def s_props
|
86
|
+
@s_props
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
acts_as_iblock_element_prop_s(iblock_id, iblock_element_class)
|
91
|
+
end
|
92
|
+
Object.const_set const_name, c
|
93
|
+
end
|
94
|
+
|
95
|
+
const_name = "IblockElementPropM#{iblock_id}"
|
96
|
+
unless Object.const_defined? const_name
|
97
|
+
c = Class.new(::ActiveRecord::Base) do
|
98
|
+
extend BitrixOnRails::IblockElementPropM
|
99
|
+
acts_as_iblock_element_prop_m(iblock_id)
|
100
|
+
end
|
101
|
+
Object.const_set "IblockElementPropM#{iblock_id}", c
|
102
|
+
end
|
103
|
+
|
104
|
+
# Вставляем связи с i_block_element_prop_* на уровень IblockElement. Это может быть полезно
|
105
|
+
# в том случае, если пользователь получил объект класса IblockElement, а не создаваемого.
|
106
|
+
# Использование этих связей полностью в компетенции пользователя объекта.
|
107
|
+
IblockElement.instance_eval do
|
108
|
+
has_one "iblock_element_prop_s#{iblock_id}".to_sym
|
109
|
+
has_many "iblock_element_prop_m#{iblock_id}".to_sym
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -1,56 +1,30 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
|
-
# -*- coding: utf-8 -*-
|
3
2
|
module BitrixOnRails::IblockElementPropM
|
4
3
|
def acts_as_iblock_element_prop_m(id)
|
5
4
|
extend ClassMethods
|
6
5
|
include InstanceMethods
|
7
6
|
|
8
|
-
# Убираем лишнюю s вконце
|
9
|
-
#set_table_name 'b_'+table_name.chop
|
10
7
|
set_table_name "b_iblock_element_prop_m#{id}"
|
11
8
|
|
12
|
-
# delegate :code, :to=>:property
|
13
|
-
|
14
9
|
belongs_to :iblock_element
|
15
10
|
belongs_to :iblock_property
|
16
11
|
end
|
17
12
|
|
18
13
|
module ClassMethods
|
19
|
-
def init
|
20
|
-
self.to_s=~/(\d+)/
|
21
|
-
iblock_id = $1.to_i
|
22
|
-
IblockElement.send :has_many, "iblock_element_prop_m#{iblock_id}".to_sym, :class_name=>"::IblockElementPropM#{iblock_id}"
|
23
|
-
end
|
24
14
|
end
|
25
15
|
|
26
16
|
module InstanceMethods
|
27
|
-
|
28
17
|
def property
|
29
18
|
# Достаем кешированный вариант
|
30
19
|
IblockProperty.find(iblock_property_id)
|
31
20
|
end
|
32
21
|
|
33
22
|
def code
|
34
|
-
@code||=property.code
|
35
|
-
end
|
36
|
-
|
37
|
-
def get_value
|
38
|
-
case property.property_type
|
39
|
-
when 'S' # String
|
40
|
-
self.value
|
41
|
-
when 'N' # Numeric
|
42
|
-
self.value_num.to_i # Когда BigDecimal - не приятно
|
43
|
-
# when 'E' # Enum?
|
44
|
-
# self.value_enum
|
45
|
-
# when 'L' # WTF?
|
46
|
-
else
|
47
|
-
raise "Не установленный тип (#{iblock_property.property_type}) свойства (#{self.class} #{id})"
|
48
|
-
end
|
23
|
+
@code ||= property.code
|
49
24
|
end
|
50
25
|
end
|
51
26
|
end
|
52
27
|
|
53
|
-
|
54
28
|
# Table b_iblock_element_prop_m7
|
55
29
|
# ==============================
|
56
30
|
# ID, IBLOCK_ELEMENT_ID, IBLOCK_PROPERTY_ID, VALUE, VALUE_ENUM, VALUE_NUM, DESCRIPTION
|
@@ -1,132 +1,141 @@
|
|
1
1
|
# -*- coding: utf-8 -*-
|
2
2
|
module BitrixOnRails::IblockElementPropS
|
3
|
-
def acts_as_iblock_element_prop_s(id)
|
4
|
-
extend ClassMethods
|
5
|
-
include InstanceMethods
|
6
|
-
|
7
|
-
# Убираем лишнюю s вконце
|
8
|
-
#set_table_name 'b_'+table_name.chop
|
9
|
-
set_table_name "b_iblock_element_prop_s#{id}"
|
10
3
|
|
11
|
-
|
4
|
+
class MPropValuesWrapper
|
5
|
+
attr_reader :iblock_element_id, :iblock_property_id
|
12
6
|
|
13
|
-
|
14
|
-
|
7
|
+
def initialize(iblock_element_id, iblock_property_id, m_prop_class)
|
8
|
+
@iblock_element_id = iblock_element_id
|
9
|
+
@iblock_property_id = iblock_property_id
|
15
10
|
|
16
|
-
|
17
|
-
|
11
|
+
@m_prop_class = m_prop_class
|
12
|
+
end
|
18
13
|
|
19
|
-
|
20
|
-
|
14
|
+
def values
|
15
|
+
@m_prop_class.where(
|
16
|
+
:iblock_element_id => @iblock_element_id,
|
17
|
+
:iblock_property_id => @iblock_property_id
|
18
|
+
).collect { |e| e.value }
|
21
19
|
end
|
22
20
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
21
|
+
def add(value)
|
22
|
+
@m_prop_class.create(
|
23
|
+
:iblock_element_id => @iblock_element_id,
|
24
|
+
:iblock_property_id => @iblock_property_id,
|
25
|
+
:value => value)
|
28
26
|
end
|
29
27
|
|
28
|
+
def remove(value)
|
29
|
+
m_props = @m_prop_class.where(
|
30
|
+
:iblock_element_id => @iblock_element_id,
|
31
|
+
:iblock_property_id => @iblock_property_id,
|
32
|
+
:value => value)
|
33
|
+
m_props.each { |p| p.destroy } if m_props.any?
|
34
|
+
end
|
30
35
|
end
|
31
36
|
|
32
|
-
def
|
33
|
-
|
34
|
-
|
37
|
+
def acts_as_iblock_element_prop_s(iblock_id, iblock_element_class)
|
38
|
+
extend ClassMethods
|
39
|
+
include InstanceMethods
|
35
40
|
|
36
|
-
|
41
|
+
@m_prop_class = "IblockElementPropM#{iblock_id}"
|
42
|
+
@m_props = {}
|
43
|
+
@s_props = {}
|
37
44
|
|
45
|
+
Iblock.get_properties(iblock_id).select { |k,e|
|
46
|
+
if e.multiple == 'Y'
|
47
|
+
@m_props[e.code] = {:id => e.id, :type => e.property_type, :user_type => e.user_type}
|
48
|
+
else
|
49
|
+
@s_props[e.code] = {:id => e.id, :type => e.property_type, :user_type => e.user_type}
|
50
|
+
end
|
51
|
+
}
|
38
52
|
|
39
|
-
|
40
|
-
# @m_prop_class = "IblockElementPropM#{id}"
|
41
|
-
# Название свойства сохраняется только для наглядности, чтобы можно было через консоль понять
|
42
|
-
# что за свойство.
|
43
|
-
@m_props ||= Iblock.get_properties(iblock_id).select { |k,e| e.multiple == 'Y' }
|
44
|
-
end
|
53
|
+
set_table_name "b_iblock_element_prop_s#{iblock_id}"
|
45
54
|
|
46
|
-
|
47
|
-
# name - русское описание
|
48
|
-
|
49
|
-
# Дальше идут пары:
|
50
|
-
# property_120
|
51
|
-
# description_120
|
52
|
-
# ..
|
53
|
-
# определяются они в iblock.iblock_properties
|
54
|
-
|
55
|
-
# Берем список свойств из таблицы iblock_properties
|
56
|
-
# и создаем такие же методы для опроса и установки, чтобы можно было использовать
|
57
|
-
# их напрямую:
|
58
|
-
#
|
59
|
-
# self.кодовое_имя_свойства
|
60
|
-
#
|
61
|
-
# Например:
|
62
|
-
#
|
63
|
-
# self.post_id вместо self.property_120
|
64
|
-
#
|
65
|
-
|
66
|
-
def iblock_id
|
67
|
-
self.name=~/(\d+)/
|
68
|
-
$1.to_i
|
69
|
-
end
|
55
|
+
belongs_to :iblock_element, :class_name => iblock_element_class.name
|
70
56
|
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
IblockElement.send :has_one, "iblock_element_prop_s#{iblock_id}".to_sym, :class_name=>Iblock.s_props_class(iblock_id).name, :autosave => true
|
76
|
-
|
77
|
-
self.properties = {}
|
78
|
-
attribute_names.select {|a| a[0,4]=='prop' }.each do |name|
|
79
|
-
prop_id = name.slice(9,5).to_i
|
80
|
-
iblock_property = IblockProperty.find(prop_id)
|
81
|
-
code = iblock_property.code.downcase.to_sym
|
82
|
-
self.properties[code] = name
|
83
|
-
define_method code do
|
84
|
-
val = send name
|
85
|
-
return val.to_i if val.is_a?(BigDecimal)
|
86
|
-
|
87
|
-
# Вот так мы проверяем сериализацию
|
88
|
-
# Есть еще вариант что поле сериализовано если iblock_property.user_type=='HTML'
|
89
|
-
#
|
90
|
-
if val.is_a? String and val.length>5 and val[0,3]=~/[a-z]:\d/
|
91
|
-
res = PHP.unserialize(val)
|
92
|
-
# res = PhpSerialization.load(val)
|
93
|
-
return iblock_property.property_type=='S' && res.is_a?(Hash) && res.include?('TEXT') ? res['TEXT'] : res
|
94
|
-
end
|
95
|
-
val
|
96
|
-
end
|
97
|
-
define_method "#{code}=" do |val|
|
98
|
-
send "#{name}=", val
|
99
|
-
end
|
100
|
-
eval "def self.find_by_#{code}(val); find_by_#{name}(val); end"
|
57
|
+
@s_props.each { |code, property|
|
58
|
+
define_method(code) do
|
59
|
+
value = send "property_#{property[:id]}"
|
60
|
+
unserialize value, property[:type], property[:user_type]
|
101
61
|
end
|
102
|
-
end
|
103
|
-
end
|
104
62
|
|
105
|
-
|
63
|
+
define_method("#{code}=") do |val|
|
64
|
+
send "property_#{property[:id]}=", serialize(val, property[:type], property[:user_type])
|
65
|
+
end
|
106
66
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
67
|
+
define_method "find_by_#{code}" do |val|
|
68
|
+
send "find_by_property#{property[:id]}", serialize(val, property[:type], property[:user_type])
|
69
|
+
end
|
70
|
+
}
|
111
71
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
72
|
+
@m_props.each { |code, property|
|
73
|
+
define_method(code) do
|
74
|
+
instance_variable_get("@m_prop_#{property[:id]}".to_sym) ||
|
75
|
+
instance_variable_set("@m_prop_#{property[:id]}".to_sym, MPropValuesWrapper.new(self.iblock_element_id, property[:id], m_prop_class))
|
76
|
+
end
|
77
|
+
}
|
78
|
+
|
79
|
+
before_save do
|
80
|
+
self.class.m_props.each { |code, p|
|
81
|
+
values = send(code).values
|
82
|
+
self.send("property_#{p[:id]}=", ::PHP.serialize({'VALUE' => values, 'DESCRIPTION' => Array.new(values.size, nil)}))
|
83
|
+
}
|
117
84
|
end
|
85
|
+
end
|
118
86
|
|
119
|
-
|
120
|
-
|
87
|
+
module ClassMethods
|
88
|
+
end
|
89
|
+
|
90
|
+
module InstanceMethods
|
91
|
+
def m_prop_class
|
92
|
+
self.class.m_prop_class
|
121
93
|
end
|
122
94
|
|
123
|
-
|
124
|
-
|
95
|
+
# Возможные значения для типов свойств:
|
96
|
+
# * S - строка
|
97
|
+
# * N - число
|
98
|
+
# * F - файл
|
99
|
+
# * L - список
|
100
|
+
# * E - привязка к элементам
|
101
|
+
# * G - привязка к группам.
|
102
|
+
def unserialize(value, type, user_type = nil)
|
103
|
+
return nil unless value
|
104
|
+
|
105
|
+
case type
|
106
|
+
when 'N'
|
107
|
+
value.is_a?(BigDecimal) ? value.to_i : value
|
108
|
+
when 'S'
|
109
|
+
if value.length > 5 && value[0..3] =~ /[a-z]:\d/
|
110
|
+
v = ::PHP.unserialize(value)
|
111
|
+
v.is_a?(Hash) && v.include?('TEXT') ? v['TEXT'] : value
|
112
|
+
elsif user_type == 'DateTime'
|
113
|
+
Time.parse(value).in_time_zone
|
114
|
+
else
|
115
|
+
value
|
116
|
+
end
|
117
|
+
when 'L' # id из таблицы b_iblock_property_enum
|
118
|
+
value
|
119
|
+
else
|
120
|
+
value
|
121
|
+
end
|
125
122
|
end
|
126
123
|
|
127
|
-
def
|
128
|
-
|
129
|
-
|
124
|
+
def serialize(value, type, user_type = nil)
|
125
|
+
case type
|
126
|
+
when 'S'
|
127
|
+
if user_type == 'HTML'
|
128
|
+
::PHP.serialize({'TEXT' => value, 'TYPE' => 'html'})
|
129
|
+
elsif user_type == 'DateTime'
|
130
|
+
value.strftime('%Y-%m-%d %H:%M:%S')
|
131
|
+
else
|
132
|
+
# Это делается потому, что пользователь может хранить в строках не только
|
133
|
+
# строковые значения.
|
134
|
+
value.to_s
|
135
|
+
end
|
136
|
+
else
|
137
|
+
value
|
138
|
+
end
|
130
139
|
end
|
131
140
|
end
|
132
141
|
end
|
data/test/factories.rb
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
FactoryGirl.define do
|
4
|
+
factory :iblock_type
|
5
|
+
|
6
|
+
factory :iblock do
|
7
|
+
version 2
|
8
|
+
timestamp_x Time.now
|
9
|
+
name 'имя инфоблока'
|
10
|
+
lid 'lid'
|
11
|
+
association :iblock_type
|
12
|
+
end
|
13
|
+
|
14
|
+
factory :iblock_property do
|
15
|
+
timestamp_x Time.now
|
16
|
+
end
|
17
|
+
|
18
|
+
factory :iblock_element do
|
19
|
+
end
|
20
|
+
|
21
|
+
#
|
22
|
+
# Инфоблок 3
|
23
|
+
#
|
24
|
+
|
25
|
+
factory :iblock3, :parent=>:iblock do
|
26
|
+
name 'Свойства поста'
|
27
|
+
|
28
|
+
after_build { |iblock|
|
29
|
+
iblock.iblock_properties.build Factory.build(:iblock3_s_prop_post_id).attributes
|
30
|
+
iblock.iblock_properties.build Factory.build(:iblock3_s_prop_preview_mpage).attributes
|
31
|
+
iblock.iblock_properties.build Factory.build(:iblock3_s_prop_publication_date).attributes
|
32
|
+
iblock.iblock_properties.build Factory.build(:iblock3_m_prop_glob_clas).attributes
|
33
|
+
}
|
34
|
+
|
35
|
+
after_create { |iblock|
|
36
|
+
connection = ActiveRecord::Base.connection
|
37
|
+
connection.create_table "b_iblock_element_prop_s#{iblock.id}".to_sym, :id => false do |t|
|
38
|
+
t.integer :iblock_element_id
|
39
|
+
iblock.iblock_properties.each { |property|
|
40
|
+
type = case property.property_type
|
41
|
+
when 'N' then :integer
|
42
|
+
when 'S' then :string
|
43
|
+
when 'L' then :integer
|
44
|
+
end
|
45
|
+
|
46
|
+
t.send type, "property_#{property.id}".to_sym
|
47
|
+
t.string "description_#{property.id}"
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
connection.create_table "b_iblock_element_prop_m#{iblock.id}".to_sym do |t|
|
52
|
+
t.integer :iblock_element_id
|
53
|
+
t.integer :iblock_property_id
|
54
|
+
t.integer :value
|
55
|
+
t.string :description
|
56
|
+
end
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
factory :iblock3_s_prop_post_id, :parent => :iblock_property do
|
61
|
+
name 'ID поста'
|
62
|
+
code 'POST_ID'
|
63
|
+
property_type 'N'
|
64
|
+
multiple 'N'
|
65
|
+
end
|
66
|
+
|
67
|
+
factory :iblock3_s_prop_preview_mpage, :parent => :iblock_property do
|
68
|
+
name 'Анонс для главной'
|
69
|
+
code 'PREVIEW_MPAGE'
|
70
|
+
property_type 'S'
|
71
|
+
multiple 'N'
|
72
|
+
user_type 'HTML'
|
73
|
+
end
|
74
|
+
|
75
|
+
factory :iblock3_s_prop_publication_date, :parent => :iblock_property do
|
76
|
+
name 'Дата публикации'
|
77
|
+
code 'PUBLICATION_DATE'
|
78
|
+
property_type 'S'
|
79
|
+
multiple 'N'
|
80
|
+
user_type 'DateTime'
|
81
|
+
end
|
82
|
+
|
83
|
+
factory :iblock3_m_prop_glob_clas, :parent => :iblock_property do
|
84
|
+
name 'Глобальный классификатор'
|
85
|
+
code 'GLOB_CLASS'
|
86
|
+
property_type 'S'
|
87
|
+
multiple 'Y'
|
88
|
+
end
|
89
|
+
|
90
|
+
factory :iblock_element3, :parent => :iblock_element do
|
91
|
+
name ''
|
92
|
+
end
|
93
|
+
|
94
|
+
#
|
95
|
+
# Инфоблок 7
|
96
|
+
#
|
97
|
+
|
98
|
+
factory :iblock7, :parent=>:iblock do
|
99
|
+
id 7
|
100
|
+
end
|
101
|
+
|
102
|
+
factory :iblock_element7, :parent => :iblock_element do
|
103
|
+
association :iblock, :factory=>:iblock7
|
104
|
+
end
|
105
|
+
end
|