flydata 0.6.3 → 0.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. checksums.yaml +4 -4
  2. data/Rakefile +2 -2
  3. data/VERSION +1 -1
  4. data/bin/fdredshift +78 -0
  5. data/circle.yml +1 -1
  6. data/ext/flydata/{parser/mysql → source_mysql/parser}/.gitignore +0 -0
  7. data/ext/flydata/{parser/mysql → source_mysql/parser}/dump_parser_ext.cpp +3 -3
  8. data/ext/flydata/source_mysql/parser/extconf.rb +3 -0
  9. data/ext/flydata/{parser/mysql → source_mysql/parser}/parser.txt +0 -0
  10. data/ext/flydata/{parser/mysql → source_mysql/parser}/sql_parser.cpp +0 -0
  11. data/ext/flydata/{parser/mysql → source_mysql/parser}/sql_parser.h +0 -0
  12. data/flydata-core/lib/flydata-core/mysql/binlog_pos.rb +34 -32
  13. data/flydata-core/lib/flydata-core/mysql/compatibility_checker.rb +20 -0
  14. data/flydata-core/lib/flydata-core/table_def/mysql_table_def.rb +12 -4
  15. data/flydata-core/lib/flydata-core/table_def/redshift_table_def.rb +60 -6
  16. data/flydata-core/spec/mysql/binlog_pos_spec.rb +474 -0
  17. data/flydata-core/spec/table_def/mysql_table_def_spec.rb +57 -0
  18. data/flydata-core/spec/table_def/mysql_to_redshift_table_def_spec.rb +174 -20
  19. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_AUTO_INCREMENT_keyword.dump +43 -0
  20. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_not_null_keyword.dump +43 -0
  21. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_unique_keyword.dump +43 -0
  22. data/flydata-core/spec/table_def/mysqldump_test_col_comment_with_unsigned_keyword.dump +43 -0
  23. data/flydata-core/spec/table_def/redshift_table_def_spec.rb +41 -8
  24. data/flydata.gemspec +0 -0
  25. data/lib/flydata/cli.rb +11 -5
  26. data/lib/flydata/command/base.rb +14 -1
  27. data/lib/flydata/command/exclusive_runnable.rb +42 -12
  28. data/lib/flydata/command/helper.rb +6 -6
  29. data/lib/flydata/command/sender.rb +4 -3
  30. data/lib/flydata/command/setup.rb +30 -381
  31. data/lib/flydata/command/stop.rb +1 -0
  32. data/lib/flydata/command/sync.rb +273 -301
  33. data/lib/flydata/compatibility_check.rb +24 -117
  34. data/lib/flydata/fluent-plugins/in_mysql_binlog_flydata.rb +3 -3
  35. data/lib/flydata/fluent-plugins/mysql/alter_table_query_handler.rb +2 -2
  36. data/lib/flydata/fluent-plugins/mysql/binlog_record_handler.rb +6 -6
  37. data/lib/flydata/fluent-plugins/mysql/truncate_table_query_handler.rb +0 -1
  38. data/lib/flydata/parser.rb +14 -0
  39. data/lib/flydata/{parser_provider.rb → parser/parser_provider.rb} +6 -4
  40. data/lib/flydata/parser/source_table.rb +33 -0
  41. data/lib/flydata/source.rb +105 -0
  42. data/lib/flydata/source/component.rb +21 -0
  43. data/lib/flydata/source/errors.rb +7 -0
  44. data/lib/flydata/source/generate_source_dump.rb +72 -0
  45. data/lib/flydata/source/parse_dump_and_send.rb +52 -0
  46. data/lib/flydata/source/setup.rb +31 -0
  47. data/lib/flydata/source/source_pos.rb +45 -0
  48. data/lib/flydata/source/sync.rb +56 -0
  49. data/lib/flydata/source/sync_generate_table_ddl.rb +43 -0
  50. data/lib/flydata/source_file/setup.rb +17 -0
  51. data/lib/flydata/source_file/sync.rb +14 -0
  52. data/lib/flydata/{command → source_mysql/command}/mysql.rb +2 -1
  53. data/lib/flydata/{command → source_mysql/command}/mysql_command_base.rb +2 -4
  54. data/lib/flydata/{command → source_mysql/command}/mysqlbinlog.rb +2 -1
  55. data/lib/flydata/{command → source_mysql/command}/mysqldump.rb +2 -1
  56. data/lib/flydata/source_mysql/generate_source_dump.rb +53 -0
  57. data/lib/flydata/source_mysql/mysql_compatibility_check.rb +114 -0
  58. data/lib/flydata/source_mysql/parse_dump_and_send.rb +28 -0
  59. data/lib/flydata/{parser/mysql → source_mysql/parser}/.gitignore +0 -0
  60. data/lib/flydata/{parser/mysql → source_mysql/parser}/dump_parser.rb +32 -67
  61. data/lib/flydata/{parser/mysql → source_mysql/parser}/mysql_alter_table.treetop +0 -0
  62. data/lib/flydata/source_mysql/setup.rb +24 -0
  63. data/lib/flydata/source_mysql/source_pos.rb +21 -0
  64. data/lib/flydata/source_mysql/sync.rb +45 -0
  65. data/lib/flydata/source_mysql/sync_generate_table_ddl.rb +40 -0
  66. data/lib/flydata/{mysql → source_mysql}/table_ddl.rb +6 -17
  67. data/lib/flydata/source_zendesk/sync_generate_table_ddl.rb +30 -0
  68. data/lib/flydata/source_zendesk/zendesk_flydata_tabledefs.rb +133 -0
  69. data/lib/flydata/sync_file_manager.rb +132 -73
  70. data/lib/flydata/table_ddl.rb +18 -0
  71. data/spec/flydata/cli_spec.rb +1 -0
  72. data/spec/flydata/command/exclusive_runnable_spec.rb +19 -8
  73. data/spec/flydata/command/sender_spec.rb +1 -1
  74. data/spec/flydata/command/setup_spec.rb +4 -4
  75. data/spec/flydata/command/sync_spec.rb +97 -134
  76. data/spec/flydata/compatibility_check_spec.rb +16 -289
  77. data/spec/flydata/fluent-plugins/mysql/alter_table_query_handler_spec.rb +3 -3
  78. data/spec/flydata/fluent-plugins/mysql/dml_record_handler_spec.rb +1 -1
  79. data/spec/flydata/fluent-plugins/mysql/shared_query_handler_context.rb +4 -2
  80. data/spec/flydata/fluent-plugins/mysql/truncate_query_handler_spec.rb +1 -1
  81. data/spec/flydata/source_mysql/generate_source_dump_spec.rb +69 -0
  82. data/spec/flydata/source_mysql/mysql_compatibility_check_spec.rb +280 -0
  83. data/spec/flydata/{parser/mysql → source_mysql/parser}/alter_table_parser_spec.rb +2 -2
  84. data/spec/flydata/{parser/mysql → source_mysql/parser}/dump_parser_spec.rb +75 -70
  85. data/spec/flydata/source_mysql/sync_generate_table_ddl_spec.rb +137 -0
  86. data/spec/flydata/{mysql → source_mysql}/table_ddl_spec.rb +2 -2
  87. data/spec/flydata/source_spec.rb +140 -0
  88. data/spec/flydata/source_zendesk/sync_generate_table_ddl_spec.rb +33 -0
  89. data/spec/flydata/sync_file_manager_spec.rb +157 -77
  90. data/tmpl/redshift_mysql_data_entry.conf.tmpl +1 -1
  91. metadata +56 -23
  92. data/ext/flydata/parser/mysql/extconf.rb +0 -3
  93. data/lib/flydata/mysql/binlog_position.rb +0 -22
  94. data/spec/flydata/mysql/binlog_position_spec.rb +0 -35
@@ -1,8 +1,4 @@
1
- require 'mysql2'
2
1
  require 'flydata/command_loggable'
3
- require 'flydata-core/mysql/config'
4
- require 'flydata-core/mysql/command_generator'
5
- require 'flydata-core/mysql/compatibility_checker'
6
2
  require 'flydata-core/errors'
7
3
 
8
4
  module Flydata
@@ -39,6 +35,30 @@ module Flydata
39
35
 
40
36
  end
41
37
 
38
+ # Collection of checks for initial sync
39
+ module InitialSyncChecks
40
+ # Checks permission of @dump_dir and @backup_dir
41
+ #
42
+ # Requires
43
+ # @dump_dir
44
+ # @backup_dir
45
+ def check_writing_permissions
46
+ write_errors = []
47
+ paths_to_check = [FLYDATA_HOME]
48
+ paths_to_check << @dump_dir unless @dump_dir.to_s.empty?
49
+ paths_to_check << @backup_dir unless @backup_dir.to_s.empty?
50
+ paths_to_check.each do |path|
51
+ full_path = File.expand_path(path)
52
+ full_path = File.dirname(full_path) unless File.directory?(full_path)
53
+ write_errors << full_path unless File.writable?(full_path) and File.executable?(full_path)
54
+ end
55
+ unless write_errors.empty?
56
+ error_dir = write_errors.join(", ")
57
+ raise FlydataCore::CompatibilityError, "We cannot access the directories: #{error_dir}"
58
+ end
59
+ end
60
+ end
61
+
42
62
  class AgentCompatibilityCheck < CompatibilityCheck
43
63
 
44
64
  TCP_PORT=45326
@@ -69,117 +89,4 @@ module Flydata
69
89
  end
70
90
  end
71
91
 
72
- class MysqlCompatibilityCheck < CompatibilityCheck
73
-
74
- def initialize(dp_hash, de_hash, options={})
75
- super
76
- @db_opts = FlydataCore::Mysql::Config.build_mysql_db_opts(de_hash)
77
- @dump_dir = options[:dump_dir] || nil
78
- @backup_dir = options[:backup_dir] || nil
79
- @tables = de_hash['tables']
80
- end
81
-
82
- def print_errors
83
- return if @errors.empty?
84
- log_error_stderr "There may be some compatibility issues with your MySQL credentials: "
85
- @errors.each do |error|
86
- log_error_stderr " * #{error.message}"
87
- end
88
- raise "Please correct these errors if you wish to run FlyData Sync"
89
- end
90
-
91
- def check_mysql_user_compat
92
- FlydataCore::Mysql::SyncPermissionChecker.new(@db_opts).do_check
93
- end
94
-
95
- def check_mysql_protocol_tcp_compat
96
- query = FlydataCore::Mysql::CommandGenerator.generate_mysql_show_grants_cmd(@db_opts)
97
-
98
- Open3.popen3(query) do |stdin, stdout, stderr|
99
- stdin.close
100
- while !stderr.eof?
101
- lines = []
102
- while line = stderr.gets; lines << line.strip; end
103
- err_reason = lines.join(" ")
104
- log_error("Error occured during access to mysql server.", {err: err_reason})
105
-
106
- unless /Warning: Using a password on the command line interface can be insecure/ === err_reason
107
- raise FlydataCore::MysqlCompatibilityError, "Cannot connect to MySQL database. Please make sure you can connect with this command:\n $ mysql -u #{@db_opts[:username]} -h #{@db_opts[:host]} -P #{@db_opts[:port]} #{@db_opts[:database]} --protocol=tcp -p"
108
- end
109
- end
110
- end
111
- end
112
-
113
- def check_mysql_parameters_compat
114
- begin
115
- FlydataCore::Mysql::OptionalBinlogParameterChecker.new(@db_opts).do_check
116
- rescue FlydataCore::MysqlCompatibilityError => e
117
- log_warn_stderr(e.to_s)
118
- end
119
- FlydataCore::Mysql::RequiredBinlogParameterChecker.new(@db_opts).do_check
120
- end
121
-
122
- def check_rds_master_status
123
- if is_rds?
124
- FlydataCore::Mysql::RdsMasterStatusChecker.new(@db_opts).do_check
125
- end
126
- end
127
-
128
- def check_mysql_binlog_retention
129
- if is_rds?
130
- run_rds_retention_check
131
- else
132
- run_mysql_retention_check
133
- end
134
- end
135
-
136
- def check_writing_permissions
137
- write_errors = []
138
- paths_to_check = [FLYDATA_HOME]
139
- paths_to_check << @dump_dir unless @dump_dir.to_s.empty?
140
- paths_to_check << @backup_dir unless @backup_dir.to_s.empty?
141
- paths_to_check.each do |path|
142
- full_path = File.expand_path(path)
143
- full_path = File.dirname(full_path) unless File.directory?(full_path)
144
- write_errors << full_path unless File.writable?(full_path) and File.executable?(full_path)
145
- end
146
- unless write_errors.empty?
147
- error_dir = write_errors.join(", ")
148
- raise FlydataCore::MysqlCompatibilityError, "We cannot access the directories: #{error_dir}"
149
- end
150
- end
151
-
152
- # If table_type='VIEW' or engine='MEMORY', raise error.
153
- def check_mysql_table_types
154
- return if @tables.empty?
155
- option = @db_opts.dup.merge(tables: @tables)
156
- FlydataCore::Mysql::TableTypeChecker.new(option).do_check
157
- end
158
-
159
- def run_mysql_retention_check
160
- FlydataCore::Mysql::NonRdsRetentionChecker.new(@db_opts).do_check
161
- end
162
-
163
- def run_rds_retention_check
164
- FlydataCore::Mysql::RdsRetentionChecker.new(@db_opts).do_check
165
- rescue Mysql2::Error => e
166
- if e.message =~ /command denied to user/
167
- retention_hours = FlydataCore::Mysql::RdsRetentionChecker::BINLOG_RETENTION_HOURS
168
- log_warn_stderr("[WARNING]Cannot verify RDS retention period on current MySQL user account.\n" +
169
- "To see retention period, please run this on your RDS:\n" +
170
- " $> call mysql.rds_show_configuration;\n" +
171
- "Please verify that the hours is not nil and is at least #{retention_hours} hours\n" +
172
- "To set binlog retention hours, you can run this on your RDS:\n" +
173
- " $> call mysql.rds_set_configuration('binlog retention hours', #{retention_hours});\n"
174
- )
175
- else
176
- raise e
177
- end
178
- end
179
-
180
- def is_rds?(hostname = @db_opts[:host])
181
- hostname.match(/rds.amazonaws.com$/) != nil
182
- end
183
- end
184
-
185
92
  end
@@ -16,7 +16,7 @@ require 'flydata/fluent-plugins/mysql/binlog_record_dispatcher'
16
16
  require 'flydata/fluent-plugins/mysql/context'
17
17
  require 'flydata/fluent-plugins/idle_event_detector'
18
18
  require 'flydata/fluent-plugins/mysql/table_meta'
19
- require 'flydata/mysql/table_ddl'
19
+ require 'flydata/source_mysql/table_ddl'
20
20
  require 'flydata-core/fluent/config_helper'
21
21
  require 'flydata-core/mysql/ssl'
22
22
 
@@ -83,7 +83,7 @@ class MysqlBinlogFlydataInput < MysqlBinlogInput
83
83
  end
84
84
 
85
85
  @sync_fm = Flydata::SyncFileManager.new(nil) # Passing nil for data_entry as this class does not use methods which require data_entry
86
- @sent_position_file_path = @sync_fm.sent_binlog_path(@position_file)
86
+ @sent_position_file_path = @sync_fm.sent_source_pos_path(@position_file)
87
87
 
88
88
  load_custom_conf
89
89
 
@@ -184,7 +184,7 @@ EOS
184
184
  return if instance_variable_defined? :@abort
185
185
 
186
186
  @context.table_meta.update
187
- Flydata::Mysql::TableDdl.migrate_tables(@context.tables, @db_opts,
187
+ Flydata::SourceMysql::TableDdl.migrate_tables(@context.tables, @db_opts,
188
188
  @context.sync_fm, @position_file,
189
189
  @context) do |event|
190
190
  @record_dispatcher.dispatch(event)
@@ -1,4 +1,4 @@
1
- require 'flydata/parser_provider'
1
+ require 'flydata/parser/parser_provider'
2
2
  require 'flydata/fluent-plugins/mysql/ddl_query_handler'
3
3
 
4
4
  module Mysql
@@ -17,7 +17,7 @@ module Mysql
17
17
  emit_record(:alter_table, record) do |opt|
18
18
  ret = nil
19
19
  begin
20
- result = ParserProvider.parser(:mysql, :mysql_alter_table).new.parse(record["normalized_query"])
20
+ result = Flydata::Parser::ParserProvider.parser(:mysql, :mysql_alter_table).new.parse(record["normalized_query"])
21
21
  if result.nil?
22
22
  $log.error("Received unsupported alter table query. normalized query:'#{record['normalized_query']}', raw query: '#{record['query']}'")
23
23
  else
@@ -1,7 +1,7 @@
1
1
  require 'fluent/plugin/in_mysql_binlog'
2
2
  require 'binlog'
3
- require 'flydata/mysql/binlog_position'
4
3
  require 'flydata-core/record/record'
4
+ require 'flydata-core/mysql/binlog_pos'
5
5
 
6
6
  module Mysql
7
7
  class BinlogRecordHandler
@@ -21,9 +21,9 @@ module Mysql
21
21
  # Load per-table binlog position
22
22
  @table_binlog_pos = {}
23
23
  @context.tables.each do |table_name|
24
- table_binlog_content = @context.sync_fm.get_table_binlog_pos(table_name)
25
- if table_binlog_content
26
- @table_binlog_pos[table_name] = Flydata::Mysql::BinLogPosition.new(table_binlog_content)
24
+ table_binlog_pos_str = @context.sync_fm.get_table_source_raw_pos(table_name)
25
+ if table_binlog_pos_str
26
+ @table_binlog_pos[table_name] = FlydataCore::Mysql::BinlogPos.new(table_binlog_pos_str)
27
27
  end
28
28
  end
29
29
  end
@@ -45,10 +45,10 @@ module Mysql
45
45
  acceptable = @context.tables.include?(table)
46
46
 
47
47
  if acceptable and @table_binlog_pos[record['table_name']]
48
- if @table_binlog_pos[record['table_name']] >= Flydata::Mysql::BinLogPosition.new(binlog_pos(record))
48
+ if @table_binlog_pos[record['table_name']] >= FlydataCore::Mysql::BinlogPos.new(binlog_pos(record))
49
49
  acceptable = false
50
50
  else
51
- @context.sync_fm.delete_table_binlog_pos(record['table_name'])
51
+ @context.sync_fm.delete_table_source_pos(record['table_name'])
52
52
  @table_binlog_pos.delete(record['table_name'])
53
53
  end
54
54
  end
@@ -1,4 +1,3 @@
1
- require 'flydata/parser_provider'
2
1
  require 'flydata/fluent-plugins/mysql/ddl_query_handler'
3
2
 
4
3
  module Mysql
@@ -0,0 +1,14 @@
1
+ module Flydata
2
+
3
+ module Parser
4
+ module State
5
+ START = 'START'
6
+ CREATE_TABLE = 'CREATE_TABLE'
7
+ CREATE_TABLE_COLUMNS = 'CREATE_TABLE_COLUMNS'
8
+ CREATE_TABLE_CONSTRAINTS = 'CREATE_TABLE_CONSTRAINTS'
9
+ INSERT_RECORD = 'INSERT_RECORD'
10
+ PARSING_INSERT_RECORD = 'PARSING_INSERT_RECORD'
11
+ end
12
+ end
13
+
14
+ end
@@ -2,7 +2,8 @@ require 'thread'
2
2
  require 'treetop'
3
3
  require 'flydata-core/table_def'
4
4
 
5
- module Mysql
5
+ module Flydata
6
+ module Parser
6
7
  class ParserProvider
7
8
 
8
9
  @@mutex = Mutex.new
@@ -15,9 +16,9 @@ module Mysql
15
16
  return @@parsers[key]
16
17
  else
17
18
  # lazy load
18
- treetop_path = File.join(
19
- File.expand_path(File.dirname(__FILE__)),
20
- "parser/#{db_type}/#{parser_type}.treetop")
19
+ treetop_path = File.expand_path(
20
+ "../source_#{db_type}/parser/#{parser_type}.treetop",
21
+ File.dirname(__FILE__))
21
22
  Treetop.load treetop_path
22
23
  @@parsers[key] = "#{parser_type}_parser".camelize.constantize
23
24
  end
@@ -25,3 +26,4 @@ module Mysql
25
26
  end
26
27
  end
27
28
  end
29
+ end
@@ -0,0 +1,33 @@
1
+ module Flydata
2
+ module Parser
3
+
4
+ class SourceTable
5
+ def initialize(table_name, columns = {}, primary_keys = [])
6
+ @table_name = table_name
7
+ @columns = columns
8
+ @column_names = columns.collect{|k,v| v[:column_name]}
9
+ @primary_keys = primary_keys
10
+ @value_converters = {}
11
+ end
12
+
13
+ attr_accessor :table_name, :columns, :column_names, :primary_keys, :value_converters
14
+
15
+ def add_column(column)
16
+ cn = column[:column_name]
17
+ @columns[cn] = column
18
+ @column_names << cn
19
+ end
20
+
21
+ def set_value_converters(converter_hash)
22
+ @value_converters ||= {} # for backward compatibility with an old marshal dump object
23
+ @columns.each_with_index do |(k, v), i|
24
+ type = v[:format_type]
25
+ if converter_hash.has_key?(type)
26
+ @value_converters[i] = converter_hash[type]
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ end
33
+ end
@@ -0,0 +1,105 @@
1
+ require 'active_support'
2
+ require 'flydata'
3
+
4
+ module Flydata
5
+
6
+ module Source
7
+ def self.create(de)
8
+ MasterContext.new(de)
9
+ end
10
+
11
+ def self.register(component_class, type_class)
12
+ @component_classes ||= {}
13
+ source_sym = underscored_source_module_sym(component_class)
14
+ unless source_sym
15
+ raise "Class must must be under a 'SourceXxxx' module"
16
+ end
17
+ component_sym = underscored_class_name_sym(type_class)
18
+ @component_classes[component_key(source_sym, component_sym)] = component_class
19
+ end
20
+
21
+ DATA_ENTRY_TYPE_MAP = {
22
+ "RedshiftMysqlDataEntry" => :source_mysql,
23
+ "RedshiftFileDataEntry" => :source_file,
24
+ "FileDataEntry" => :source_file,
25
+ "RedshiftPostgresDataEntry" => :source_postgres,
26
+ "RedshiftZendeskDataEntry" => :source_zendesk,
27
+ }
28
+ def self.component_class_for(component_sym, de)
29
+ source_sym = DATA_ENTRY_TYPE_MAP[de['type']]
30
+ unless source_sym
31
+ raise "No source components are available for this data entry"
32
+ end
33
+ @component_classes ||= {}
34
+ component_key = component_key(source_sym, component_sym)
35
+ unless @component_classes.has_key?(component_key)
36
+ require_component(source_sym, component_sym)
37
+ unless @component_classes.has_key?(component_key)
38
+ @component_classes[component_key] = nil
39
+ end
40
+ end
41
+ klass = @component_classes[component_key]
42
+ unless klass
43
+ raise "Component '#{component_sym}' is not defined in source '#{source_sym}'"
44
+ end
45
+ klass
46
+ end
47
+
48
+ COMMAND_SUBDIR = "command"
49
+ # Returns require-friendly paths to the source specific command directory.
50
+ # ex) ["flydata/source_mysql/command", "flydata/source_postgres/command"]
51
+ def self.command_dir_paths
52
+ result = []
53
+ DATA_ENTRY_TYPE_MAP.values.each do |source_key|
54
+ path = File.join(source_dir_path(source_key), COMMAND_SUBDIR)
55
+ if File.exists?(path)
56
+ result << File.join("flydata", source_key.to_s, COMMAND_SUBDIR)
57
+ end
58
+ end
59
+ result
60
+ end
61
+
62
+ private
63
+
64
+ def self.underscored_source_module_sym(klass)
65
+ source_module = klass.name.split("::").detect{|m| m.start_with?("Source")}
66
+ source_module ? underscore_sym(source_module) : nil
67
+ end
68
+
69
+ def self.underscored_class_name_sym(klass)
70
+ underscore_sym(klass.name.split("::").last)
71
+ end
72
+
73
+ def self.underscore_sym(name)
74
+ ActiveSupport::Inflector.underscore(name).to_sym
75
+ end
76
+
77
+ def self.component_key(source_key, component_key)
78
+ "#{source_key}:#{component_key}"
79
+ end
80
+
81
+ def self.require_component(source_key, component_key)
82
+ path = source_dir_path(source_key)
83
+ path = File.join(path, "**", "#{component_key}.rb")
84
+ paths = Dir.glob(path)
85
+ paths.each {|pth| require pth}
86
+ end
87
+
88
+ def self.source_dir_path(source_key)
89
+ File.absolute_path(File.join(__FILE__, "..", source_key.to_s))
90
+ end
91
+
92
+ class MasterContext
93
+ def initialize(de)
94
+ @de = de
95
+ end
96
+ attr_reader :de
97
+
98
+ def method_missing(method_sym, *args)
99
+ source_class = Source.component_class_for method_sym, de
100
+ source_class.new(self, *args)
101
+ end
102
+ end
103
+ end
104
+
105
+ end
@@ -0,0 +1,21 @@
1
+ require 'forwardable'
2
+
3
+ module Flydata
4
+ module Source
5
+
6
+ class Component
7
+ extend Forwardable
8
+
9
+ def initialize(source, options = {})
10
+ @source = source
11
+ @options = options
12
+ end
13
+
14
+ protected
15
+
16
+ attr_reader :options
17
+ def_delegators :@source, :de
18
+ end
19
+
20
+ end
21
+ end