Module: DattsRight::Base

Defined in:
lib/datts_right/base.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.create_definitions!Object

Used when you have already existing records that have don’t have definitions, and you want them to



72
73
74
75
76
# File 'lib/datts_right/base.rb', line 72

def self.create_definitions!
  self.all.each do |record|
    record.create_dynamic_attribute_definition_if_needed
  end
end

.defined?Boolean

Returns:

  • (Boolean)


94
95
96
# File 'lib/datts_right/base.rb', line 94

def self.defined?
  dynamic_attributes_options[:of]
end

.defined_by?(arg) ⇒ Boolean

Returns:

  • (Boolean)


98
99
100
101
102
103
104
105
106
107
108
# File 'lib/datts_right/base.rb', line 98

def self.defined_by?(arg)
  return false unless self.defined?
  symbol = if arg.is_a?(Class)
    arg.name.underscore.to_sym
  elsif arg.is_a?(Symbol)
    arg
  else # should be an instance
    arg.class.name.underscore.to_sym
  end
  dynamic_attributes_options[:of] == symbol
end

.definesObject



90
91
92
# File 'lib/datts_right/base.rb', line 90

def self.defines
  dynamic_attributes_options[:defines] if defines?
end

.defines?(klass = nil) ⇒ Boolean

Returns:

  • (Boolean)


78
79
80
81
82
83
84
85
86
87
88
# File 'lib/datts_right/base.rb', line 78

def self.defines?(klass=nil)
  return false unless !dynamic_attributes_options[:defines].nil? && !dynamic_attributes_options[:defines].empty?
  #puts "There is [:defines], and it's not empty"
  if klass
    klass = klass.class unless klass.is_a?(Class)
    klass_symbol = klass.name.downcase.to_sym
    dynamic_attributes_options[:defines].include?(klass_symbol)
  else
    true
  end
end

.method_missing(method_id, *arguments) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/datts_right/base.rb', line 112

def self.method_missing(method_id, *arguments)
  # TODO better way to hook this into the rails code, and not define my own
  begin # Prioritize ActiveRecord's method_missing
    super(method_id, *arguments)
  rescue NoMethodError => e
    if method_id.to_s =~ /^find_(all_|last_)?by_(dynamic_attribute|datt)_([_a-z]\w*)$/
      all_or_last = $1
      attributes = $3.split("_and_")
      results = self
      attributes.each_with_index do |attribute, i|
        results = results.where_dynamic_attribute(attribute.to_sym => arguments[i])
      end

      case all_or_last
      when "all_"
        results
      when "last_"
        results.last
      when nil
        results.first
      else
        nil
      end
    else
      raise e
    end
  end
end

Instance Method Details

#has_dynamic_attributes(options = {}) ⇒ Object Also known as: has_datts



3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/datts_right/base.rb', line 3

def has_dynamic_attributes(options={})
  include DattsRight::InstanceMethods
  cattr_accessor :dynamic_attributes_options
  self.dynamic_attributes_options = options

  has_many :dynamic_attributes, :as => :attributable, :dependent => :destroy
  has_one :dynamic_attribute_definition, :as => :attribute_defineable, :dependent => :destroy
  after_save :save_dynamic_attribute_definition
  after_create :create_dynamic_attribute_definition_if_needed, :inherit_definition!
  delegate :definition, :definition=, :to => :dynamic_attribute_definition

  # Carry out delayed actions before save
  before_save :build_dynamic_attributes

  after_find :cache_dynamic_attributes
  default_scope includes(:dynamic_attributes).includes(:dynamic_attribute_definition)

  #validate :must_be_reflected_by_definer

  # scope :scope_self when looking through attributes so we don't look through all dynamic_attributes
  # Why? What if you have Friend and Page models. 
  #   * Some Phone records have a dynamic_attribute :price
  #   * Some Page records have a dynamic_attribute :price
  #
  #   When we do Page.find_by_price(400) we want to search only the dynamic_attributes that belong to Page
  #     and we want to disregard the rest of the dynamic_attributes.
  scope :scope_self, lambda { joins(:dynamic_attributes).where("dynamic_attributes.attributable_type = :klass", :klass => self.name) }
  scope :with_datt_key, lambda { |args| with_dynamic_attribute_key(args) }
  scope :with_dynamic_attribute_key, lambda { |datt_key| scope_self.joins(:dynamic_attributes).where("dynamic_attributes.attr_key = :datt_key", :datt_key => datt_key)}
  scope :with_datt_type, lambda { |args| with_dynamic_attribute_type(args) }
  scope :with_dynamic_attribute_type, lambda { |object_type| scope_self.joins(:dynamic_attributes).where("object_type = :object_type", :object_type => object_type) }

  scope :order_by_datt, lambda { |attr_key_with_order, object_type| order_by_dynamic_attribute(attr_key_with_order, object_type) }
  scope :order_by_dynamic_attribute, lambda { |attr_key_with_order, object_type|
    # possible attr_key_with_order forms: "field_name", "field_name ASC", "field_name DESC"
    split_attr_key_with_order = attr_key_with_order.split(" ")
    attr_key = split_attr_key_with_order.first
    order_by = split_attr_key_with_order.last if split_attr_key_with_order.size > 1
    order_value = "dynamic_attributes.#{object_type}_value"
    order_value << " #{order_by}" if order_by
    scope_self.with_dynamic_attribute_key(attr_key).joins(:dynamic_attributes).with_dynamic_attribute_type(object_type).order(order_value)
  }
  
  scope :where_datt, lambda { |opts| where_dynamic_attribute(opts) }
  scope :where_datts, lambda { |opts| where_dynamic_attribute(opts) }
  scope :where_dynamic_attributes, lambda { |opts| where_dynamic_attribute(opts) }
  scope :where_dynamic_attribute, lambda { |opts|
    # TODO accept stuff other than the normal hash
    # Lifted from AR::Relation#build_where
    attributes = case opts
    when String, Array
      self.expand_hash_conditions_for_aggregates(opts)
    when Hash
      opts
    end
    results = self
    attributes.each do |k, v|
      conditions = "exists (" +
        "select 1 from dynamic_attributes dynamic_attribute where " +
        "#{self.table_name}.id = dynamic_attribute.attributable_id " +
        "and dynamic_attribute.attributable_type = :attributable_type " +
        "and dynamic_attribute.attr_key = :attr_key and dynamic_attribute.#{DynamicAttribute.attr_column(v)} = :value" +
      ")"
      results = results.where(conditions, :attributable_type => self.name, :attr_key => k.to_s, :value => v)
    end
    results
  }

  # Used when you have already existing records that have don't have definitions, and you want them to
  def self.create_definitions!
    self.all.each do |record|
      record.create_dynamic_attribute_definition_if_needed
    end
  end

  def self.defines?(klass=nil)
    return false unless !dynamic_attributes_options[:defines].nil? && !dynamic_attributes_options[:defines].empty?
    #puts "There is [:defines], and it's not empty"
    if klass
      klass = klass.class unless klass.is_a?(Class)
      klass_symbol = klass.name.downcase.to_sym
      dynamic_attributes_options[:defines].include?(klass_symbol)
    else
      true
    end
  end

  def self.defines
    dynamic_attributes_options[:defines] if defines?
  end

  def self.defined?
    dynamic_attributes_options[:of]
  end

  def self.defined_by?(arg)
    return false unless self.defined?
    symbol = if arg.is_a?(Class)
      arg.name.underscore.to_sym
    elsif arg.is_a?(Symbol)
      arg
    else # should be an instance
      arg.class.name.underscore.to_sym
    end
    dynamic_attributes_options[:of] == symbol
  end

  private

  def self.method_missing(method_id, *arguments)
    # TODO better way to hook this into the rails code, and not define my own
    begin # Prioritize ActiveRecord's method_missing
      super(method_id, *arguments)
    rescue NoMethodError => e
      if method_id.to_s =~ /^find_(all_|last_)?by_(dynamic_attribute|datt)_([_a-z]\w*)$/
        all_or_last = $1
        attributes = $3.split("_and_")
        results = self
        attributes.each_with_index do |attribute, i|
          results = results.where_dynamic_attribute(attribute.to_sym => arguments[i])
        end

        case all_or_last
        when "all_"
          results
        when "last_"
          results.last
        when nil
          results.first
        else
          nil
        end
      else
        raise e
      end
    end
  end

  # Override AR::Base#respond_to? so we can return the matchers even if the
  # attribute doesn't exist in the actual columns. Is this expensive?
  def respond_to?(method_id, include_private=false)
    # TODO perhaps we could save a cache somewhere of all methods used by
    # any of the records of this class. that way, we can make this method
    # act a bit more like AR::Base#respond_to?
    #   Ex:
    #     return true if all_attributes_exists?(match.attribute_names) || all_dynamic_attributes_exists?(match.attribute_names)
    if match = ActiveRecord::DynamicFinderMatch.match(method_id)
      return true
    elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
      return true
    end

    super
  end
end

#respond_to?(method_id, include_private = false) ⇒ Boolean

Override AR::Base#respond_to? so we can return the matchers even if the attribute doesn’t exist in the actual columns. Is this expensive?

Returns:

  • (Boolean)


143
144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/datts_right/base.rb', line 143

def respond_to?(method_id, include_private=false)
  # TODO perhaps we could save a cache somewhere of all methods used by
  # any of the records of this class. that way, we can make this method
  # act a bit more like AR::Base#respond_to?
  #   Ex:
  #     return true if all_attributes_exists?(match.attribute_names) || all_dynamic_attributes_exists?(match.attribute_names)
  if match = ActiveRecord::DynamicFinderMatch.match(method_id)
    return true
  elsif match = ActiveRecord::DynamicScopeMatch.match(method_id)
    return true
  end

  super
end