bitrix_on_rails 0.1.8 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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