顯示具有 ruby 標籤的文章。 顯示所有文章
顯示具有 ruby 標籤的文章。 顯示所有文章

2016年5月5日 星期四

writing-a-domain-specific-language-in-ruby


how to write domain-specific language in ruby

example:
Smokestack.define do
  factory User do
    name "Gabe BW"
    pet_name "Toto"
  end
end

user = Smokestack.build(User)
puts user.name == 'Gabe BW'  # true
puts user.pet_name == 'Toto' # true

other_user = Smokestack.build(User, name: "Bob")
puts other_user.name == 'Bob'      # true
puts other_user.pet_name == 'Toto' # true

how it works?

module Smokestack
  @registry = {}

  def self.registry
    @registry
  end

  def self.define(&block)
    definition_proxy = DefinitionProxy.new
    definition_proxy.instance_eval(&block)
  end

  def self.build(factory_class, overrides = {})
    instance = factory_class.new
    factory = registry[factory_class]
    attributes = factory.attributes.merge(overrides)
    attributes.each do |attribute_name, value|
      instance.send("#{attribute_name}=", value)
    end
    instance
  end
end

class DefinitionProxy
  def factory(factory_class, &block)
    factory = Factory.new
    factory.instance_eval(&block)
    Smokestack.registry[factory_class] = factory
  end
end

class Factory < BasicObject
  def initialize
    @attributes = {}
  end

  attr_reader :attributes

  def method_missing(name, *args, &block)
    @attributes[name] = args[0]
  end
end


三個主要的物件
module Smokestack => 記錄所有的, 用@registry 來記錄目前所有的定義(definition_proxy), build就是實際產生一個需要的instance
class DefinitionProxy => 定義 factory方法, 用instance_eval打開每一個factory, 再傳入需要做的block, 之後透過smokestack來registry factory

class Factory < BasicObject => 實際的每一個factory要做什麼, 用method_missing來實現 

2016年5月1日 星期日

load, autoload, require, require_relative




load, autoload, require, require_relative

load:
load 命令提供了一種最原始的方法,即每次都會重新加載整個文件,刷新內存中的類定義.

autoload:
load 命令每次都加載類有些浪費,很多類並不是一開始就需要,可以用autoload 來先創建一個鉤子,等到真的訪問到的時候再加載:
autoload :Calendar, './calendar.rb'


但這種方式有個問題: 相同常量如果多次定義autoload 鉤子,只有最後一個會被觸發. 設想在實際開發中,類定義可能分佈在多個文件中,所以這種方式並不常用.

require:
和autoload 一樣, require 想解決的也是性能問題: require 只在第一次被調用的時候被觸發,之後針對相同文件的require 就不會真正執行了

require_relative:
require_relative 相當於是默認將當前路徑加入了$LOAD_PATH,不用給相對路徑或絕對路徑, 其他和require 是一致的

2016年4月10日 星期日

ruby object


  • ruby中,一切皆對象
  • 理解ruby對像模型
  • 了解ruby查找方法的方式

描述了Ruby中方法調用的過程,“向左一步進入該對象的類,然後沿著祖先鏈一直查找方法,找到方法之後,根據自身的綁定執行該方法”。 因此,對象本身只有一組綁定,而方法定義都是在類中。那麼上面說到的單件方法和類宏應該在什麼地方定義呢?單件方法肯定不能定義在類中,否則將會影響該類的所有實例對象。類本身也是對象,類的方法不能定義在自身,因為對象的方法必須定義在對象的類中,而類對象的類是Class,如果把類方法定義到Class上,那麼所有的類對像都會擁有該方法。這一切迷思的答案都來源於一個Ruby中的高級概念,Eigenclass Eigenclass在Ruby中,當調用obj.class向一個對象索要它的類的時候,編譯器並沒有告訴大家全部的真相,你得到的類並不是你看到的類,你得到的是一個對象特有的隱藏類,這就是該對象的Eigenclass,雖然Object#class方法想把關於Eigenclass的信息隱藏起來,但是,存在即存在,總會被人挖出來的。 



Eigenclass是一個類,但是是一個很特殊的類,它只能有一個實例,且不能被繼承,但是其自身可以繼承其它類。因此,所有的對像都有一個自己的Eigenclass,單件方法就定義在該對象的Eigenclass中,類宏定義在該類對象的Eigenclass中

為了區分普通類和Eigenclass,Ruby會使用“#"表明該類是一個Eigenclass。 
*一個實例對象的Eigenclass的父類是該對象的類 
*一個類對象的Eigenclass的父類是該類對象的父類的EigenClass。 


singleton_method

singleton_method means object’s specific method

when we define method on specific object, will insert the singleton class between object and his class.
  1. puts example2. class  
  2.   
  3. class  << example2  
  4.   puts  self  
  5. end  

輸出結果:
  1. ExampleClass  
  2. #<Class:#<ExampleClass:0x28305d>>  


And if we add class method on class just like add singleton method on class 

instance_eval 與class_eval

instance_eval’s receiver must be an instance. and if an instance call instance_eval => mean you can define this instance’s singleton_method.
and because class is a instance of Class => so class also can call instance_eval => define this class’s singleton_method => class method

class_eval’s receiver must be an class. and if class call class_eval => you can define this class’s instance_method

instance_eval must be call by instance, use to define singleton_methods

class_eval must be call by class, use to define instance_methods 

2016年2月13日 星期六

ActiveModel: Make Any Ruby Object Feel Like ActiveRecord


  • ActiveModel API

the important thing about the ActiveModel API is that your models can become ActiveModel compliant without using a single line of Rails code
use "include ActiveModel::Lint::Tests” to make sure your model become ActiveModel compliant

  • The Validations System

use "include ActiveModel::Validations” to add validation module to your model

The validations system calls read_attribute_for_validation to get the attribute, but by default, it aliases that method to send, which supports the standard Ruby attribute system of attr accessor

validates_presence_of is using the more primitive validates_with, passing it the validator class, merging in {:attributes => attribute names} into the options passed to the validator

  • Serialization

include ActiveModel::Serialization

ActiveRecord also comes with default serialization for JSON and XML


  • AttributeMethods: Makes it easy to add attributes that are set like table_name :foo
  • Callbacks: ActiveRecord-style lifecycle callbacks.
  • Dirty: Support for dirty tracking
  • Naming: Default implementations of model.model_name, which are used by ActionPack (for instance, when you do render :partial => model
  • Observing: ActiveRecord-style observers
  • StateMachine: A simple state-machine implementation for models
  • Translation: The core translation support

2016年2月8日 星期一

Confident Ruby

chapter 1.
     four parts of a method
  • collecting input
  • performing work
  • delivering output
  • handling failures
chapter 2.
1. We must identify the messages we want to send in order to accomplish the task at hand.
2. We must identify the roles which correspond to those messages.
3. We must ensure the method's logic receives objects which can play those roles
第二張 performing work
在講說
method裡面要做的事情
要定義好說
要傳遞的訊息是什麼(Message) 以及 誰接收這個訊息(Receiver Role)

def import_legacy_purchase_data(data)
          purchase_list = legacy_data_parser.parse_purchase_records(data)
purchase_list.each do |purchase_record|      
customer =    customer_list.get_customer(purchase_record.email_address)      
product  =    product_inventory.get_product(purchase_record.product_id)      
customer.add_purchased_product(product)      
customer.notify_of_files_available(product)      
log_successful_import(purchase_record)
end
end

chapter 3.
3.2 Use built-in conversion protocols
If we want to provide maximum leeway in input, we can use explicit conversion methods like #to_i. If we want to provide a little  exibility while ensuring that client code isn't blatantly mis-using our method, we can use an implicit conversion such as #to_int
3.3 Conditionally call conversion methods
在講說 有時候輸入的參數 要確認型別時 就條件式的轉換
她有這種型別方法的時候 就轉換
可能可以有好幾種型別  都沒有的時候就報錯
3.4 Define your own conversion protocols
建立自己的轉換型別的方法
3.5 Define conversions to user-defined types
定義好轉型的方法

主要方法接收參數時 只接有此轉型方法的參數
其餘的要報錯誤

3.6 Use built-in conversion functions
用最大的力量來轉型

3.7 Use the Array() conversion function to array-ify inputs
當參數需要是array時 用Array(參數) 先來強制轉型 

3.8 Define conversion functions
建立一個idempotent的方法 強制轉換, 中間可能包含多種的轉換 不過結果就是出來一種型態
當我們需要某一個型態的時候 都用這個方法來強制轉換

3.9 Replace "string typing" with classes
當傳入的參數是string 而且方法裡面有很多case when的時候
試著把傳入的參數 變成有意義的物件

3.12 Reject unworkable values with preconditions
在preconditions就把一些需要的檢查做掉 還有需要使用到的變數轉換

3.13 Use #fetch to assert the presence of Hash keys
用fetch來判斷hash有沒有哪些key值

3.14 Use #fetch for defaults
用fetch來判斷default值
用block來當做參數 丟入fetch
If we had used the block form, the expensive computation would only have been triggered when it was actually needed.

3.15 Document assumptions with assertions
接外部可能會變動的api時
要再接的時候 針對每個參數 做處理檢查 一有不同 就要報錯
ex: hash要用 fetch確保參數都有, 型別轉換也要不是這個型別的話 就要報錯  Kernel#Float
amount = transaction.fetch("amount")
amount_cents = (Float(amount) * 100).to_i

3.16 Handle special cases with a Guard Clause
特別case的處理時 可以放在方法的最上方 當發生時 就導去別處
而不用因為用到if else 導致程式的結構不好閱讀
方法裡面主要就放常常發生的case

3.17 Represent special cases as objects
特別的case處理時 如果這個case再很多地方用到
可以用一個特別的class來處理他
例如current_user,  可以有一個特別的class => GuessUser 來代表沒有current_user的情況

3.18 Represent do-nothing cases as null objects
有時候會有一些要判斷是不是nil的情況時
可以用一個null object來處理  就不用寫很多if來判斷是不是nil
但是在null object裡面 log要寫好 不然有一些錯誤永遠不會發現
class NullObject < BasicObject
     def method_missing(*) end
     def respond_to_missing?(name) true end
     def nil? true end
     def ! true end
end

3.19 Substitute a benign value for nil
當nil的情況 是要走另一種情形 不會導致錯誤, 給他一個有意義的值

3.20 Use symbols as placeholder objects
當nil的情況 會導致錯誤時
給他一個有意義的symbol來報錯 會比較好debug

3.21 Bundle arguments into parameter objects
當兩個參數一定是要一起出現時 用一個物件把他包起來
當有if的情況出現時, 看是不是可以在refactor

3.22 Yield a parameter builder object
後面看不太懂

3.23 Receive policies instead of data
可以用block的方式 來做錯誤處理
def delete_files(files, &error_policy)
     error_policy ||= ->(file, error) { raise error }
     files.each do |file|
          begin
                File.delete(file)
           rescue => error
               error_policy.call(file, error)
           end
     end
end

4.1 Write total functions
當回傳的形態是array時
不管任何情形都要回array

4.2 Call back instead of returning
有時候用callback來取代回傳直會更好
用callback有時能更明顯的表達出這個function在做什麼
command-query separation (CQS). CQS is a simplifying principle of OO design which advises us to write methods which either have side effects (commands), or return values (queries), but never both

4.3 Represent failure with a benign value
不要用nil回復失敗的情況
用個有意義一點的值

4.4 Represent failure with a special case object
錯誤case時用特別的物件來回
反正不要nil就是了

4.5 Return a status object
當回傳情況有多種時 可以用一個新的狀態物件來回傳

4.6 Yield a status object
一樣用callback 可以讓function更清楚的表達她是要做什麼
callback是一個狀態物件的各種處理情形
就可以知道回傳的各種情況 是怎麼處理

4.7 Signal early termination with throw
用throw和catch來提早結束某些情況

5.1 Prefer top-level rescue clause
用top-level的rescue

5.2 Use checked methods for risky operations
還是用block的方式 傳入錯誤的時候要怎麼處理

5.3 Use bouncer methods

在用另一個方法 來包裝錯誤處理完後 要回傳的東西 

2014年11月15日 星期六

production環境上 也能直接顯示css js的變動

production環境上 也能直接顯示css js的變動

因為人因需求 需要直接修改production環境的css檔
因此先修改production.rb的設定
rails s -e production
# Code is not reloaded between requests.
# config.cache_classes = true
   config.cache_classes = false

# Disable Rails's static asset server (Apache or nginx will already do this).
  #config.serve_static_assets = false
  config.serve_static_assets = true

# Do not fallback to assets pipeline if a precompiled asset is missed.
  # config.assets.compile = false
  config.assets.compile = true

# Generate digests for assets URLs.
  #config.assets.digest = true
  config.assets.digest = false

2014年8月9日 星期六

call create! with a block

call create! with a block

def self.create_with_omniauth(auth)
    create! do |user|
      user.provider = auth["provider"]
      user.uid = auth["uid"]
      user.name = auth["user_info"]["name"]
    end
  end
This is useful as it allows us to modify the new user before it’s saved to the database and because it returns the new user

2014年7月19日 星期六

ruby . array.all , bsearch


data.select { |h| h[:sex] == sex}.all? { |a| a[:age] > age_is_greater_than }
先select出來 再用all來找出是否全部符合條件
all?() public
Passes each element of the collection to the given block. The method returns true if the block never returns false or nil. If the block is not given, Ruby adds an implicit block of {|obj| obj} (that is all? will return true only if none of the collection members are false or nil.)
bsearch
ary = [0, 4, 7, 10, 12]
# try to find v such that 4 <= v < 8
ary.bsearch {|x| 1 - x / 4 } #=> 4 or 7
# try to find v such that 8 <= v < 10
ary.bsearch {|x| 4 - x / 2 } #=> nil
我簡單的理解成當要用binary search來找array時 如何設計條件
要找出4 <= v < 8的 就是條件中{|x| 1 - x / 4 } x的範圍為多少時 結果為0
In find-any mode (this behaves like libc’s bsearch(3)), the block must return a number, and there must be two indices i and j (0 <= i <= j <= ary.size) so that:
the block returns a positive number for ary if 0 <= k < i,
the block returns zero for ary if i <= k < j, and
the block returns a negative number for ary if j <= k < ary.size.
Under this condition, this method returns any element whose index is within i…j. If i is equal to j (i.e., there is no element that satisfies the block), this method returns nil.

2014年7月1日 星期二

devise omniauth-fb carrierwave 登入


安裝 gem
/Gemfile
gem "devise" gem "omniauth-facebook"
設定 devise
/config/initializiers/devise.rb
config.omniauth :facebook, Settings.story.fb_app_id, Settings.story.fb_secret, {:provider_ignores_state => true}
add the omniauthable model to the devise method in our User model.
/app/models/user.rb
class User < ActiveRecord::Base devise :database_authenticatable, :registerable, :omniauthable, :recoverable, :rememberable, :trackable, :validatable end
如此會產生兩個routes
user_omniauth_authorize
GET|POST /users/auth/:provider(.:format) omniauth_callbacks#passthru {:provider=>/facebook/}
user_omniauth_callback GET|POST /users/auth/:action/callback(.:format) omniauth_callbacks#(?-mix:facebook)
建立一個omniauth_callbacks的controller來處理以上的這些資訊
設定
/routes.rb
devise_for :users, controllers: {omniauth_callbacks: "omniauth_callbacks"}
/app/controllers/omniauth_callbacks_controller.rb
1 class OmniauthCallbacksController < Devise::OmniauthCallbacksController 2 3 def all 4 user = User.from_omniauth(request.env["omniauth.auth"]) 5 if user.persisted? 6 sign_in_and_redirect user, notice: "Signed in!" 7 else 8 session["devise.user_attributes"] = user.attributes 9 redirect_to new_user_registration_url 10 end 11 end 12 13 alias_method :facebook, :all 14 end
user要多增加欄位來存provider和uid
增加上述的from_omniauth
/app/models/user.rb
19 def self.from_omniauth(auth) 20 where(auth.slice(:provider, :uid)).first_or_create do |user| 21 user.provider = auth.provider 22 user.uid = auth.uid 23 user.email = auth.info.email 24 user.name = auth.info.name 25 user.remote_image_url = auth.info.image.gsub('http://','https://') 26 end 27 end
增加new_with_session
/app/models/user.rb
29 def self.new_with_session(params, session) 30 if session["devise.user_attributes"] 31 new(session["devise.user_attributes"], without_protection: true) do |user| 32 user.attributes = params 33 user.valid? 34 end 35 else 36 super 37 end 38 end
password_required?
/app/models/user.rb
40 def update_with_password(params, *options) 41 if encrypted_password.blank? 42 update_attributes(params, *options) 43 else 44 super 45 end 46 end 47 48 def password_required? 49 super && provider.blank? 50 end 51 52 def email_required? 53 super && provider.blank? 54 end

2014年6月15日 星期日

CarrierWave & rmagick


CarrierWave provides a generator called uploader to do this to which we pass the name we want to give our uploader, in this case image.
$ rails generate uploader Avatar
加欄位
$ rails g migration add_avatar_to_users avatar:string
Open your model file and mount the uploader
class User < ActiveRecord::Base
  mount_uploader :avatar, AvatarUploader
end

resize

class MyUploader < CarrierWave::Uploader::Base
  include CarrierWave::MiniMagick

  process :resize_to_fit => [800, 800]

  version :thumb do
    process :resize_to_fill => [200,200]
  end
end