How to instantiate a model object with attributes? - ruby-on-rails

I would like to instantiate a model object specifying some attributes. For instance
post = Post.new
should set post.vote_total to 0. I tried to do this in the initialize method but it seems it's not working :
def initialize()
vote_total=0
end
thank you in advance.

Pass an attributes hash to the object, as in:
post = Post.new(:vote_total => 123, :author => "Jason Bourne", ...)
If you're new to Ruby on Rails, you'll probably want to read the Getting Started Guide, which covers this and many more useful Rails idioms in some detail.

I would use callbacks:
Available Callbacks
class Post
before_save :set_defaults
def set_defaults
self.vote_total ||= 0
#do other stuff
end
end

You could allow the database to store default values for you
class AddColumnWithDefaultValue < ActiveRecord::Migration
def change
add_column :posts, :vote_total, :integer, :default => 0
end
end
Or for an existing table:
class ChangeColumnWithDefaultValue < ActiveRecord::Migration
def up
change_column_default(:posts, :vote_total, 0)
end
def down
change_column_default(:posts, :vote_total, nil)
end
end
There's a lot of debate about storing default values in the database though.

Related

Serializing Rails 6 models based on current_user

In an API controller, I'd like to limit what fields of a model can be seen depending on who is logged in. ActiveModel Serializers would seem to allow this, but I've had no luck with the following:
class MyModelSerializer < ActiveModel::Serializer
attributes :name, :custom_field, :secret_field
has_many :linked_records
def custom_field
object.do_something
end
def filter(keys)
unless scope.is_admin?
keys.delete :secret_field
keys.delete :linked_records
end
keys
end
end
But, the filtering is never performed and so my output always contains :secret_field and :linked_records even if there's no user logged in.
Perhaps this is because I am using Rails 6, and it would seem that ActiveModel Serializers might no longer be the best tool (e.g. https://stevenyue.com/blogs/migrating-active-model-serializers-to-jserializer).
Please do offer your suggestions for a means to perform this, if you can think of a better means.
EDIT:
Further to all the comments below, here's some different code:
attributes :name, :id, :admin_only_field, :is_admin
$admin_only = %i[:id, :admin_only_field]
def attributes(*args)
hash = super
$admin_only.each do |key|
unless scope.is_admin?
hash.delete(key)
end
end
hash
end
def is_admin
if scope.is_admin?
'admin!'
else
'not an admin!'
end
end
If I then visit the model's index page without being an admin I see that the admin_only_field and id are both present, and is_admin says that I'm not. Bizarre.
class MyModelSerializer < ActiveModel::Serializer
attributes :name, :custom_field, :secret_field
has_many :linked_records
def custom_field
object.do_something
end
private
def attributes
hash = super
unless scope.is_admin?
hash.delete :secret_field
hash.delete :linked_records
end
hash
end
end

Inline `after_commit` Callbacks in Rails

I've got a sidekiq job that needs to be run after the commit, but only in some situations and not all, in order to avoid a common race condition.
For example, the below after_commit will always fire but the code inside will only execute if the flag is true (previously set in the verify method).
class User < ActiveRecord::Base
...
after_commit do |user|
if #enqueue_some_job
SomeJob.new(user).enqueue
#enqueue_some_job = nil
end
end
def verify
#enqueue_some_job = ...
...
save!
end
end
The code is a bit ugly. I'd much rather be able to somehow wrap the callback inline like this:
class User < ActiveRecord::Base
def verify
if ...
run_after_commit do |user|
SomeJob.new(user).enqueue
end
end
...
save!
end
end
Does anything built into Rails exist to support a syntax like this (that doesn't rely on setting a temporary instance variable)? Or do any libraries exist that extend Rails to add a syntax like this?
Found a solution using a via a concern. The snippet gets reused enough that it is probably a better option to abstract the instance variable and form a reusable pattern. It doesn't handle returns (not sure which are supported via after_commit since no transaction is present to roll back.
app/models/concerns/callbackable.rb
module Callbackable
extend ActiveSupport::Concern
included do
after_commit do |resource|
if #_execute_after_commit
#_execute_after_commit.each do |callback|
callback.call(resource)
end
#_execute_after_commit = nil
end
end
end
def execute_after_commit(&callback)
if callback
#_execute_after_commit ||= []
#_execute_after_commit << callback
end
end
end
app/models/user.rb
class User < ActiveRecord::Base
include Callbackable
def verify
if ...
execute_after_commit do |user|
SomeJob.new(user).enqueue
end
end
...
save!
end
end
You can use a method name instead of a block when declaring callbacks:
class User < ActiveRecord::Base
after_commit :do_something!
def do_something!
end
end
To set a condition on the callback you can use the if and unless options. Note that these are just hash options - not keywords.
You can use a method name or a lambda:
class User < ActiveRecord::Base
after_commit :do_something!, if: -> { self.some_value > 2 }
after_commit :do_something!, unless: :something?
def do_something!
end
def something?
true || false
end
end
Assuming that you need to verify a user after create.
after_commit :run_sidekiq_job, on: :create
after_commit :run_sidekiq_job, on: [:create, :update] // if you want on update as well.
This will ensure that your job will run only after a commit to db.
Then define your job that has to be performed.
def run_sidekiq_job
---------------
---------------
end
Hope it helps you :)

How to monkey patch alias_attributes

I have a database with a field 'update' corresponding to updated_at.
The database is not only used by a rails application and it's not possible to migrate the field currently.
In my model, I want to make an alias on the attribute :
class Model < ActiveRecord::Base
alias_attribute 'updated_at', 'update'
...
end
But this call the update method of the model.
So i found the alias_attribute definition here : github code
I want to monkey patch this for something like :
def self.alias_attribute(new_name, old_name)
module_eval <<-STR, __FILE__, __LINE__ + 1
def #{new_name}; self.attributes[#{old_name}]; end
def #{new_name}?; self.attributes[#{old_name}?; end
def #{new_name}=(v); self.attributes[#{old_name} = v; end
STR
end
How can i make this work?

Is there a way to override the << operator in Ruby?

I'm trying to do something like:
account.users << User.new
But I need users to be a method on an account. So I've tried things like:
def users<<(obj)
But I've had no luck with that. Is this even possible to do in Ruby? I would assume so because the ActiveRecord relationships seem to work this way in Rails.
Check this answer: Rails: Overriding ActiveRecord association method
[this code is completely from the other answer, here for future searchers]
has_many :tags, :through => :taggings, :order => :name do
def << (value)
"overriden" #your code here
end
end
It seems like you might not be describing your actual problem, but to answer your question -- yes you can override the << operator:
class Foo
def <<(x)
puts "hi! #{x}"
end
end
f = Foo.new
=> #<Foo:0x00000009b389f0>
> f << "there"
hi! there
I assume you have a model like this:
class Account < ActiveRecord::Base
has_and_belongs_to_many :users
end
To override Account#users<<, you need to define it in a block that you pass to has_and_belongs_to_many:
class Account < ActiveRecord::Base
has_and_belongs_to_many :users do
def <<(user)
# ...
end
end
end
You can access the appropriate Account object by referring to proxy_association.owner:
def <<(user)
account = proxy_association.owner
end
To call the original Account#users<<, call Account#users.concat:
def <<(user)
account = proxy_association.owner
# user = do_something(user)
account.users.concat(user)
end
For more details, see this page: Association extensions - ActiveRecord
In this case it's the << of you class of you User. So can be an Array or a AssociationProxy.
The must simplest is create a new method to do what you want.
You can override the method by instance instead.
account.users.instance_eval do
def <<(x)
put 'add'
end
end
account.users << User.new
# add
But you need do that all the time before you add by <<
users would return an object that has overridden << operator like Array, IO, String, or any type you create. You override like this:
class SomeType
def <<(obj)
puts "Appending #{obj}"
end
end
If you are trying to perform an action upon adding an User to the users collection, you can use association callbacks instead of over-riding <<(as there are many ways to add an object to an association).
class Account
has_many :users, :after_add => :on_user_add
def on_user_add(user)
p "Added user : #{user.name} to the account: #{name}"
end
end

How to modify a record before saving on Ruby on Rails

Looking for a way to either:
Change one of the fields of a new record (namely - force it to lower-case) before saving it to a RoR db.
I've tried:
before_create do |term|
term.myfield.downcase!
end
but this gives an error of:
undefined method `before_create' for RowsController:Class
or
Check that the field is all lowercase, and if not, raise an error message, and not create the record.
tried:
before_filter :check_lowcase, :only => [:new]
def check_lowcase
if (Term.new =~ /[^a-z]+/)
flash[:notice] = "Sorry, must use lowercase"
redirect_to terms_path
end
end
this seems to just be ignored....
You need to do it on your model, not your controller:
class YourModel < ActiveRecord::Base
before_create :downcase_stuff
private
def downcase_stuff
self.myfield.downcase!
end
end
before_create :lower_case_fields
def lower_case_fields
self.myfield.downcase!
end
before_save { |classname| classname.myfield = myfield.downcase }

Resources