gladwords 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.circleci/config.yml +34 -0
- data/.gitignore +4 -0
- data/.projections.json +5 -0
- data/.rspec +1 -0
- data/.rubocop.yml +57 -0
- data/.rubocop_todo.yml +32 -0
- data/.vim/coc-settings.json +12 -0
- data/.vim/install.sh +38 -0
- data/.vscode/launch.json +13 -0
- data/.vscode/settings.json +9 -0
- data/.vscode/tasks.json +21 -0
- data/Gemfile +20 -0
- data/Gemfile.lock +200 -0
- data/LICENSE.txt +21 -0
- data/README.md +71 -0
- data/Rakefile +15 -0
- data/bin/rake +31 -0
- data/bin/rspec +31 -0
- data/bin/solargraph +29 -0
- data/config/environment.rb +3 -0
- data/gladwords.code-workspace +11 -0
- data/gladwords.gemspec +27 -0
- data/lib/ext/rom/inflector.rb +8 -0
- data/lib/gladwords.rb +22 -0
- data/lib/gladwords/associations.rb +7 -0
- data/lib/gladwords/associations/many_to_many.rb +18 -0
- data/lib/gladwords/associations/many_to_one.rb +22 -0
- data/lib/gladwords/associations/one_to_many.rb +19 -0
- data/lib/gladwords/associations/one_to_one.rb +10 -0
- data/lib/gladwords/associations/one_to_one_through.rb +8 -0
- data/lib/gladwords/commands.rb +7 -0
- data/lib/gladwords/commands/core.rb +76 -0
- data/lib/gladwords/commands/create.rb +18 -0
- data/lib/gladwords/commands/delete.rb +22 -0
- data/lib/gladwords/commands/error_wrapper.rb +25 -0
- data/lib/gladwords/commands/update.rb +17 -0
- data/lib/gladwords/errors.rb +7 -0
- data/lib/gladwords/gateway.rb +48 -0
- data/lib/gladwords/inflector.rb +20 -0
- data/lib/gladwords/relation.rb +197 -0
- data/lib/gladwords/relation/association_methods.rb +29 -0
- data/lib/gladwords/relation/joined_relation.rb +52 -0
- data/lib/gladwords/schema.rb +26 -0
- data/lib/gladwords/schema/attributes_inferrer.rb +171 -0
- data/lib/gladwords/schema/dsl.rb +28 -0
- data/lib/gladwords/schema/inferrer.rb +19 -0
- data/lib/gladwords/selector_fields_db.rb +30 -0
- data/lib/gladwords/selector_fields_db/v201806.json +3882 -0
- data/lib/gladwords/selector_fields_db/v201809.json +4026 -0
- data/lib/gladwords/struct.rb +24 -0
- data/lib/gladwords/types.rb +27 -0
- data/lib/gladwords/version.rb +5 -0
- data/rakelib/generate_selector_fields_db.rake +72 -0
- data/spec/integration/commands/create_spec.rb +24 -0
- data/spec/integration/commands/delete_spec.rb +47 -0
- data/spec/integration/commands/update_spec.rb +24 -0
- data/spec/shared/campaigns.rb +56 -0
- data/spec/shared/labels.rb +17 -0
- data/spec/spec_helper.rb +33 -0
- data/spec/support/adwords_helpers.rb +41 -0
- data/spec/unit/commands/create_spec.rb +85 -0
- data/spec/unit/commands/delete_spec.rb +32 -0
- data/spec/unit/commands/update_spec.rb +96 -0
- data/spec/unit/inflector_spec.rb +11 -0
- data/spec/unit/relation/association_methods_spec.rb +91 -0
- data/spec/unit/relation_spec.rb +187 -0
- data/spec/unit/schema/attributes_inferrer_spec.rb +83 -0
- data/spec/unit/selector_fields_db_spec.rb +29 -0
- data/spec/unit/types_spec.rb +49 -0
- metadata +190 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2019 Gladwords Team
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Gladwords
|
2
|
+
|
3
|
+
A saner Ruby wrapper over the AdWords API, using [ROM.rb](https://rom-rb.org).
|
4
|
+
|
5
|
+
# Clout
|
6
|
+
|
7
|
+
[![CircleCI](https://circleci.com/gh/adHawk/gladwords.svg?style=svg)](https://circleci.com/gh/adHawk/gladwords)
|
8
|
+
|
9
|
+
## Usage
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
# setup your client following these instructions:
|
13
|
+
# https://github.com/googleads/google-api-ads-ruby/blob/master/adwords_api/README.md#2---using-the-client-library
|
14
|
+
client = AdwordsApi::Api.new
|
15
|
+
|
16
|
+
ROM::Configuration.new(:adwords, client: client) do |config|
|
17
|
+
config.relation(:campaigns) do
|
18
|
+
auto_struct(true)
|
19
|
+
auto_map(true)
|
20
|
+
|
21
|
+
schema(infer: true) do
|
22
|
+
attribute :id, Gladwords::Types::ID
|
23
|
+
|
24
|
+
primary_key :id
|
25
|
+
|
26
|
+
associations do
|
27
|
+
has_many :ad_groups, combine_key: :campaign_id
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
config.relation(:ad_groups) do
|
33
|
+
auto_struct(true)
|
34
|
+
auto_map(true)
|
35
|
+
|
36
|
+
schema(infer: true) do
|
37
|
+
attribute :id, Gladwords::Types::ID
|
38
|
+
|
39
|
+
primary_key :id
|
40
|
+
|
41
|
+
associations do
|
42
|
+
belongs_to :campaign, combine_key: :id
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
scope = subject.select(:id, :name).where(name: 'Test Campaign 2')
|
49
|
+
gladwords = ROM.container(configuration)
|
50
|
+
|
51
|
+
# query the relations
|
52
|
+
campaigns = gladwords.relations[:campaigns]
|
53
|
+
campaigns.select(:id, :name).where(name: 'Campaign 2').to_a # [{ id: '123', name: 'Campaign 2']]
|
54
|
+
```
|
55
|
+
|
56
|
+
## Installation
|
57
|
+
|
58
|
+
Add this line to your application's Gemfile:
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
gem 'attr-gather'
|
62
|
+
```
|
63
|
+
|
64
|
+
And then execute:
|
65
|
+
|
66
|
+
$ bundle
|
67
|
+
|
68
|
+
Or install it yourself as:
|
69
|
+
|
70
|
+
$ gem install attr-gather
|
71
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rake'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
require 'rspec/core/rake_task'
|
6
|
+
require 'rubocop/rake_task'
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
9
|
+
RuboCop::RakeTask.new(:lint)
|
10
|
+
|
11
|
+
task :default do
|
12
|
+
sh "curl -fLSs https://circle.ci/cli | DESTDIR=#{__dir__}/tmp bash" unless File.exist?('./tmp/circleci')
|
13
|
+
|
14
|
+
sh './tmp/circleci local execute build'
|
15
|
+
end
|
data/bin/rake
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rake' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
# rubocop:disable all
|
12
|
+
require "pathname"
|
13
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
14
|
+
Pathname.new(__FILE__).realpath)
|
15
|
+
|
16
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
17
|
+
|
18
|
+
if File.file?(bundle_binstub)
|
19
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
20
|
+
load(bundle_binstub)
|
21
|
+
else
|
22
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
23
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require "rubygems"
|
28
|
+
require "bundler/setup"
|
29
|
+
|
30
|
+
load Gem.bin_path("rake", "rake")
|
31
|
+
# rubocop:enable all
|
data/bin/rspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'rspec' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
# rubocop:disable all
|
12
|
+
require "pathname"
|
13
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
14
|
+
Pathname.new(__FILE__).realpath)
|
15
|
+
|
16
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
17
|
+
|
18
|
+
if File.file?(bundle_binstub)
|
19
|
+
if File.read(bundle_binstub, 150) =~ /This file was generated by Bundler/
|
20
|
+
load(bundle_binstub)
|
21
|
+
else
|
22
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
23
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
require "rubygems"
|
28
|
+
require "bundler/setup"
|
29
|
+
|
30
|
+
load Gem.bin_path("rspec-core", "rspec")
|
31
|
+
# rubocop:enable all
|
data/bin/solargraph
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
#
|
5
|
+
# This file was generated by Bundler.
|
6
|
+
#
|
7
|
+
# The application 'solargraph' is installed as part of a gem, and
|
8
|
+
# this file is here to facilitate running it.
|
9
|
+
#
|
10
|
+
|
11
|
+
require "pathname"
|
12
|
+
ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
|
13
|
+
Pathname.new(__FILE__).realpath)
|
14
|
+
|
15
|
+
bundle_binstub = File.expand_path("../bundle", __FILE__)
|
16
|
+
|
17
|
+
if File.file?(bundle_binstub)
|
18
|
+
if File.read(bundle_binstub, 300) =~ /This file was generated by Bundler/
|
19
|
+
load(bundle_binstub)
|
20
|
+
else
|
21
|
+
abort("Your `bin/bundle` was not generated by Bundler, so this binstub cannot run.
|
22
|
+
Replace `bin/bundle` by running `bundle binstubs bundler --force`, then run this command again.")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require "rubygems"
|
27
|
+
require "bundler/setup"
|
28
|
+
|
29
|
+
load Gem.bin_path("solargraph", "solargraph")
|
data/gladwords.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'gladwords/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'gladwords'
|
9
|
+
spec.version = Gladwords::VERSION.dup
|
10
|
+
spec.authors = ['Ian Ker-Seymer', 'Patrick Sparrow']
|
11
|
+
spec.email = ['ian@tryadhawk.com', 'patrick@tryadhawk.com']
|
12
|
+
spec.summary = 'AdWords support for ROM.rb'
|
13
|
+
spec.description = spec.summary
|
14
|
+
spec.homepage = 'https://github.com/adHawk/gladwords'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0")
|
18
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ['lib']
|
21
|
+
|
22
|
+
spec.add_runtime_dependency 'google-adwords-api'
|
23
|
+
spec.add_runtime_dependency 'rom', '~> 4.2.0'
|
24
|
+
|
25
|
+
spec.add_development_dependency 'bundler'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
27
|
+
end
|
data/lib/gladwords.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rom'
|
4
|
+
|
5
|
+
require_relative 'gladwords/inflector'
|
6
|
+
require_relative 'ext/rom/inflector'
|
7
|
+
require_relative 'gladwords/selector_fields_db'
|
8
|
+
require_relative 'gladwords/errors'
|
9
|
+
require_relative 'gladwords/version'
|
10
|
+
require_relative 'gladwords/gateway'
|
11
|
+
require_relative 'gladwords/relation'
|
12
|
+
require_relative 'gladwords/commands'
|
13
|
+
require_relative 'gladwords/types'
|
14
|
+
|
15
|
+
# rom-rb adapter for Google AdWords
|
16
|
+
#
|
17
|
+
# @api public
|
18
|
+
module Gladwords
|
19
|
+
include Dry::Core::Constants
|
20
|
+
end
|
21
|
+
|
22
|
+
ROM.register_adapter(:adwords, Gladwords)
|
@@ -0,0 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gladwords/associations/many_to_many'
|
4
|
+
require 'gladwords/associations/many_to_one'
|
5
|
+
require 'gladwords/associations/one_to_many'
|
6
|
+
require 'gladwords/associations/one_to_one'
|
7
|
+
require 'gladwords/associations/one_to_one_through'
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rom/associations/many_to_many'
|
4
|
+
|
5
|
+
module Gladwords
|
6
|
+
module Associations
|
7
|
+
# Many to many implementation
|
8
|
+
class ManyToMany < ROM::Associations::ManyToMany
|
9
|
+
def call(target:)
|
10
|
+
target
|
11
|
+
end
|
12
|
+
|
13
|
+
def preload(target, _loaded)
|
14
|
+
target
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rom/associations/many_to_one'
|
4
|
+
|
5
|
+
module Gladwords
|
6
|
+
module Associations
|
7
|
+
# A class which represents a many to one relationship (i.e. belongs_to)
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class ManyToOne < ROM::Associations::ManyToOne
|
11
|
+
def call(target:)
|
12
|
+
target
|
13
|
+
end
|
14
|
+
|
15
|
+
def preload(target, loaded)
|
16
|
+
target_pks = loaded.pluck(:campaign_id)
|
17
|
+
|
18
|
+
target.where(target.schema.primary_key_name => target_pks)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rom/associations/one_to_many'
|
4
|
+
|
5
|
+
module Gladwords
|
6
|
+
module Associations
|
7
|
+
# @api private
|
8
|
+
class OneToMany < ROM::Associations::OneToMany
|
9
|
+
def call(target:)
|
10
|
+
target
|
11
|
+
end
|
12
|
+
|
13
|
+
def preload(target, loaded)
|
14
|
+
target_pks = loaded.pluck(:id)
|
15
|
+
target.where(foreign_key => target_pks)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'gladwords/commands/error_wrapper'
|
4
|
+
|
5
|
+
module Gladwords
|
6
|
+
module Commands
|
7
|
+
# @api private
|
8
|
+
module Core
|
9
|
+
extend Dry::Core::ClassAttributes
|
10
|
+
|
11
|
+
# @api private
|
12
|
+
module Types
|
13
|
+
include Dry::Types.module
|
14
|
+
|
15
|
+
Operation = Hash.schema(
|
16
|
+
operator: Coercible::String.enum('ADD', 'SET', 'REMOVE'),
|
17
|
+
operand: Hash
|
18
|
+
)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.included(klass)
|
22
|
+
super
|
23
|
+
|
24
|
+
klass.include ErrorWrapper
|
25
|
+
klass.defines :adwords_operator
|
26
|
+
klass.defines :operand_mapper
|
27
|
+
klass.option :mutator, default: -> { method(:mutate) }
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute(tuples)
|
31
|
+
perform_operations(tuples)
|
32
|
+
end
|
33
|
+
|
34
|
+
def perform_operations(tuples)
|
35
|
+
operations = build_operations(tuples)
|
36
|
+
raw_result = mutator.call(operations)
|
37
|
+
|
38
|
+
unwrap_result(raw_result)
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def build_operations(tuples)
|
44
|
+
ensure_enumerable(tuples).map do |tuple|
|
45
|
+
operator(tuple)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def mutate(operations)
|
50
|
+
relation.dataset.mutate(operations)
|
51
|
+
end
|
52
|
+
|
53
|
+
def unwrap_result(result)
|
54
|
+
result[:value]
|
55
|
+
end
|
56
|
+
|
57
|
+
def ensure_enumerable(tuples)
|
58
|
+
if tuples.is_a?(Array)
|
59
|
+
tuples
|
60
|
+
else
|
61
|
+
[tuples]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def operator(operand)
|
66
|
+
op = if self.class.operand_mapper
|
67
|
+
self.class.operand_mapper.call(operand)
|
68
|
+
else
|
69
|
+
operand
|
70
|
+
end
|
71
|
+
|
72
|
+
Types::Operation[operator: self.class.adwords_operator, operand: op]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|