用户可以从其他网站导入他的数据。他需要做的只是在国外网站上输入他的用户名,我们将抓住所有图片并将其保存到自己的图库中。有些图片需要使用rMagick(旋转,水印)进行转换,这取决于导入器(取决于用户选择从哪个网站导入数据)
我们正在讨论最性感和最灵活的方式。我们正在使用carrierwave,但如果它适合我们,我们将改为回形针。
当前结构看起来像(大致是伪代码)
module Importer
class Website1
def grab_pictures
end
end
class Website2
def grab_pictures
end
end
end
class ImporterJob
def perform(user, type, foreign_username)
pictures = Importer::type.grab_pictures(foreign_username)
pictures.each do |picture|
user.pictures.create picture
end
end
end
我们为这个决定而苦苦挣扎,是进口商的最佳回报。
导入程序返回带有网址的字符串数组 [" http:// ...."," http:// ...。&# 34;," http://..."]。 我们可以轻松地循环并告诉carrierwave / paperclip remote_download图像。之后,如果需要,我们将运行处理器来转换图片。
def get_picture_urls username
pictures = []
page = get_html(username)
page.scan(/\/p\/\d{4}-\d{2}\/#{username}\/[\w\d]{32}-thumb.jpg/).each do |path|
pictures << path
end
pictures.uniq.collect{|x| "http://www.somewebsite.com/#{x.gsub(/medium|thumb/, "big")}"}
end
这实际上会返回一个数组[&#34; url_to_image&#34;,&#34; url_to_image&#34;,&#34; url_to_image&#34;]
然后在Picture.after_create中,我们调用了一些东西来移除该Image上的Watermark。
grab_pictures正在将每张图片下载到一个tempfile并对其进行转换。它将返回一个临时文件数组 [tempfile,tempfile,tempfile]
代码是:
def read_pictures username
pictures = []
page = get_html(username)
page.scan(/\/p\/\d{4}-\d{2}\/#{username}\/[a-z0-9]{32}-thumb.jpg/).each do |path|
pictures << path
end
pictures.uniq.map { |pic_url| remove_logo(pic_url) }
end
def remove_logo pic_url
big = Magick::Image.from_blob(@agent.get(pic_url.gsub(/medium.jpg|thumb.jpg/, 'big.jpg')).body).first
# ... do some transformation and watermarking
file = Tempfile.new(['tempfile', '.jpg'])
result.write(file.path)
file
end
这实际上返回了[Tempfile,Tempfile,Tempfile]
的数组结果对用户来说是一样的 - 但在内部我们发现了两种不同的数据处理方式。
我们希望将逻辑保留在它所属的位置并尽可能地通用。
你们能帮我们选择正确的方法吗?长期我们希望有大约15个不同的进口商。
答案 0 :(得分:4)
我最近遇到了类似的情况 - 我推荐一系列字符串有几个原因:
熟悉:您多久使用一次临时文件?您团队中的其他开发人员呢?操纵字符串与操纵临时文件有多容易?
灵活性:现在您只想处理图片,但将来您可能需要跟踪外部网站上每张图片的图片ID。这对于一系列字符串来说是微不足道的。使用一系列临时文件,它更难(只需多少,但事实是它会更难)。当然,这也适用于其他尚未知的目标。
速度:它比一组文件更快,使用更少的磁盘空间来处理字符串数组。这可能是一个小问题,但如果你同时充斥着很多照片,那么根据你的环境可能需要考虑。
最终,我能说的最好的事情是从字符串开始,制作一些进口商,然后看看它的外观和感觉。假装您是项目经理或客户 - 开始对您收集的数据提出奇怪的,可能不合理的要求。您目前的实施方式满足这些要求有多容易?如果你使用临时文件会更容易吗?
答案 1 :(得分:2)
我这样做是为了一个类似的项目,我必须在不同的网站上浏览和获取信息。在每个网站上,我必须通过执行大致相同的操作来达到相同的目标,并且它们的结构不同,所有结构都不同。
该解决方案的灵感来自OOP的基本原则:
主类:处理高级操作,处理数据库操作,处理图像操作,管理错误
class MainClass
def import
# Main method, prepare the download and loop through each images
log_in
go_to_images_page
images = get_list_of_images
images.each do |url|
begin
image_record = download_image url
transform_image image_record
rescue
manage_error
end
end
display_logs
send_emails
end
def download_image(url)
# Once the specific class returned the images url, this common method
# Is responsible for downloading and creating database record
record = Image.new picture: url
record.save!
record
end
def transform_image(record)
# Transformation is common so this method sits in the main class
record.watermark!
end
# ... the same for all commom methods (manage_error, display_logs, ...)
end
特定类(每个目标网站一个):处理低级别的操作并将数据返回到主类。这个类必须具有的唯一交互是网站,这意味着没有数据库访问和尽可能没有错误管理(不要被你的设计卡住;)
注意:在我的设计中,我只是从MainClass继承,但如果您愿意,可以使用模块包含。
class Target1Site < MainClass
def log_in
# Perform specific action in website to log the use in
visit '/log_in'
fill_in :user_name, with: ENV['user_name']
...
end
def go_to_images_page
# Go to specific url
visit '/account/gallery'
end
def get_list_of_images
# Use specific css paths
images = all :css, 'div#image-listing img'
images.collect{|i| i['src']}
end
# ...
end
答案 2 :(得分:2)
我解决了类似的问题......我不得不使用以下方法从xls文件导入不同的资源类型:
ResourcesGroupsImporter
)。ResourceMapper
)它充当特定映射器的接口。它为所有资源提供了通用方法,并且引发NotImplementedError
,鼓励您在添加新资源类型时实施这些方法。 DetentionsPollMapper
,FrontCycleMapper
)。每一个都为特定资源实现特定逻辑。进口商......
class ResourcesGroupsImporter
attr_reader :group
attr_reader :mappers
def initialize(_source, _resources_group)
@group = _resources_group
@source = _source
@xls = Roo::Spreadsheet.open(@source.path, extension: :xlsx)
@mappers = Resource::RESOURCEABLE_CLASSES.map { |klass| resource_mapper(klass) }
end
def import
ActiveRecord::Base.transaction do
self.mappers.each { |mapper| create_resource(mapper) }
relate_source_with_group unless self.has_errors?
raise ActiveRecord::Rollback if self.has_errors?
end
end
def has_errors?
!self.mappers.select { |mapper| mapper.has_errors? }.empty?
end
private
def resource_mapper(_class)
"#{_class}Mapper".constantize.new(@xls, @group)
end
def create_resource(_mapper)
return unless _mapper.resource
_mapper.load_resource_attributes
_mapper.resource.complete
_mapper.resource.force_validation = true
if _mapper.resource.save
create_resource_items(_mapper)
else
_mapper.load_general_errors
end
end
def create_resource_items(_mapper)
_mapper.set_items_sheet
columns = _mapper.get_items_columns
@xls.each_with_index(columns) do |data, index|
next if data == columns
break if data.values.compact.size.zero?
item = _mapper.build_resource_item(data)
_mapper.add_detail_errors(index, item.errors.messages) unless item.save
end
end
def relate_source_with_group
@group.reload
@group.source = @source
@group.save!
end
end
界面......
class ResourceMapper
attr_reader :general_errors
attr_reader :detailed_errors
attr_reader :resource
def initialize(_xls, _resource_group)
@xls = _xls
@resource = _resource_group.resourceable_by_class_type(resource_class)
end
def resource_class
raise_implementation_error
end
def items_sheet_number
raise_implementation_error
end
def load_resource_attributes
raise_implementation_error
end
def get_items_columns
raise_implementation_error
end
def build_resource_item(_xls_item_data)
resource_items.build(_xls_item_data)
end
def raise_implementation_error
raise NotImplementedError.new("#{caller[0]} method not implemented on inherited class")
end
def has_errors?
!self.general_errors.nil? || !self.detailed_errors.nil?
end
def resource_items
self.resource.items
end
def human_resource_name
resource_class.model_name.human
end
def human_resource_attr(_attr)
resource_class.human_attribute_name(_attr)
end
def human_resource_item_attr(_attr)
"#{resource_class}Item".constantize.human_attribute_name(_attr)
end
def load_general_errors
@general_errors = self.resource.errors.messages
end
def add_detail_errors(_xls_row_idx, _error)
@detailed_errors ||= []
@detailed_errors << [ _xls_row_idx+1, _error ]
end
def set_items_sheet
@xls.default_sheet = items_sheet
end
def general_sheet
sheet(0)
end
def items_sheet
sheet(self.items_sheet_number)
end
def sheet(_idx)
@xls.sheets[_idx]
end
def general_cell(_col, _row)
@xls.cell(_col, _row, general_sheet)
end
end
特定的映射器类型......
class DetentionsPollMapper < ResourceMapper
def items_sheet_number
6
end
def resource_class
DetentionsPoll
end
def load_resource_attributes
self.resource.crew = general_cell("N", 3)
self.resource.supervisor = general_cell("N", 4)
end
def get_items_columns
{
issue: "Problema identificado",
creation_date: "Fecha",
workers_count: "N° Trabajadores esperando",
detention_hours_string: "HH Detención",
lost_hours: "HH perdidas",
observations: "Observación"
}
end
def build_resource_item(_xls_item_data)
activity = self.resource.activity_by_name(_xls_item_data[:issue])
data = {
creation_date: _xls_item_data[:creation_date],
workers_count: _xls_item_data[:workers_count],
detention_hours_string: _xls_item_data[:detention_hours_string],
lost_hours: _xls_item_data[:lost_hours],
observations: _xls_item_data[:observations],
activity_id: !!activity ? activity.id : nil
}
resource_items.build(data)
end
end
class FrontCycleMapper < ResourceMapper
def items_sheet_number
8
end
def resource_class
FrontCycle
end
def load_resource_attributes
self.resource.front = general_cell("S", 3)
end
def get_items_columns
{
task: "Tarea",
start_time_string: "Hora",
task_type: "Tipo de Tarea",
description: "Descripción"
}
end
def build_resource_item(_xls_item_data)
activity = self.resource.activity_by_name_and_category(
_xls_item_data[:task], _xls_item_data[:task_type])
data = {
description: _xls_item_data[:description],
start_time_string: _xls_item_data[:start_time_string],
activity_id: !!activity ? activity.id : nil
}
resource_items.build(data)
end
end
答案 3 :(得分:2)
帮助者必须提供一种方法来访问pict。
但是保存“http:// ...”,“http:// ...”,“http:// ...”这种字符串,缺乏安全性。
我更喜欢像这样的哈希:domain_name = {“name_on_url.jpg”=&gt; path_on_disk,...}
确保访问的灵活性。