I was barking up the wrong tree with my original answer - I still have the problem but the plot thickens
I'm upgrading a Ruby 1.8.7/ Rails 3.0.20 project to Ruby 2.1.2 /Rails 4.1.8
In the code I have a class which delegates a number of instance methods.
class Account < ActiveRecord::Base
has_one :subscription
delegate :trial?, :payg? :to => :subscription
end
I have a decorator
class AccountOverview < SimpleDelegator
def self.for(account)
new(account)
end
def description
if payg?
'Pay As You Go'
elsif trial?
'Free Trial'
else
'Subscription'
end
end
end
Accessing the decorator from view gives the error message:
ActionView::Template::Error (undefined local variable or method `m' for ):
I hunted for an extraneous m, but can't find one.
I had thought that it was using the decorator ok for methods that didn't include delegated methods and that by defining the delegated functions within the class stopped the error:
def payg?
subscription.payg?
end
def trial?
subscription.trial?
end
However, it was not the case. What I was seeing was that a second attempt to render the view succeeds. But if I restart the server I get the error again.
I've tried the approach of instantiating SimpleDelegator as suggested, rather than deriving from it, and found that I could not access the methods in the original class.
Try this in the rails console:
class Test1
def testing
puts 'hi'
end
end
class Test2
def initialize(test)
SimpleDelegator.new(test)
end
def testing2
puts 'hello'
end
end
t = Test1.new
t.testing
hi
=> nil
h = SimpleDelegator.new(t)
h.testing
hi
=> nil
h = Test2.new(t)
h.testing
NoMethodError: undefined method `testing' for #<Test2:0x007fbea15d27a0>
But if instead I use
2.1.2 :008 > class Test2 < SimpleDelegator
2.1.2 :009?> def testing2
2.1.2 :010?> puts 'hello'
2.1.2 :011?> end
2.1.2 :012?> end
=> :testing2
2.1.2 :013 > h = Test2.new(t)
=> #<Test1:0x007fee090afc90>
2.1.2 :014 > h.testing
hi
=> nil
2.1.2 :015 > h.testing2
hello
=> nil
Also, I have found that the code that fails rendering in the view works in the console ( deriving from SimpleDelegator ):
a = Account.last
c = AccountOverview.new(a)
c.description
=> "Free Trial"
Reduced the classes to the very simplest
class AccountOverview < SimpleDelegator
def self.for(account)
new(account)
end
def description
'Pay As You Go'
end
class Account < ActiveRecord::Base
end
Still seeing the same error referring to missing method m
Created a brand new rails 4 project and added the same code and ran against the same db - this worked with no errors, so its something in my legacy setup.
Have given up and removed SimpleDelegator from the code and just used delegate to map the required functions to an instance variable for the base object and its now working. But not very satisfying.
class AccountOverview
delegate :payg?, :trial? :to => :#account
def initialize(account)
#account = account
end
def description
if payg?
'Pay As You Go'
elsif trial?
'Free Trial'
else
'Subscription'
end
end
end
Related
In model:
class State < ActiveRecord::Base
belongs_to :country
alias_method :abc, :xyz
def self.xyz
end
end
In log:
1.9.3-p551 :005 > State.abc
NameError: undefined method 'xyz' for class 'State'
I am new to alias_method in rails. Please help me out.
This is nothing to do with Rails, alias_method is part of ruby itself, which you really should learn before trying to use Rails.
Your problem here is that you've defined xyz as a class/singleton method, but alias_method called as you've done works on instance methods.
You can try the following :
class State
def self.xyz
ap 'inside'
end
self.singleton_class.send(:alias_method, :abc, :xyz)
end
Following should work :
>> State.xyz
>> State.abc
I am attempting to setup MongoHQ using Heroku and rails 4. I have everything setup correctly to my knowledge, but I'm now getting this error:
uninitialized constant Job::TempEmailContactStoreCsv
This is the Job model where error is happening:
class Job < ActiveRecord::Base
belongs_to :user
def store_email_contact_csv(file)
contact_array = csv_to_array(file)
TempEmailContactStoreCsv.create(email_contact_array: contact_array, job_id: id)
end
end
And my mongo model:
class TempEmailContactStoreCsv
include Mongoid::Document
field :email_contact_array, type: Array
field :job_id
def self.store(job_id, email_contact_array)
r = TempEmailContactStoreCsv.find_by(job_id: job_id)
if (r.nil?)
TempEmailContactStoreCsv.create!(job_id: job_id, email_contact_array: email_contact_array)
end
end
def self.exists?(job_id)
r = TempEmailContactStoreCsv.find_by(job_id: job_id)
return r.nil? == false
end
def self.retrieve(job_id)
return TempEmailContactStoreCsv.find_by(job_id: job_id)
end
def self.delete(job_id)
r = TempEmailContactStoreCsv.find_by(job_id: job_id)
r.destroy unless r.nil?
end
end
So it seems that my mongo model is not being initialized, and the namespacing seems weird to me also.
Any thoughts as to what is causing this error and how to fix it?
For rails to load a class automatically, the file must be within rails load path (which includes app/models so you are fine there) and the file name should be the camelcased version of the class name.
In your case the file name should be temp_email_contact_store_csv.rb not temp_email_store_csv.rb
I want to simulate an abstract class in Ruby on Rails. I.e. I want to raise an exception if someone tries to call Abstract.new, but he should be able to call Child.new (while Child < Abstract).
How to do this? Overwriting both new and initialize does not work.
In another comment, the OP mentions that the purpose of the abstract class is to share behavior (methods) needed by its children. In Ruby, that's often best done with a module used to "mix in" methods where needed. For example, instead of:
class Abstract
def foo
puts "foo!"
end
end
class Concrete
end
Concrete.new.foo # => "foo!"
this:
module Foo
def foo
puts "foo!"
end
end
class Concrete
include Foo
end
Concrete.new.foo # => "foo!"
But here's how the original request might be satisfied:
#!/usr/bin/ruby1.8
class Abstract
def initialize(*args)
raise if self.class == Abstract
super
end
end
class Concrete < Abstract
end
Concrete.new # OK
Abstract.new # Raises an exception
Why would you want to do this? The point of abstract/interfaced classes are to hack Strongly typed languages into a dynamic paradigm. If you need your class to fit in the signature, name your methods according to the original class or make a facade and plug it in, no need to trick a compiler into allowing it, it just works.
def my_printer obj
p obj.name
end
So I defined the interface as any object with a name property
class person
attr_accessor :name
def initialize
#name = "Person"
end
end
class Employee
attr_accessor :name
def initialize
#name = "Employee"
#wage = 23
end
end
so nothing stops us from calling our printer method with either of these
my_printer Person.new
my_printer Employee.new
both print there names without a hitch :D
You almost always need to do this to enforce an API, when some third party is going to implement some stub, and you're sure they're going to mess it up. You can use specific prefix-templates in your parent class and a module that introspects on creation to achieve this:
module Abstract
def check
local = self.methods - Object.methods
templates = []
methods = []
local.each do |l|
if l =~ /abstract_(.*)/ # <--- Notice we look for abstract_* methods to bind to.
templates.push $1
end
methods.push l.to_s
end
if !((templates & methods) == templates)
raise "Class #{self.class.name} does not implement the required interface #{templates}"
end
end
end
class AbstractParent
include Abstract
def initialize
check
end
def abstract_call # <--- One abstract method here
end
def normal_call
end
end
class Child < AbstractParent # <-- Bad child, no implementation
end
class GoodChild < AbstractParent
def call # <-- Good child, has an implementation
end
end
Test:
begin
AbstractParent.new
puts "Created AbstractParent"
rescue Exception => e
puts "Unable to create AbstractParent"
puts e.message
end
puts
begin
Child.new
puts "Created Child"
rescue Exception => e
puts "Unable to create Child"
puts e.message
end
puts
begin
GoodChild.new
puts "Created GoodChild"
rescue Exception => e
puts "Unable to create GoodChild"
puts e.message
end
Result:
[~] ruby junk.rb
Unable to create AbstractParent
Class AbstractParent does not implement the required interface ["call"]
Unable to create Child
Class Child does not implement the required interface ["call"]
Created GoodChild
If you want this for doing STI, you could follow the suggestions in this thread:
class Periodical < ActiveRecord::Base
private_class_method :new, :allocate
validates_presence_of :type
end
class Book < Periodical
public_class_method :new, :allocate
end
class Magazine < Periodical
public_class_method :new, :allocate
end
Caveat: I'm not sure if this is a working solution. This hides new and allocate in the base class and re-enables them in child classes -- but that alone does not seem to prevent objects being created with create!. Adding the validation on type prevents the base class from being created. I guess you could also hide create!, but I'm not sure if that covers all the ways Rails can instantiate a model object.
I'm using the Rails 3 Vestal Versions gem: https://github.com/lailsonbm/vestal_versions
I'd like to create some logic to determine if/when to create a new version on a model update. Per the specs, I did:
class Note < ActiveRecord::Base
versioned :if => :really_create_a_version?
def really_create_a_version
Rails.logger.debug 'really_create_a_version really_create_a_version really_create_a_version really_create_a_version - START'
record.inspect
#note = Note.find(32)
Rails.logger.debug 'really_create_a_version really_create_a_version really_create_a_version really_create_a_version - END'
end
end
But that doesn't work, I get the following error:
NoMethodError (undefined method `really_create_a_version?' for #<Note:0x155c39a28>):
app/controllers/notes_controller.rb:124:in `update'
Any suggestions or ideas? thxs
UPDATE
Conditional version creation. The versioned method now accepts :if and :unless options. Each expects a symbol representing an instance method or a proc that will be evaluated to determine whether or not to create a new version after an update. An array containing any combination of symbols and procs can also be given.
class User < ActiveRecord::Base
versioned :if => :really_create_a_version?
end
Define your method like this
def really_create_a_version?
You are missing the trailing ?
I've got a legacy table that my rails application shares with another application. It has a column called "class". The first time I reference any attribute in that model, I get an error. Subsequent references to attributes work. Is there a good workaround for this, or should I just go modify the other application that uses this table (ugh)?
>> Member::Ssg.find(:first)
=> #<Member::Ssg ssg_key: #<BigDecimal:10b169688,'0.253E3',4(8)>, org_id: 2, academic_year: 2006, class: true, next_due_date: "2011-06-01", submitted_date: "2006-02-13", notes: nil, owner_id: "1">
>> Member::Ssg.find(:first).notes
NoMethodError: undefined method `generated_methods' for true:TrueClass
from /Library/Ruby/Gems/1.8/gems/activerecord-2.3.8/lib/active_record/attribute_methods.rb:247:in `method_missing'
from (irb):2
>> Member::Ssg.find(:first).notes
=> nil
SOLUTION:
I went with a combination of the Bellmyer solution and adding the code below to my model
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
NOTE: Please see the updated solution at the end of this answer. Leaving the original outdated solution for historic reasons.
This has come up often enough (legacy column names interfering with ruby/rails) that I might just make a plugin out of this. Here's how you can fix it right away, though. Create this file in your app:
# lib/bellmyer/create_alias.rb
module Bellmyer
module CreateAlias
def self.included(base)
base.extend CreateAliasMethods
end
module CreateAliasMethods
def create_alias old_name, new_name
define_method new_name.to_s do
self.read_attribute old_name.to_s
end
define_method new_name.to_s + "=" do |value|
self.write_attribute old_name.to_s, value
end
end
end
end
end
And now, in your model:
class Member < ActiveRecord::Base
include Bellmyer::CreateAlias
create_alias 'class', 'class_name'
end
The first parameter to create_alias is the old method name, and the second parameter is the new name you want to call it, that won't interfere with rails. It basically uses the read_attribute and write_attribute methods to interact with the column instead of the ruby methods that get defined by ActiveRecord. Just be sure to use the new name for the field everywhere, like so:
member.class_name = 'helper'
This works with ruby 1.8, but I haven't tested with ruby 1.9 yet. I hope this helps!
UPDATE: I've found a better solution that works in Rails 3, the safe_attributes gem. I've written a blog post explaining how to use it, with example code snippets, and a full sample app you can download from github and play around with. Here's the link:
Legacy Database Column Names in Rails 3
The following works in Rails 6.0.2.2
class ReasonCode < ApplicationRecord
class << self
def instance_method_already_implemented?(method_name)
return true if method_name == 'class'
super
end
end
def as_json(options={})
add_class = attributes.keys.include?('class')
if add_class
if options[:only]
add_class = Array(options[:only]).map(&:to_s).include?('class')
elsif Array(options[:except])
add_class = Array(options[:except]).map(&:to_s).exclude?('class')
end
end
options[:except] = Array(options[:except])
options[:except].push('class')
json = super(options)
json['class'] = attributes['class'] if add_class
json
end
end
Adapted from this answer https://www.ruby-forum.com/t/activerecord-column-with-reserved-name-class/125705/2. The as_json method was added because rendering the record as json gave a SystemStackError (stack level too deep). I followed the serialization code in the Rails repo to only render the class attribute if specified in the as_json options.