Rails - really_create_a_version in the model - ruby-on-rails

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 ?

Related

Append an attribute to a model for temporary use on Rails 3

I have a user model => #user
I want to add new attribute current_time to #user for temporary use.
Don't want to do migration to add a column (just for temporary use):
#user.current_time = Time.now
Is there any way to achieve this?
NoMethodError (undefined method `current_time=' for #<User:0x007fd6991e1050>):
app/controllers/carts_controller.rb:47:in `block in search_user'
app/controllers/carts_controller.rb:45:in `search_user'
attr_accessor will set up a reader and writer for the instance variable:
class Foo
attr_accessor :current_time
end
foo = Foo.new
foo.current_time = Time.now # Writes value
foo.current_time # Reads value
You might also be interested in attr_reader and attr_writer.
Try to define few methods in User.model:
def current_time= (time)
#current_time = time
end
def current_time
#current_time
end
UPD according to precisely right comment from kristinalim
Note, that attr_accessible, being part of framework, was deprecated in Rails 4. Now strong params are used instead. At the same time getter/setter attr_accessor is part of core Ruby and works as usual.
The difference between attr_accessible and attr_accessor is in very well explained in this post

expecting a hash rather than an instance

I have this error
ArgumentError (wrong number of arguments (1 for 0)):
lib/law/production.rb:20:in `clone'
lib/law/production.rb:20:in `clone_law'
lib/law/production.rb:11:in `initialize'
app/controllers/laws_controller.rb:86:in `new'
app/controllers/laws_controller.rb:86:in `prod_law'
app/controllers/laws_controller.rb:44:in `create'
when using this
module Law
class Production
attr_accessor :law
attr_accessor :creator
def initialize(law,current_user)
#law = law
#creator = current_user
clone_law
end
def current__user
User.find_by_authentication_token(session[:_csrf_token])
end
def clone_law
clone(#law)
end
end
end
where clone, create, prod_law are some methods
I assume Rails is expecting a hash but I don't understand why
Firstly, clone is a standard Ruby method.
Secondly, it expects no arguments at
all (as error message says), it should be called on the object you want to clone, like this:
#law.clone

How should I use the alias_method_chain for the build method?

I am using Ruby on Rails 3.2.13 and I would like to properly use the alias_method_chain :build, :option_name statement since I am getting a strange error. That is, ...
... in my controller file I have:
class Articles::CommentsController < ApplicationController
def create
#articles_comment = #article.comments.build(params[:comment])
...
end
end
... in my model file I have:
class Articles::Comment < ActiveRecord::Base
def self.build_with_option_name
...
end
alias_method_chain :build, :option_name
end
When I run the create controller action I get the following error in log:
ActionController::RoutingError (undefined method `build' for class `Articles::Comment'):
app/models/articles/comment.rb:5:in `<class:Comment>'
How should I use the alias_method_chain for the build method? Or, maybe better, should I proceed in another way to reach what I would like to make (for example, should I overwrite the build method in the Articles::Comment model instead of using alias_method_chain)?
Note I: I don't know if it helps, but the build method refers to an association (#article.comments). More, I do not state the build method in the Articles::Comment model because it should be "added" / "attached" to the class by the Ruby on Rails framework itself (I think it is made through meta-programming).
Note II: The same error occurs when considering the new method instead of build; that is, when using alias_method_chain :new, :option_name.
As you said, build is a method defined on association proxy. What you can do is to use association extensions, so in a model you can pass a block to your has_many call, which will be treated as an extension for given association_proxy:
class Article < ActiveRecord::Base
...
has_many :comments do
alias_method_chain :build, :option_name
end

What is the right way to override a setter method in Ruby on Rails?

I am using Ruby on Rails 3.2.2 and I would like to know if the following is a "proper"/"correct"/"sure" way to override a setter method for a my class attribute.
attr_accessible :attribute_name
def attribute_name=(value)
... # Some custom operation.
self[:attribute_name] = value
end
The above code seems to work as expected. However, I would like to know if, by using the above code, in future I will have problems or, at least, what problems "should I expect"/"could happen" with Ruby on Rails. If that isn't the right way to override a setter method, what is the right way?
Note: If I use the code
attr_accessible :attribute_name
def attribute_name=(value)
... # Some custom operation.
self.attribute_name = value
end
I get the following error:
SystemStackError (stack level too deep):
actionpack (3.2.2) lib/action_dispatch/middleware/reloader.rb:70
===========================================================================
Update: July 19, 2017
Now the Rails documentation is also suggesting to use super like this:
class Model < ActiveRecord::Base
def attribute_name=(value)
# custom actions
###
super(value)
end
end
===========================================================================
Original Answer
If you want to override the setter methods for columns of a table while accessing through models, this is the way to do it.
class Model < ActiveRecord::Base
attr_accessible :attribute_name
def attribute_name=(value)
# custom actions
###
write_attribute(:attribute_name, value)
# this is same as self[:attribute_name] = value
end
end
See Overriding default accessors in the Rails documentation.
So, your first method is the correct way to override column setters in Models of Ruby on Rails. These accessors are already provided by Rails to access the columns of the table as attributes of the model. This is what we call ActiveRecord ORM mapping.
Also keep in mind that the attr_accessible at the top of the model has nothing to do with accessors. It has a completely different functionlity (see this question)
But in pure Ruby, if you have defined accessors for a class and want to override the setter, you have to make use of instance variable like this:
class Person
attr_accessor :name
end
class NewPerson < Person
def name=(value)
# do something
#name = value
end
end
This will be easier to understand once you know what attr_accessor does. The code attr_accessor :name is equivalent to these two methods (getter and setter)
def name # getter
#name
end
def name=(value) # setter
#name = value
end
Also your second method fails because it will cause an infinite loop as you are calling the same method attribute_name= inside that method.
Use the super keyword:
def attribute_name=(value)
super(value.some_custom_encode)
end
Conversely, to override the reader:
def attribute_name
super.some_custom_decode
end
In rails 4
let say you have age attribute in your table
def age=(dob)
now = Time.now.utc.to_date
age = now.year - dob.year - ((now.month > dob.month || (now.month == dob.month && now.day >= dob.day)) ? 0 : 1)
super(age) #must add this otherwise you need to add this thing and place the value which you want to save.
end
Note:
For new comers in rails 4 you don't need to specify attr_accessible in model. Instead you have to white-list your attributes at controller level using permit method.
I have found that (at least for ActiveRecord relationship collections) the following pattern works:
has_many :specialties
def specialty_ids=(values)
super values.uniq.first(3)
end
(This grabs the first 3 non-duplicate entries in the array passed.)
Using attr_writer to overwrite setter
attr_writer :attribute_name
def attribute_name=(value)
# manipulate value
# then send result to the default setter
super(result)
end

Legacy table with column named "class" in Rails

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.

Resources