diff --git a/README.md b/README.md index 1e1887b..7aa61df 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,30 @@ -DCI-Sample -========== +## Why DCI? + +DCI (Data Context Interaction) is a new way to look at object-oriented programming. Instead of focusing on individual objects, the DCI paradigm focuses on communication between objects and makes it explicit. It improves the readability of the code, which helps programmers to reason about their programs. + +I'd been reading blogs posts and watching talks about DCI for a long time and I felt that it's time to try it out. I found that most material related to DCI is theoretical. There were a few books, a dozen of blog posts, and several presentations that could give you a good picture of what DCI is. However, I could not find any "real world" examples of actually using it. That's why I decided to write a simple application showing how to implement DCI in Ruby. + +## Like what? + +Like the following projects: + +* [DDDSample](http://dddsample.sourceforge.net/). This is a how-to example of implementing a typical DDD application. + +* [The Silverlight Cookbook](http://silverlightcookbook.codeplex.com/). A reference application for Silverlight 5 LOB apps. + +## Ruby/Rails + +The application is a Rails project. There are a few reasons for it: + +* All the DCI concepts can be implemented in Ruby. It doesn’t feel clunky or alien. +* Ruby is concise and readable. Even if you are a C# programmer, you should be able to understand what is going on. +* There is more interest in DCI in the Ruby community than in any other community. + +## Developers + +The application has been developed by Victor Savkin and Rinaldi Fonseca. + +## Read More + +[Read more about DCI and the project.](http://dci-in-ruby.info) -A sample application illustrating the Data Context Interaction paradigm (in Ruby) \ No newline at end of file diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 0d6c5f6..54adaf9 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -2,6 +2,7 @@ body { padding-top: 60px; padding-left: 20px; + font-size: 13px; } @import "twitter/bootstrap/responsive"; @@ -42,16 +43,15 @@ select.datetime { width: 60px; } -.new_bid_params { - float: left; - padding-right: 20px; - - input[type=text]{ - width: 50px; - margin: 0px; - } -} - #bids { float: right; +} + +dl.dl-horizontal { + font-size: 17px; + + dt { + width: 200px; + margin-right: 20px; + } } \ No newline at end of file diff --git a/app/contexts/bidding.rb b/app/contexts/bidding.rb index 5aba28e..b5a9f98 100644 --- a/app/contexts/bidding.rb +++ b/app/contexts/bidding.rb @@ -17,54 +17,73 @@ def self.buy user, bid_params, listener bidding.bid end - attr_reader :bidder, :biddable_auction, :bid_creator, :listener + attr_reader :bidder, :biddable, :validator, :request, :listener def initialize user, auction, bid_params, listener @bidder = user.extend Bidder - @biddable_auction = auction.extend BiddableAuction - @bid_creator = bid_params.extend BidCreator + @biddable = auction.extend Biddable + @validator = bid_params.extend Validator + @request = bid_params @listener = listener end def bid in_context do - bidder.create_bid + validator.validate + biddable.create_bid end + rescue InvalidRecordException => e listener.create_on_error e.errors + rescue ValidationException => e listener.create_on_error [e.message] end - module Bidder + module Validator include ContextAccessor - def create_bid - validate_bid - context.biddable_auction.create_bid - end - - def validate_bid + def validate validate_bidding_against_yourself - context.biddable_auction.validate_status - context.bid_creator.validate + validate_status + validate_presence + validate_against_last_bid + validate_against_buy_it_now end def validate_bidding_against_yourself - raise ValidationException, "Bidding against yourself is not allowed." if last_bidder? + raise ValidationException, "Bidding against yourself is not allowed." if context.bidder.last_bidder? end - def last_bidder? - context.biddable_auction.last_bidder == self + def validate_status + raise ValidationException, "Bidding on closed auction is not allowed." unless context.biddable.started? + end + + def validate_presence + raise ValidationException, errors.full_messages.join(", ") unless valid? + end + + def validate_against_last_bid + last_bid = context.biddable.last_bid + raise ValidationException, "The amount must be greater than the last bid." if last_bid && last_bid.amount >= amount + end + + def validate_against_buy_it_now + buy_it_now_price = context.biddable.buy_it_now_price + raise ValidationException, "Bid cannot exceed the buy it now price." if amount > buy_it_now_price end end - module BiddableAuction + module Bidder include ContextAccessor - def validate_status - raise ValidationException, "Bidding on closed auction is not allowed." unless started? + def last_bidder? + context.biddable.last_bidder == self end + end + + module Biddable + include ContextAccessor def last_bidder return nil unless last_bid @@ -72,7 +91,7 @@ def last_bidder end def create_bid - bid = make_bid(context.bidder, context.bid_creator.amount) + bid = make_bid(context.bidder, context.request.amount) if purchasing? bid close_auction @@ -104,32 +123,4 @@ def extend_auction extend_end_date_for EXTENDING_INTERVAL end end - - module BidCreator - include ContextAccessor - - def validate - validate_presence - validate_against_last_bid - validate_against_buy_it_now - end - - def validate_presence - raise ValidationException, validation_message unless valid? - end - - def validation_message - errors.full_messages.join(", ") - end - - def validate_against_last_bid - last_bid = context.biddable_auction.last_bid - raise ValidationException, "The amount must be greater than the last bid." if last_bid && last_bid.amount >= amount - end - - def validate_against_buy_it_now - buy_it_now_price = context.biddable_auction.buy_it_now_price - raise ValidationException, "Bid cannot exceed the buy it now price." if amount > buy_it_now_price - end - end end diff --git a/app/controllers/auctions_controller.rb b/app/controllers/auctions_controller.rb index 9adb54b..62f974e 100644 --- a/app/controllers/auctions_controller.rb +++ b/app/controllers/auctions_controller.rb @@ -1,4 +1,6 @@ class AuctionsController < ApplicationController + before_filter :authenticate_user!, except: [:index, :show] + def index @auctions = AuctionsPresenter.new(Auction.all, current_user, view_context) end diff --git a/app/controllers/bids_controller.rb b/app/controllers/bids_controller.rb index eb9e394..f635186 100644 --- a/app/controllers/bids_controller.rb +++ b/app/controllers/bids_controller.rb @@ -1,4 +1,5 @@ class BidsController < ApplicationController + before_filter :authenticate_user! def create Bidding.bid(current_user, bid_params, self) diff --git a/app/models/user.rb b/app/models/user.rb index 7d29d6f..babcf11 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -6,6 +6,9 @@ class User < ActiveRecord::Base attr_accessible :name, :email, :password, :password_confirmation, :remember_me + validates :name, presence: true + validates :email, presence: true + def address Address.new(address_country, address_city, address_street, address_postal_code) end diff --git a/app/presenters/auction_presenter.rb b/app/presenters/auction_presenter.rb index dcd2cab..864b3ea 100644 --- a/app/presenters/auction_presenter.rb +++ b/app/presenters/auction_presenter.rb @@ -5,7 +5,7 @@ class AuctionPresenter def_delegator :@item, :description, :item_description def_delegator :@seller, :name, :seller_name - def_delegators :@auction, :buy_it_now_price, :id, :created_at, :extendable, :end_date + def_delegators :@auction, :buy_it_now_price, :id, :created_at, :extendable def initialize auction, current_user, view_context @auction = auction @@ -18,21 +18,27 @@ def initialize auction, current_user, view_context @current_user = current_user end + def render_end_date + return "" unless @auction.end_date + h.content_tag :dl, class: "dl-horizontal" do + h.content_tag(:dt, "End Date") + + h.content_tag(:dd, @auction.end_date, id: "end-date") + end + end + def render_winner return "" unless @winner - h.content_tag :p do - h.content_tag(:b, "Winner") + - h.tag(:br) + - h.content_tag(:span, @winner.name, id: "winner") + h.content_tag :dl, class: "dl-horizontal" do + h.content_tag(:dt, "Winner") + + h.content_tag(:dd, @winner.name, id: "winner") end end def render_last_bid return "" unless @auction.last_bid - h.content_tag :p do - h.content_tag(:b, "Last Bid") + - h.tag(:br) + - @auction.last_bid.amount.to_s + h.content_tag :dl, class: "dl-horizontal" do + h.content_tag(:dt, "Last Bid") + + h.content_tag(:dd, @auction.last_bid.amount.to_s, id: "last-bid") end end @@ -55,7 +61,7 @@ def render_actions def all_bids @auction.bids.map do |bid| h.content_tag :li do - "#{bid.user.name} $#{bid.amount}" + "#{bid.user.name} bids $#{bid.amount}" end.html_safe end.join("").html_safe end diff --git a/app/views/auctions/_bid.html.erb b/app/views/auctions/_bid.html.erb index d737e6b..909c6d6 100644 --- a/app/views/auctions/_bid.html.erb +++ b/app/views/auctions/_bid.html.erb @@ -1,4 +1,6 @@ -<%= form_for BidParams.empty, url: auction_bids_path(auction_id) do |f| %> - <%= f.text_field :amount %> +<%= form_for BidParams.empty, url: auction_bids_path(auction_id), html:{class: "form-inline", style: 'float: left; margin-right: 20px;'} do |f| %> +
- Description
- <%= @auction.item_description %>
-
- Seller
- <%= @auction.seller_name %>
-
- Price
- <%= @auction.buy_it_now_price %>
-
- Extendable
- <%= @auction.extendable %>
-
- End date
- <%= @auction.end_date %>
-
Welcome <%= @resource.email %>!
+ +You can confirm your account email through the link below:
+ +<%= link_to 'Confirm my account', confirmation_url(@resource, :confirmation_token => @resource.confirmation_token) %>
diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb new file mode 100644 index 0000000..ae9e888 --- /dev/null +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -0,0 +1,8 @@ +Hello <%= @resource.email %>!
+ +Someone has requested a link to change your password, and you can do this through the link below.
+ +<%= link_to 'Change my password', edit_password_url(@resource, :reset_password_token => @resource.reset_password_token) %>
+ +If you didn't request this, please ignore this email.
+Your password won't change until you access the link above and create a new one.
diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb new file mode 100644 index 0000000..2263c21 --- /dev/null +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -0,0 +1,7 @@ +Hello <%= @resource.email %>!
+ +Your account has been locked due to an excessive amount of unsuccessful sign in attempts.
+ +Click the link below to unlock your account:
+ +<%= link_to 'Unlock my account', unlock_url(@resource, :unlock_token => @resource.unlock_token) %>
diff --git a/app/views/devise/passwords/edit.html.erb b/app/views/devise/passwords/edit.html.erb new file mode 100644 index 0000000..fe620ef --- /dev/null +++ b/app/views/devise/passwords/edit.html.erb @@ -0,0 +1,16 @@ +Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), :data => { :confirm => "Are you sure?" }, :method => :delete %>.
+ +<%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.erb b/app/views/devise/registrations/new.html.erb new file mode 100644 index 0000000..ac1e312 --- /dev/null +++ b/app/views/devise/registrations/new.html.erb @@ -0,0 +1,42 @@ +<%= form_for(resource, :as => resource_name, :url => registration_path(resource_name), html: {class: 'form-horizontal'}) do |f| %> + + + + +