her 0.5.3 → 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (72) hide show
  1. checksums.yaml +8 -8
  2. data/.gitignore +2 -2
  3. data/.rspec +1 -2
  4. data/.travis.yml +2 -2
  5. data/README.md +10 -16
  6. data/UPGRADE.md +4 -0
  7. data/examples/grape-and-her/.env.default +3 -0
  8. data/examples/grape-and-her/Procfile +2 -0
  9. data/examples/grape-and-her/README.md +27 -0
  10. data/examples/grape-and-her/api/Gemfile +11 -0
  11. data/examples/grape-and-her/api/Rakefile +14 -0
  12. data/examples/grape-and-her/api/app/api.rb +49 -0
  13. data/examples/grape-and-her/api/app/models/organization.rb +7 -0
  14. data/examples/grape-and-her/api/app/models/user.rb +9 -0
  15. data/examples/grape-and-her/api/app/views/organizations/_base.rabl +2 -0
  16. data/examples/grape-and-her/api/app/views/organizations/index.rabl +3 -0
  17. data/examples/grape-and-her/api/app/views/organizations/show.rabl +3 -0
  18. data/examples/grape-and-her/api/app/views/users/_base.rabl +8 -0
  19. data/examples/grape-and-her/api/app/views/users/index.rabl +3 -0
  20. data/examples/grape-and-her/api/app/views/users/show.rabl +3 -0
  21. data/examples/grape-and-her/api/config.ru +5 -0
  22. data/examples/grape-and-her/api/config/boot.rb +17 -0
  23. data/examples/grape-and-her/api/config/unicorn.rb +7 -0
  24. data/examples/grape-and-her/api/db/migrations/001_create_users.rb +11 -0
  25. data/examples/grape-and-her/api/db/migrations/002_create_organizations.rb +8 -0
  26. data/examples/grape-and-her/consumer/Gemfile +23 -0
  27. data/examples/grape-and-her/consumer/app/assets/stylesheets/application.scss +190 -0
  28. data/examples/grape-and-her/consumer/app/assets/stylesheets/reset.scss +53 -0
  29. data/examples/grape-and-her/consumer/app/consumer.rb +74 -0
  30. data/examples/grape-and-her/consumer/app/models/organization.rb +13 -0
  31. data/examples/grape-and-her/consumer/app/models/user.rb +13 -0
  32. data/examples/grape-and-her/consumer/app/views/index.haml +9 -0
  33. data/examples/grape-and-her/consumer/app/views/layout.haml +20 -0
  34. data/examples/grape-and-her/consumer/app/views/organizations/index.haml +25 -0
  35. data/examples/grape-and-her/consumer/app/views/organizations/show.haml +11 -0
  36. data/examples/grape-and-her/consumer/app/views/users/index.haml +33 -0
  37. data/examples/grape-and-her/consumer/app/views/users/show.haml +9 -0
  38. data/examples/grape-and-her/consumer/config.ru +20 -0
  39. data/examples/grape-and-her/consumer/config/boot.rb +30 -0
  40. data/examples/grape-and-her/consumer/config/unicorn.rb +7 -0
  41. data/examples/grape-and-her/consumer/lib/response_logger.rb +18 -0
  42. data/her.gemspec +2 -2
  43. data/lib/her/model.rb +22 -26
  44. data/lib/her/model/associations.rb +19 -19
  45. data/lib/her/model/attributes.rb +173 -0
  46. data/lib/her/model/base.rb +17 -0
  47. data/lib/her/model/http.rb +58 -242
  48. data/lib/her/model/introspection.rb +7 -8
  49. data/lib/her/model/nested_attributes.rb +3 -3
  50. data/lib/her/model/orm.rb +15 -205
  51. data/lib/her/model/parse.rb +86 -0
  52. data/lib/her/model/paths.rb +54 -14
  53. data/lib/her/version.rb +1 -1
  54. data/spec/model/attributes_spec.rb +139 -0
  55. data/spec/model/dirty_spec.rb +40 -0
  56. data/spec/model/introspection_spec.rb +5 -5
  57. data/spec/model/orm_spec.rb +14 -128
  58. data/spec/model/paths_spec.rb +26 -0
  59. data/spec/model/validations_spec.rb +17 -0
  60. data/spec/spec_helper.rb +7 -32
  61. data/spec/support/extensions/array.rb +5 -0
  62. data/spec/support/extensions/hash.rb +5 -0
  63. data/spec/support/macros/model_macros.rb +29 -0
  64. metadata +52 -15
  65. data/examples/twitter-oauth/Gemfile +0 -13
  66. data/examples/twitter-oauth/app.rb +0 -50
  67. data/examples/twitter-oauth/config.ru +0 -5
  68. data/examples/twitter-oauth/views/index.haml +0 -9
  69. data/examples/twitter-search/Gemfile +0 -12
  70. data/examples/twitter-search/app.rb +0 -55
  71. data/examples/twitter-search/config.ru +0 -5
  72. data/examples/twitter-search/views/index.haml +0 -9
checksums.yaml CHANGED
@@ -1,15 +1,15 @@
1
1
  ---
2
2
  !binary "U0hBMQ==":
3
3
  metadata.gz: !binary |-
4
- MmIxMjIyZWU2NTIyOTdmZDdlNzU2MjBiM2NlNDU2MmFhMjJlNzVhNQ==
4
+ MmJlNGEzMjNhZjMyOTYyZWVkYmM2NjhhNGEwZDAwZmY0ODk4MWRkNQ==
5
5
  data.tar.gz: !binary |-
6
- MDJmNTM0YzI1ZmY1OTM3NDM4NTczNDYxNWZjOWI0NjU1NzNlYzY2Mg==
6
+ OGVjNGUzNDY5YTQxZmQzMzgxMDJmMWM4OGUxNmRhMjE1NWY2YzQ3Mw==
7
7
  !binary "U0hBNTEy":
8
8
  metadata.gz: !binary |-
9
- M2Q3MDc2NWJmNDI1NDM3Y2M1YjNkNjgwN2JlMWY3M2M5ZjhmYWZmMTY4Njdk
10
- MzViMjE0NDY1ZDlmOTNkNzBkNWI1MzgwNWFmYWYyM2YzOWE3YTE5YjcyZTg4
11
- YzM5ZWZkMDFjODBjMjBlN2Q0MDBiNDI5MzYyYWNhODZlZDQzNDU=
9
+ YmQ0Y2VkYTM2YTg1ZmRkNzBmODkxMGVhMDI3NTI2YzRkZmYwZTJjMzE5OTI3
10
+ Nzg0ZjRiMzU3ZTEzMTY5YTBiMjk4YjYzYzYzMWMwNjFlMmU2MzI3MTdhZDQ5
11
+ OGUyODNhMTc4YTA3ZDYzYjVlOTc5ZGY1Y2UzZTk4MzJlM2U0YzI=
12
12
  data.tar.gz: !binary |-
13
- YWJmZDEwMzlhMzkxZWJlMDZjOTZlZDRkNzM4MTMyZDBlZTlkZmM1NWUzOTMz
14
- YWFkMjBlNjgzY2U2ZWQxYTg3ZThlODZkMGVkOWU3MzVjNTU2ODc5ZjYyMGQ2
15
- YzJkNWFhYjRiY2EzZGI1YzE1OGQ5YzJkYzVjYTY0MWJiYjBmNmQ=
13
+ NzVmNzU1OWVlNjQ0NzNlNWM2MmVjZDc2NjU5OWQ5NWJlN2I0ZWRkZmY3Y2Q4
14
+ MTI1YzkyMDJhNDM2ZDBmN2ZmNmE4MzQxODc5MzhmZDEwNGFjOTE4MzFmYzA4
15
+ OTY3OGZlNjM5MDMxZGFhMzExZDRjZTRmOTNkMjZkYWFkOTA4Nzc=
data/.gitignore CHANGED
@@ -2,7 +2,7 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
- .yardoc
6
- doc
7
5
  rake
8
6
  tmp
7
+ .env
8
+ *.db
data/.rspec CHANGED
@@ -1,2 +1 @@
1
- --color
2
- --format=documentation
1
+ --colour --format=Fivemat
data/.travis.yml CHANGED
@@ -3,6 +3,6 @@ language: ruby
3
3
  rvm:
4
4
  - 1.9.3
5
5
  - 1.9.2
6
- - 1.8.7
6
+ - 2.0.0
7
7
 
8
- script: "bundle exec rake spec"
8
+ script: "echo 'COME ON!' && bundle exec rake spec"
data/README.md CHANGED
@@ -92,7 +92,7 @@ user = User.new(:fullname => "Maeby Fünke")
92
92
  user.save
93
93
  ```
94
94
 
95
- You can look into the `examples` directory for sample applications using Her. For a complete reference of all the methods you can use, check out [the documentation](http://rdoc.info/github/remiprev/her).
95
+ You can look into the `examples` directory for a sample application using Her. For a complete reference of all the methods you can use, check out [the documentation](http://rdoc.info/github/remiprev/her).
96
96
 
97
97
  ## Middleware
98
98
 
@@ -102,31 +102,23 @@ Since Her relies on [Faraday](https://github.com/technoweenie/faraday) to send H
102
102
 
103
103
  Her doesn’t support authentication by default. However, it’s easy to implement one with request middleware. Using the `connection` block, we can add it to the middleware stack.
104
104
 
105
- For example, to add a API token header to your requests in a Rails application, you would do something like this:
105
+ For example, to add a token header to your API requests in a Rails application, you could use the excellent [`request_store`](https://rubygems.org/gems/request_store) gem like this:
106
106
 
107
107
  ```ruby
108
108
  # app/controllers/application_controller.rb
109
109
  class ApplicationController < ActionController::Base
110
- around_filter :do_with_authenticated_user
111
-
112
- def do_with_authenticated_user
113
- Thread.current[:my_api_token] = session[:my_api_token]
114
- begin
115
- yield
116
- ensure
117
- Thread.current[:my_access_token] = nil
118
- end
110
+ before_filter :set_user_api_token
111
+
112
+ protected
113
+ def set_user_api_token
114
+ RequestStore.store[:my_api_token] = current_user.api_token # or something similar based on `session`
119
115
  end
120
116
  end
121
117
 
122
118
  # lib/my_token_authentication.rb
123
119
  class MyTokenAuthentication < Faraday::Middleware
124
- def initialize(app, options={})
125
- @app = app
126
- end
127
-
128
120
  def call(env)
129
- env[:request_headers]["X-API-Token"] = Thread.current[:my_api_token] if Thread.current[:my_api_token].present?
121
+ env[:request_headers]["X-API-Token"] = RequestStore.store[:my_api_token]
130
122
  @app.call(env)
131
123
  end
132
124
  end
@@ -136,6 +128,7 @@ require "lib/my_token_authentication"
136
128
 
137
129
  Her::API.setup :url => "https://api.example.com" do |connection|
138
130
  connection.use MyTokenAuthentication
131
+ connection.use Faraday::Request::UrlEncoded
139
132
  connection.use Her::Middleware::DefaultParseJSON
140
133
  connection.use Faraday::Adapter::NetHttp
141
134
  end
@@ -781,6 +774,7 @@ These fine folks helped with Her:
781
774
  * [@joanniclaborde](https://github.com/joanniclaborde)
782
775
  * [@seanreads](https://github.com/seanreads)
783
776
  * [@jonkarna](https://github.com/jonkarna)
777
+ * [@aclevy](https://github.com/aclevy)
784
778
 
785
779
  ## License
786
780
 
data/UPGRADE.md CHANGED
@@ -2,6 +2,10 @@
2
2
 
3
3
  Here is a list of backward-incompatible changes that were introduced while Her is pre-1.0. After reaching 1.0, it will follow the [Semantic Versioning](http://semver.org/) system.
4
4
 
5
+ ## 0.5.4
6
+
7
+ * Her does not support Ruby 1.8.7 anymore. You should upgrade to 1.9.2, 1.9.3 or 2.0.0.
8
+
5
9
  ## 0.5
6
10
 
7
11
  * Her is now compatible with `ActiveModel` and includes `ActiveModel::Validations`.
@@ -0,0 +1,3 @@
1
+ RACK_ENV=development
2
+ API_PORT=3100
3
+ CONSUMER_PORT=3200
@@ -0,0 +1,2 @@
1
+ api: ruby -C ./api -S bundle exec unicorn -p $API_PORT -c ./config/unicorn.rb -E $RACK_ENV
2
+ consumer: ruby -C ./consumer -S bundle exec unicorn -p $CONSUMER_PORT -c ./config/unicorn.rb -E $RACK_ENV
@@ -0,0 +1,27 @@
1
+ # Grape + Her example
2
+
3
+ This is an example of how to use Her to consume a simple API. It consists of two separate applications, a REST API (powered by `grape` and `activerecord`) and a consumer application (powered by `sinatra` and `her`).
4
+
5
+ ![](http://i.imgur.com/AGfYwzl.png)
6
+
7
+ ## Installation and Usage
8
+
9
+ ```shell
10
+ # Clone the repository
11
+ $ git clone git://github.com/remiprev/her.git
12
+
13
+ # Go to the example directory
14
+ $ cd her/examples/grape-and-her
15
+
16
+ # Go to each application and run `bundle install`
17
+ $ cd api; bundle install; cd ..
18
+ $ cd consumer; bundle install; cd ..
19
+
20
+ # Create the API database
21
+ $ cd api; sqlite3 db/development.db ""; bundle exec rake db:migrate; cd ..
22
+
23
+ # Start foreman with the Procfile
24
+ $ foreman start
25
+ ```
26
+
27
+ This should start the API on `http://0.0.0.0:3100` and the consumer on `http://0.0.0.0:3200`
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Server
4
+ gem 'unicorn'
5
+
6
+ # App
7
+ gem 'grape'
8
+ gem 'sqlite3'
9
+ gem 'activerecord', '~> 3.2'
10
+ gem 'portugal'
11
+ gem 'grape-rabl'
@@ -0,0 +1,14 @@
1
+ require 'portugal/tasks'
2
+ Portugal.configure do |config|
3
+ config.migrations_path = File.expand_path("../db/migrations", __FILE__)
4
+ end
5
+
6
+ # This task is called before Portugal executes its tasks.
7
+ # It should require ActiveRecord somehow and establish the database connection
8
+ #
9
+ # Change it so it works for your application
10
+ task :environment do
11
+ require 'bundler'
12
+ Bundler.require
13
+ ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: File.expand_path('../db/development.db', __FILE__)
14
+ end
@@ -0,0 +1,49 @@
1
+ class API < Grape::API
2
+ content_type :json, "application/json; charset=UTF-8"
3
+ format :json
4
+ formatter :json, Grape::Formatter::Rabl
5
+ use(Rack::Config) { |env| env['api.tilt.root'] = File.expand_path('../views', __FILE__) }
6
+ rescue_from :all
7
+
8
+ resources :users do
9
+ desc 'Return all users'
10
+ get nil, :rabl => "users/index" do
11
+ @users = User.all
12
+ end
13
+
14
+ desc 'Return a specific user'
15
+ get ':id', :rabl => "users/show" do
16
+ @user = User.find(params[:id])
17
+ end
18
+
19
+ desc 'Create a new user'
20
+ post nil, :rabl => "users/show" do
21
+ @user = User.new(params[:user])
22
+ error!({ :errors => @user.errors.full_messages }, 400) unless @user.save
23
+ end
24
+ end
25
+
26
+ resources :organizations do
27
+ desc 'Return all organizations'
28
+ get nil, :rabl => "organizations/index" do
29
+ @organizations = Organization.all
30
+ end
31
+
32
+ desc 'Return a specific organization'
33
+ get ':id', :rabl => "organizations/show" do
34
+ @organization = Organization.find(params[:id])
35
+ end
36
+
37
+ desc 'Return all users for specific organization'
38
+ get ':id/users', :rabl => "users/index" do
39
+ organization = Organization.find(params[:id])
40
+ @users = organization.users
41
+ end
42
+
43
+ desc 'Create a new organization'
44
+ post nil, :rabl => "organizations/show" do
45
+ @organization = Organization.new(params[:organization])
46
+ error!({ :errors => @organization.errors.full_messages }, 400) unless @organization.save
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,7 @@
1
+ class Organization < ActiveRecord::Base
2
+ # Associations
3
+ has_many :users
4
+
5
+ # Validations
6
+ validates :name, presence: true
7
+ end
@@ -0,0 +1,9 @@
1
+ class User < ActiveRecord::Base
2
+ # Associations
3
+ belongs_to :organization
4
+
5
+ # Validations
6
+ validates :email, presence: true
7
+ validates :fullname, presence: true
8
+ validates :organization, presence: true
9
+ end
@@ -0,0 +1,2 @@
1
+ attribute :id
2
+ attribute :name
@@ -0,0 +1,3 @@
1
+ collection @organizations
2
+
3
+ extends("organizations/base")
@@ -0,0 +1,3 @@
1
+ object @organization
2
+
3
+ extends("organizations/base")
@@ -0,0 +1,8 @@
1
+ attribute :id
2
+ attribute :email
3
+ attribute :fullname
4
+ attribute :organization_id
5
+
6
+ child :organization do
7
+ extends("organizations/base")
8
+ end
@@ -0,0 +1,3 @@
1
+ collection @users
2
+
3
+ extends("users/base")
@@ -0,0 +1,3 @@
1
+ object @user
2
+
3
+ extends("users/base")
@@ -0,0 +1,5 @@
1
+ require File.expand_path('../config/boot', __FILE__)
2
+
3
+ map "/" do
4
+ run API
5
+ end
@@ -0,0 +1,17 @@
1
+ # Bundler setup
2
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
3
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
4
+ Bundler.require(:default, ENV['RACK_ENV']) if defined? Bundler
5
+
6
+ # Configure ActiveRecord
7
+ ActiveRecord::Base.establish_connection adapter: 'sqlite3', database: File.expand_path('../../db/development.db', __FILE__)
8
+
9
+ # Require models
10
+ Dir[File.expand_path('../../app/models/**/*.rb', __FILE__)].each do |file|
11
+ dirname = File.dirname(file)
12
+ file_basename = File.basename(file, File.extname(file))
13
+ require "#{dirname}/#{file_basename}"
14
+ end
15
+
16
+ # Application setup
17
+ require File.expand_path('../../app/api', __FILE__)
@@ -0,0 +1,7 @@
1
+ if ENV["RACK_ENV"] == "development"
2
+ worker_processes 1
3
+ else
4
+ worker_processes 4
5
+ end
6
+
7
+ timeout 30
@@ -0,0 +1,11 @@
1
+ class CreateUsers < ActiveRecord::Migration
2
+ def change
3
+ create_table :users do |t|
4
+ t.string :email
5
+ t.string :fullname
6
+ t.integer :organization_id, null: false
7
+
8
+ t.timestamps
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ class CreateOrganizations < ActiveRecord::Migration
2
+ def change
3
+ create_table :organizations do |t|
4
+ t.string :name
5
+ t.timestamps
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Server
4
+ gem 'unicorn'
5
+
6
+ # Web
7
+ gem 'sinatra'
8
+ gem 'sinatra-reloader'
9
+ gem 'haml'
10
+ gem 'her'
11
+ gem 'multi_json'
12
+
13
+ # Assets
14
+ gem 'sass'
15
+ gem 'sprockets'
16
+ gem 'sprockets-sass'
17
+ gem 'sprockets-helpers'
18
+
19
+ group :development do
20
+ gem 'pry'
21
+ gem 'rake'
22
+ gem 'foreman'
23
+ end
@@ -0,0 +1,190 @@
1
+ @import "reset.css";
2
+
3
+ * {
4
+ -webkit-box-sizing: border-box;
5
+ -moz-box-sizing: border-box;
6
+ box-sizing: border-box;
7
+ }
8
+
9
+ html {
10
+ font-size: 62.5%;
11
+ }
12
+
13
+ body {
14
+ background: #eee;
15
+ color: #000;
16
+ padding: 30px;
17
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
18
+ font-size: 150%;
19
+ }
20
+
21
+ input, textarea, select, option, button {
22
+ font-family: 'Helvetica Neue', Helvetica, sans-serif;
23
+ font-size: 100%;
24
+ }
25
+
26
+ hr {
27
+ display: none;
28
+ }
29
+
30
+ a {
31
+ color: #1b49ff;
32
+ }
33
+
34
+ #wrap {
35
+ background: #fff;
36
+ box-shadow: 0 0 20px rgba(0,0,0,0.3);
37
+ border: 1px solid #bbb;
38
+ border-radius: 4px;
39
+ overflow:hidden;
40
+ }
41
+
42
+ #main-header {
43
+ font-size: 130%;
44
+ background: #ddd;
45
+
46
+ strong {
47
+ font-family: Pacifico, serif;
48
+ font-size: 250%;
49
+ font-weight: normal;
50
+ display: inline-block;
51
+ vertical-align: middle;
52
+ }
53
+
54
+ span {
55
+ display: inline-block;
56
+ vertical-align: middle;
57
+ color: rgba(0,0,0,0.4);
58
+ font-weight: normal;
59
+ margin: 0 0 0 20px;
60
+ font-size: 80%;
61
+ }
62
+
63
+ a {
64
+ display: block;
65
+ padding: 30px 20px;
66
+ color: #000;
67
+ text-decoration: none;
68
+
69
+ &:hover {
70
+ background: rgba(0,0,0,0.05);
71
+ }
72
+ }
73
+ }
74
+
75
+ #main-footer {
76
+ pre {
77
+ padding: 20px;
78
+ color: rgba(0,0,0,0.75);
79
+ line-height: 1.4;
80
+ font-size: 80%;
81
+ }
82
+ }
83
+
84
+ #content {
85
+ padding: 30px;
86
+
87
+ h1 {
88
+ margin: 25px 0 15px;
89
+ font-size: 150%;
90
+ &:first-child { margin-top: 0; }
91
+ }
92
+
93
+ h2 {
94
+ margin: 25px 0 15px;
95
+ font-size: 120%;
96
+ &:first-child { margin-top: 0; }
97
+ }
98
+
99
+ h3 {
100
+ margin: 25px 0 15px;
101
+ font-size: 110%;
102
+ &:first-child { margin-top: 0; }
103
+ }
104
+
105
+ pre {
106
+ background: #f9ffeb;
107
+ border: 1px solid rgba(0,0,0,0.15);
108
+ padding: 15px;
109
+ }
110
+
111
+ form {
112
+ margin: 0 0 15px;
113
+ padding: 0 0 15px;
114
+ border-bottom: 1px solid #ddd;
115
+
116
+ p {
117
+ margin: 0 0 15px;
118
+ }
119
+
120
+ label {
121
+ display: block;
122
+ margin: 0 0 6px;
123
+ cursor: pointer;
124
+ }
125
+
126
+ input[type=text], input[type=email] {
127
+ padding: 10px;
128
+ width: 100%;
129
+ border: 1px solid #ccc;
130
+ border-radius: 2px;
131
+ }
132
+
133
+ button {
134
+ cursor: pointer;
135
+ background: #1b49ff;
136
+ color: #fff;
137
+ padding: 8px 11px;
138
+ border-radius: 3px;
139
+ border: 1px solid rgba(0,0,0,0.3);
140
+ }
141
+
142
+ .errors {
143
+ padding: 15px;
144
+ background: #fcc;
145
+ border: 1px solid rgba(0,0,0,0.2);
146
+ margin: 0 0 20px;
147
+ font-size: 90%;
148
+ color: #900;
149
+
150
+ li {
151
+ margin: 0 0 5px 20px;
152
+ list-style: disc;
153
+ &:last-child { margin-bottom: 0; }
154
+ }
155
+ }
156
+ }
157
+
158
+ .records {
159
+ li {
160
+ list-style: disc;
161
+ margin: 0 0 10px 20px;
162
+
163
+ &:last-child { margin-bottom: 0; }
164
+ }
165
+ }
166
+
167
+ .no-records {
168
+ color: rgba(0,0,0,0.8);
169
+ font-style: italic;
170
+ font-size: 80%;
171
+ }
172
+
173
+ .details {
174
+ margin: 0 0 20px;
175
+
176
+ li {
177
+ list-style: disc;
178
+ margin: 0 0 6px 20px;
179
+
180
+ &:last-child { margin-bottom: 0; }
181
+ }
182
+ }
183
+
184
+ .back {
185
+ border-top: 1px solid #ddd;
186
+ padding: 15px 0 0;
187
+ margin: 30px 0 0;
188
+ font-size: 85%;
189
+ }
190
+ }