sklik-api 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +21 -0
- data/LICENSE.txt +20 -0
- data/README.markdown +75 -0
- data/Rakefile +64 -0
- data/VERSION +1 -0
- data/config/access.rb.example +8 -0
- data/lib/sklik-api.rb +42 -0
- data/lib/sklik-api/access.rb +54 -0
- data/lib/sklik-api/campaign.rb +132 -0
- data/lib/sklik-api/campaign_parts/adgroup.rb +127 -0
- data/lib/sklik-api/campaign_parts/adtext.rb +99 -0
- data/lib/sklik-api/campaign_parts/keyword.rb +109 -0
- data/lib/sklik-api/connection.rb +82 -0
- data/lib/sklik-api/sklik_object.rb +60 -0
- data/lib/sklik-api/xmlrpc_setup.rb +21 -0
- data/sklik-api.gemspec +101 -0
- data/test/fake_web.rb +40 -0
- data/test/helper.rb +22 -0
- data/test/unit/campaign.rb +103 -0
- metadata +236 -0
data/.document
ADDED
data/Gemfile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "json"
|
4
|
+
gem "unicode", "~> 0.4.0"
|
5
|
+
gem "text", "~> 0.2.0"
|
6
|
+
gem "i18n", "~> 0.6.0"
|
7
|
+
gem "activesupport", "~> 3.1.0"
|
8
|
+
|
9
|
+
|
10
|
+
group :development do
|
11
|
+
gem "rack-test"
|
12
|
+
gem 'shoulda-context'
|
13
|
+
gem "turn", "~> 0.8.2"
|
14
|
+
gem "minitest"
|
15
|
+
gem "ansi", "~> 1.2.5"
|
16
|
+
gem "jeweler", "~> 1.8.3"
|
17
|
+
gem "fakeweb"
|
18
|
+
gem "thin"
|
19
|
+
gem "shotgun"
|
20
|
+
gem "rcov", "= 0.9.10"
|
21
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Ondrej Bartas
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Sklik API
|
2
|
+
|
3
|
+
Sklik advertising PPC api for creating campaigns
|
4
|
+
|
5
|
+
# Implementation
|
6
|
+
|
7
|
+
Gemfile.rb
|
8
|
+
``` ruby
|
9
|
+
gem "sklik-api", :require => "sklik-api"
|
10
|
+
```
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
# Usage
|
15
|
+
|
16
|
+
``` ruby
|
17
|
+
campaign_hash = {
|
18
|
+
:name => "name of your campaign",
|
19
|
+
:cpc => 3.5, # cpc is in CZK and in float and is set for adgroup
|
20
|
+
:budget => 15.4, # budget is in CZK and in float
|
21
|
+
:customer_id => 123456, #optional without specifying it will be created on logged account
|
22
|
+
|
23
|
+
:network_setting => {
|
24
|
+
:content => true,
|
25
|
+
:search => true
|
26
|
+
},
|
27
|
+
|
28
|
+
:ad_groups => [
|
29
|
+
{
|
30
|
+
:name => "my adgroup name",
|
31
|
+
:ads => [
|
32
|
+
{
|
33
|
+
:headline => "Super headline",
|
34
|
+
:description1 => "Trying to do ",
|
35
|
+
:description2 => "best description ever",
|
36
|
+
:display_url => "bartas.cz",
|
37
|
+
:url => "http://www.bartas.cz"
|
38
|
+
}
|
39
|
+
],
|
40
|
+
:keywords => [
|
41
|
+
"\"some funny keyword\"",
|
42
|
+
"[myphrase keyword]",
|
43
|
+
"my broad keyword for me",
|
44
|
+
"test of diarcritics âô"
|
45
|
+
]
|
46
|
+
}
|
47
|
+
]
|
48
|
+
}
|
49
|
+
|
50
|
+
#you can set it before every action to sklik api, if you have multiple accounts :-)
|
51
|
+
SklikApi::Access.set(
|
52
|
+
:email => "your_email@seznam.cz",
|
53
|
+
:password => "password"
|
54
|
+
)
|
55
|
+
|
56
|
+
# this will create campaign object and do save to sklik advertising system
|
57
|
+
# if you have more than one account where to save your campaigns -> set customer_id where campaign will be created
|
58
|
+
SklikApi::Campaign.new(campaign_hash).save
|
59
|
+
```
|
60
|
+
|
61
|
+
== Contributing to sklik-api
|
62
|
+
|
63
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet.
|
64
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it.
|
65
|
+
* Fork the project.
|
66
|
+
* Start a feature/bugfix branch.
|
67
|
+
* Commit and push until you are happy with your contribution.
|
68
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
69
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
70
|
+
|
71
|
+
== Copyright
|
72
|
+
|
73
|
+
Copyright (c) 2012 Ondrej Bartas. See LICENSE.txt for
|
74
|
+
further details.
|
75
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
require 'rubygems'
|
4
|
+
require 'bundler'
|
5
|
+
begin
|
6
|
+
Bundler.setup(:default, :development)
|
7
|
+
rescue Bundler::BundlerError => e
|
8
|
+
$stderr.puts e.message
|
9
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
10
|
+
exit e.status_code
|
11
|
+
end
|
12
|
+
require 'rake'
|
13
|
+
|
14
|
+
require 'jeweler'
|
15
|
+
Jeweler::Tasks.new do |gem|
|
16
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
17
|
+
gem.name = "sklik-api"
|
18
|
+
gem.homepage = "http://github.com/ondrejbartas/sklik-api"
|
19
|
+
gem.license = "MIT"
|
20
|
+
gem.summary = %Q{Sklik advertising PPC api for creating campaigns}
|
21
|
+
gem.description = %Q{Sklik advertising PPC api for creating campaigns and updating them when they runs}
|
22
|
+
gem.email = "ondrej@bartas.cz"
|
23
|
+
gem.authors = ["Ondrej Bartas"]
|
24
|
+
# dependencies defined in Gemfile
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
|
29
|
+
#get directories!
|
30
|
+
CONF_DIR = File.expand_path(File.join("..", "config"), __FILE__)
|
31
|
+
|
32
|
+
#copy example config files for redis and elastic if they don't exists
|
33
|
+
unless File.exists?(File.join(CONF_DIR, "access.rb"))
|
34
|
+
FileUtils.cp(File.join(CONF_DIR, "access.rb.example"), File.join(CONF_DIR, "access.rb") )
|
35
|
+
puts "WARNING: you need to setup your config/access.rb -> I created this file for you with example usage"
|
36
|
+
end
|
37
|
+
|
38
|
+
require 'rake/testtask'
|
39
|
+
Rake::TestTask.new(:test) do |test|
|
40
|
+
test.libs << 'lib' << 'test'
|
41
|
+
test.pattern = 'test/unit/*.rb'
|
42
|
+
test.verbose = true
|
43
|
+
end
|
44
|
+
|
45
|
+
require 'rcov/rcovtask'
|
46
|
+
Rcov::RcovTask.new do |test|
|
47
|
+
test.libs << 'test'
|
48
|
+
test.pattern = 'test/unit/*.rb'
|
49
|
+
test.verbose = true
|
50
|
+
test.rcov_opts << '--exclude "gems/*"'
|
51
|
+
end
|
52
|
+
|
53
|
+
task :default => :test
|
54
|
+
|
55
|
+
require 'rdoc/task'
|
56
|
+
Rake::RDocTask.new do |rdoc|
|
57
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
58
|
+
|
59
|
+
rdoc.rdoc_dir = 'rdoc'
|
60
|
+
rdoc.title = "sklik-api #{version}"
|
61
|
+
rdoc.rdoc_files.include('README*')
|
62
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
63
|
+
end
|
64
|
+
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
data/lib/sklik-api.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
require 'rubygems'
|
3
|
+
require 'rack'
|
4
|
+
require 'pp'
|
5
|
+
require 'json'
|
6
|
+
require 'unicode'
|
7
|
+
require 'uri'
|
8
|
+
|
9
|
+
#for sklik miner
|
10
|
+
require 'net/http'
|
11
|
+
require 'net/https'
|
12
|
+
require "xmlrpc/client"
|
13
|
+
|
14
|
+
require 'active_support'
|
15
|
+
require 'active_support/inflector'
|
16
|
+
require 'active_support/inflector/inflections'
|
17
|
+
require 'active_support/core_ext/hash/keys'
|
18
|
+
require 'active_support/core_ext/class/inheritable_attributes'
|
19
|
+
require 'active_support/core_ext'
|
20
|
+
|
21
|
+
require 'date'
|
22
|
+
require 'logger'
|
23
|
+
|
24
|
+
ENV['RACK_ENV'] ||= "development"
|
25
|
+
|
26
|
+
|
27
|
+
#initialzie SklikApi class
|
28
|
+
class SklikApi
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
#including sklik-api
|
33
|
+
["xmlrpc_setup", "access", "connection", "sklik_object", "campaign"].each do |file|
|
34
|
+
require File.join(File.dirname(__FILE__),"/sklik-api/#{file}.rb")
|
35
|
+
end
|
36
|
+
|
37
|
+
#including config
|
38
|
+
["access"].each do |file|
|
39
|
+
require File.join(File.dirname(__FILE__),"../config/#{file}.rb")
|
40
|
+
end
|
41
|
+
|
42
|
+
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi::Access
|
3
|
+
|
4
|
+
#set credentials
|
5
|
+
def self.set args = {}
|
6
|
+
args.symbolize_keys!
|
7
|
+
#check required arguments
|
8
|
+
raise ArgumentError, "email is required" unless args[:email]
|
9
|
+
raise ArgumentError, "password is required" unless args[:password]
|
10
|
+
|
11
|
+
#save argument to right places
|
12
|
+
@args = args
|
13
|
+
|
14
|
+
#return this object!
|
15
|
+
return self
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.get
|
19
|
+
Marshal.load( Marshal.dump (@args ))
|
20
|
+
end
|
21
|
+
|
22
|
+
#return email
|
23
|
+
def self.email
|
24
|
+
@args[:email].to_s
|
25
|
+
end
|
26
|
+
|
27
|
+
#for login take first part of email "name@seznam.cz" -> "name"
|
28
|
+
def self.login
|
29
|
+
@args[:email].to_s.split("@").first
|
30
|
+
end
|
31
|
+
|
32
|
+
#return customer_id
|
33
|
+
def self.customer_id
|
34
|
+
@args.has_key?(:customer_id) && @args[:customer_id] ? @args[:customer_id] : nil
|
35
|
+
end
|
36
|
+
|
37
|
+
#return password
|
38
|
+
def self.password
|
39
|
+
@args[:password].to_s
|
40
|
+
end
|
41
|
+
|
42
|
+
#if you change Access credentials change uniq identifier ->
|
43
|
+
#used for stroing sessions for multiple logins
|
44
|
+
def self.uniq_identifier
|
45
|
+
"#{@args[:email]}:#{@args[:password]}"
|
46
|
+
end
|
47
|
+
|
48
|
+
#to prevent changes in settings dump it
|
49
|
+
def self.access
|
50
|
+
Marshal.load( Marshal.dump( @args ))
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi
|
3
|
+
class Campaign
|
4
|
+
|
5
|
+
NAME = "campaign"
|
6
|
+
|
7
|
+
include Object
|
8
|
+
=begin
|
9
|
+
Example of input hash
|
10
|
+
{
|
11
|
+
:campaign_id => 12345, #(OPTIONAL) -> when setted it will on save do update of existing campaign
|
12
|
+
:name => "my campaign name - #{Time.now.strftime("%Y.%m.%d %H:%M:%S")}",
|
13
|
+
:status => :running,
|
14
|
+
:cpc => 3,
|
15
|
+
:budget => 50,
|
16
|
+
|
17
|
+
:network_setting => {
|
18
|
+
:content => true,
|
19
|
+
:search => true
|
20
|
+
},
|
21
|
+
|
22
|
+
:ad_groups => [
|
23
|
+
{
|
24
|
+
:name => "my adgroup name",
|
25
|
+
:ads => [
|
26
|
+
{
|
27
|
+
:headline => "Super headline",
|
28
|
+
:description1 => "Trying to do ",
|
29
|
+
:description2 => "best description ever",
|
30
|
+
:display_url => "my_test_url.cz",
|
31
|
+
:url => "http://my_test_url.cz"
|
32
|
+
}
|
33
|
+
],
|
34
|
+
:keywords => [
|
35
|
+
"\"some funny keyword\"",
|
36
|
+
"[phrase keyword]",
|
37
|
+
"broad keyword for me",
|
38
|
+
"test of diarcritics âô"
|
39
|
+
]
|
40
|
+
}
|
41
|
+
]
|
42
|
+
}
|
43
|
+
=end
|
44
|
+
|
45
|
+
def initialize args
|
46
|
+
#variable where are saved current data from system
|
47
|
+
@campaign_data = nil
|
48
|
+
|
49
|
+
#initialize adgroups
|
50
|
+
@adgroups = []
|
51
|
+
if args[:ad_groups] && args[:ad_groups].size > 0
|
52
|
+
args[:ad_groups].each do |adgroup|
|
53
|
+
@adgroups << SklikApi::Adgroup.new(self, adgroup)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
@customer_id = args[:customer_id]
|
58
|
+
super args
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.find args = {}
|
62
|
+
out = []
|
63
|
+
super(NAME, args[:customer_id]).each do |campaign|
|
64
|
+
if args[:campaign_id].nil? || (args[:campaign_id] && args[:campaign_id].to_i == campaign[:id].to_i)
|
65
|
+
out << Campaign.new(
|
66
|
+
:campaign_id => campaign[:id],
|
67
|
+
:budget => campaign[:dayBudget].to_f/100.0,
|
68
|
+
:name => campaign[:name],
|
69
|
+
:status => fix_status(campaign)
|
70
|
+
)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
out
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.fix_status campaign
|
77
|
+
if campaign[:removed] == true
|
78
|
+
return :stopped
|
79
|
+
elsif campaign[:status] == "active"
|
80
|
+
return :running
|
81
|
+
elsif campaign[:status] == "suspend"
|
82
|
+
return :paused
|
83
|
+
else
|
84
|
+
return :unknown
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def to_hash
|
89
|
+
if @campaign_data
|
90
|
+
@campaign_data
|
91
|
+
else
|
92
|
+
@campaign_data = @args
|
93
|
+
@campaign_data[:ad_groups] = Adgroup.find(self).collect{|a| a.to_hash}
|
94
|
+
@campaign_data
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def create_args
|
99
|
+
out = []
|
100
|
+
|
101
|
+
#prepare campaign struct
|
102
|
+
args = {}
|
103
|
+
args[:name] = @args[:name]
|
104
|
+
args[:dayBudget] = (@args[:budget] * 100).to_i if @args[:budget]
|
105
|
+
args[:context] = @args[:network_setting][:context] ||= true if @args[:network_setting]
|
106
|
+
out << args
|
107
|
+
|
108
|
+
#add customer id on which account campaign should be created
|
109
|
+
out << @customer_id if @customer_id
|
110
|
+
pp @customer_id
|
111
|
+
pp out
|
112
|
+
out
|
113
|
+
end
|
114
|
+
|
115
|
+
def save
|
116
|
+
if @args[:campaign_id] #do update
|
117
|
+
|
118
|
+
else #do save
|
119
|
+
#create campaign
|
120
|
+
create
|
121
|
+
|
122
|
+
#create adgroups
|
123
|
+
@adgroups.each{ |adgroup| adgroup.save }
|
124
|
+
|
125
|
+
@campaign_data = @args
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
#include campaign parts
|
132
|
+
["keyword", "adtext", "adgroup"].each { |file| require File.join(File.dirname(__FILE__), "campaign_parts/#{file}.rb") }
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# -*- encoding : utf-8 -*-
|
2
|
+
class SklikApi
|
3
|
+
class Adgroup
|
4
|
+
|
5
|
+
NAME = "group"
|
6
|
+
|
7
|
+
include Object
|
8
|
+
=begin
|
9
|
+
Example of input hash
|
10
|
+
{
|
11
|
+
:adgroup_id => 1234, #(OPTIONAL) -> when setted it will on save do update of existing adgroup
|
12
|
+
:name => "my adgroup name",
|
13
|
+
:ads => [
|
14
|
+
{
|
15
|
+
:headline => "Super headline",
|
16
|
+
:description1 => "Trying to do ",
|
17
|
+
:description2 => "best description ever",
|
18
|
+
:display_url => "my_test_url.cz",
|
19
|
+
:url => "http://my_test_url.cz"
|
20
|
+
}
|
21
|
+
],
|
22
|
+
:keywords => [
|
23
|
+
"\"some funny keyword\"",
|
24
|
+
"[phrase keyword]",
|
25
|
+
"broad keyword for me",
|
26
|
+
"test of diarcritics âô"
|
27
|
+
]
|
28
|
+
}
|
29
|
+
|
30
|
+
|
31
|
+
=end
|
32
|
+
|
33
|
+
def initialize campaign, args
|
34
|
+
@adgroup_data = nil
|
35
|
+
#set adgroup owner campaign
|
36
|
+
@campaign = campaign
|
37
|
+
|
38
|
+
#initialize adgroups
|
39
|
+
@adtexts = []
|
40
|
+
if args[:ads] && args[:ads].size > 0
|
41
|
+
args[:ads].each do |adtext|
|
42
|
+
@adtexts << SklikApi::Adtext.new(self, adtext)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
#initialize adgroups
|
46
|
+
@keywords = []
|
47
|
+
if args[:keywords] && args[:keywords].size > 0
|
48
|
+
args[:keywords].each do |keyword|
|
49
|
+
@keywords << SklikApi::Keyword.new(self, :keyword => keyword)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
super args
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.find campaign, args = {}
|
57
|
+
out = []
|
58
|
+
super(NAME, campaign.args[:campaign_id]).each do |adgroup|
|
59
|
+
if args[:adgroup_id].nil? || (args[:adgroup_id] && args[:adgroup_id].to_i == adgroup[:id].to_i)
|
60
|
+
out << Adgroup.new( campaign,
|
61
|
+
:adgroup_id => adgroup[:id],
|
62
|
+
:cpc => adgroup[:cpc].to_f/100.0,
|
63
|
+
:name => adgroup[:name],
|
64
|
+
:status => fix_status(adgroup)
|
65
|
+
)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
out
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.fix_status adgroup
|
72
|
+
if adgroup[:removed] == true
|
73
|
+
return :stopped
|
74
|
+
else
|
75
|
+
return :running
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def to_hash
|
80
|
+
if @adgroup_data
|
81
|
+
@adgroup_data
|
82
|
+
else
|
83
|
+
@adgroup_data = @args
|
84
|
+
@adgroup_data[:ads] = Adtext.find(self).collect{|a| a.to_hash}
|
85
|
+
@adgroup_data[:keywords] = Keyword.find(self).collect{|k| k.to_hash}
|
86
|
+
@adgroup_data
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
|
91
|
+
def create_args
|
92
|
+
raise ArgumentError, "Adgroup need's to know campaign_id" unless @campaign.args[:campaign_id]
|
93
|
+
raise ArgumentError, "Adgroup need's to know campaigns CPC" unless @campaign.args[:cpc]
|
94
|
+
|
95
|
+
out = []
|
96
|
+
#add campaign id to know where to create adgroup
|
97
|
+
out << @campaign.args[:campaign_id]
|
98
|
+
|
99
|
+
#add adgroup struct
|
100
|
+
args = {}
|
101
|
+
args[:name] = @args[:name]
|
102
|
+
args[:cpc] = (@campaign.args[:cpc] * 100).to_i if @campaign.args[:cpc]
|
103
|
+
out << args
|
104
|
+
|
105
|
+
#return output
|
106
|
+
out
|
107
|
+
end
|
108
|
+
|
109
|
+
def save
|
110
|
+
if @args[:adgroup_id] #do update
|
111
|
+
|
112
|
+
else #do save
|
113
|
+
#create adgroup
|
114
|
+
create
|
115
|
+
|
116
|
+
#create adtexts
|
117
|
+
@adtexts.each{ |adtext| adtext.save }
|
118
|
+
|
119
|
+
#create keywords
|
120
|
+
@keywords.each{ |keyword| keyword.save }
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
|