flydata 0.6.3 → 0.6.4

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.
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