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.
@@ -1,7 +1,4 @@
1
1
  module BitrixOnRails
2
2
  class Engine < Rails::Engine
3
- initializer 'bitrix_on_rails.init' do
4
- BitrixOnRails.init
5
- end
6
3
  end
7
4
  end
@@ -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
- belongs_to :iblock_element
4
+ class MPropValuesWrapper
5
+ attr_reader :iblock_element_id, :iblock_property_id
12
6
 
13
- # Хеш соответсвия кодов свойств названию полей: post_id -> property_149
14
- cattr_accessor :properties
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
- attr_accessor :properties
11
+ @m_prop_class = m_prop_class
12
+ end
18
13
 
19
- after_find do
20
- self.properties = self.class.properties.keys.inject({}) { |hash, prop| hash[prop] = send prop; hash }
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
- before_save do
24
- self.class.m_props.each_value { |p|
25
- values = m_prop_values(p.id)
26
- self.send("property_#{p.id}=", PHP.serialize({'VALUE' => values, 'DESCRIPTION' => Array.new(values.size, nil)}))
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 create_element_association
33
- IblockElement.send :has_one, "iblock_element_prop_s#{id}".to_sym, :class_name=>"::IblockElementPropS#{id}"
34
- end
37
+ def acts_as_iblock_element_prop_s(iblock_id, iblock_element_class)
38
+ extend ClassMethods
39
+ include InstanceMethods
35
40
 
36
- module ClassMethods
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
- def m_props
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
- # code - врое как кодовое название свойства
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
- def init
72
-
73
- # Вот это не срабатывает для Post::Element
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
- module InstanceMethods
63
+ define_method("#{code}=") do |val|
64
+ send "property_#{property[:id]}=", serialize(val, property[:type], property[:user_type])
65
+ end
106
66
 
107
- def iblock_id
108
- self.class.to_s=~/(\d+)/
109
- $1.to_i
110
- end
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
- # prop - код свойства (post_id к примеру)
115
- def get_value(prop)
116
- send prop
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
- def m_prop_values(prop_id)
120
- Iblock.m_props_class(iblock_id).where(:iblock_element_id => self.id, :iblock_property_id => prop_id).collect { |e| e.value }
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
- def create_m_prop_value(prop_id, value)
124
- Iblock.m_props_class(iblock_id).create(:iblock_element_id => self.id, :iblock_property_id => prop_id, :value => value)
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 destroy_m_prop_value(prop_id, value)
128
- m_props = Iblock.m_props_class(iblock_id).where(:iblock_element_id => self.iblock_element_id, :iblock_property_id => prop_id, :value => value)
129
- m_props.each { |p| p.destroy } if m_props.any?
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