Rails.Share variable between 2 models(Validation) - ruby-on-rails

So having some troubles how to resolve my puzzle.
I'm having 2 models
1) Mode1.rb
class Model1 < ActiveRecord::Base
set_table_name "Model1"
set_sequence_name "Model1"
module Validate
def validate_discount
errors.add(:discount, "#blank") if discount.blank?
end
end
end
2) Model2.rb
class Model2 < ActiveRecord::Base
include Model1::Validate
validate :validate_discount
end
What i need? The trouble is that on submit page operating model2, so i need to execute validation from there to get proper error display, but as discount exists only in model1 i need to pass it to model2
The error what i get is now:
undefined local variable or method `discount' for #<Model2:0x12c952f8>
Might i need somehow pass it through the controller? I mean smth like this:
Model2.new
Model2["discount"] = 20
Model2.discount
I'm stuck now.

I think you can use attr_accessor for this purpose. With that you can set and get value of discount attribute.
class Model2 < ActiveRecord::Base
include Model1::Validate
attr_accessor :discount
validate :validate_discount
end
With this you can call:
model2 = Model2.new
model2.discount = params[:discount] #or whatever you set value for discount
And then validate it with your Model1::Validate module.

Related

Create method that returns associated object's attribute value if current object's attribute is nil (jsonb type)

I have the following two classes:
class Account < ActiveRecord::Base
# columns
# ...
# features :jsonb
has_one :plan
end
class Plan < ActiveRecord::Base
# columns
# ...
# features :jsonb
end
And will be calling features like this:
account.features # being account is an instance of Account
# or
account.features[:feature_key]
The thing is I want account to look for features or features[:feature_key] within itself, and if that is nil or empty it should pick the value from the associated Plan object.
Something like:
features.present? ? features : plan.features
# and
features[:feature_key].present ? features[:feature_key] : plan.features[:feature_key]
But in a proper method within Account class
Well, it's not best practice, but you can override the getter with a custom method. In your case:
def features
return features[:feature_key] if features[:feature_key].present?
return plan&.features[:feature_key] if plan&.features[:feature_key].present?
end
Not sure that's the logic you need, but that'll work for an override if you put it in the Account model. The preferred way to do it would be to name the method something else and call that method so you can still access the unmodified attribute if need be.
Not sure I completely understand but given your comment under the other answer I am assuming you are looking for something like:
class Account < ActiveRecord::Base
def feature_info(key)
return plan.features[key] unless features.present? && features[key].present?
features[key]
end
end
Then called as
account = Account.first
account.feature_info(:feature_key)
This might be cleaner though
class Account < ActiveRecord::Base
def features
read_attribute(:features) || {}
end
def feature_info(key)
return plan.features[key] unless features[key].present?
features[key]
end
end

Rails — How do I see old and new model values from an after_save callback?

Let's say I have the following models:
class V < ActiveRecord::Base
belongs_to :p
after_save :after_save_v
...
end
class P < ActiveRecord::Base
has_many :vs
# Has an attribute called "score" (defined in the DB table)
...
end
Creating a V increases v.p.score by 1.
Now in my after_save_v, I would like to track the old value of p.score. How can I do this?
Using a p.score_was does not work.
class V < ActiveRecord::Base
def after_save_v
puts p.score_was # => 1
puts p.score # => 1
end
end
What am I missing here? Or is this something that is not possible from the after_save callback?
In after_save, if you use the changes method, it returns the hash of all the changes on your object. You can specify which attribute's changes you need. This will be an array. [old-value, new-value]
In your Example, it can be self.p.changes["score"]. This returns an array [0, 1], where 0 is the old value, and 1 is the new value

Rails 4 with Devise: update_attributes doesn't work

I have a problem updating current_user attributes.
Here is my controller:
class BuildingsController < ApplicationController
def create
# ---- many_things
#current_user.user_info.money = 1000
current_user.user_info.update_attributes!(money: 100)
#current_user.user_info.money = 1000 --- why not 100?
# ---- many_things
end
end
And here is my model:
class User < ActiveRecord::Base
has_one :user_info
before_create :initialize_user
def initialize_user
self.create_user_info!(money:1000,level:1)
end
end
UserInfo model is blank:
class UserInfo < ActiveRecord::Base
end
update_attributes doesn't throw any exception, it seems that it doesn't save the record.
Any idea?
Thanks!
Are you trying to set money to $100 or $1,000? If looks like you are setting and resetting the money attribute at the same time.
When you call the code block in create if sets the value to $100. After that, in the model, you have an after callback :after_create that runs initialize_user, which sets the value of money to $1,000. Perhaps you want a before callback on create instead?

Attribute that indicates is activerecord model in saving state?

I need to know if model is in saving state (between before_validate and after_save).
class ModelA < ActiveRecord::Base
before_save: do_before
def do_before
modelb.create(:attr => 123, :ref => self)
end
end
class ModelB < ActiveRecord::Base
before_create: do_before
def do_before
self.ref.my_attr = 321
self.ref.save! unless self.ref.is_saving?
end
end
I need "is_saving?" attribute for every model instance in my project. What is the best way to implement that?
Can't you rather set an instance variable in between :before_save and :after_save by using ActiveRecord's :around_save callback and then yield your save? Anyway, the question is not too clear to me. What is the purpose of is_saving? method?

How do I initialize attributes when I instantiate objects in Rails?

Clients have many Invoices. Invoices have a number attribute that I want to initialize by incrementing the client's previous invoice number.
For example:
#client = Client.find(1)
#client.last_invoice_number
> 14
#invoice = #client.invoices.build
#invoice.number
> 15
I want to get this functionality into my Invoice model, but I'm not sure how to. Here's what I'm imagining the code to be like:
class Invoice < ActiveRecord::Base
...
def initialize(attributes = {})
client = Client.find(attributes[:client_id])
attributes[:number] = client.last_invoice_number + 1
client.update_attributes(:last_invoice_number => client.last_invoice_number + 1)
end
end
However, attributes[:client_id] isn't set when I call #client.invoices.build.
How and when is the invoice's client_id initialized, and when can I use it to initialize the invoice's number? Can I get this logic into the model, or will I have to put it in the controller?
Generate a migration that adds invoices_number column to users table. Then in Invoice model write this:
class Invoice < ActiveRecord::Base
belongs_to :user, :counter_cache => true
...
end
This will automatically increase invoices_count attribute for user once the invoice is created.
how about this:
class Invoice < ActiveRecord::Base
...
def initialize(attributes = {})
super
self.number = self.client.invoices.size + 1 unless self.client.nil?
end
end
Here is some useful discussion on after_initialize per Jonathan R. Wallace's comment above:
http://blog.dalethatcher.com/2008/03/rails-dont-override-initialize-on.html
first of all, you don't need to use the attributes collection, you can just do self.client_id. Better yet, as long as you have a belongs_to :client in your Invoice, you could just do self.client.last_invoice_number. Lastly, you almost always want to raise an exception if an update or create fails, so get used to using update_attributes!, which is a better default choice. (if you have any questions about those points, ask, and I'll go into more detail)
Now that that is out of the way, you ran into a bit of a gotcha with ActiveRecord, initializer methods are almost never the right choice. AR gives you a bunch of methods to hook into whatever point of the lifecycle you need to. These are
after_create
after_destroy
after_save
after_update
after_validation
after_validation_on_create
after_validation_on_update
before_create
before_destroy
before_save
before_update
before_validation
before_validation_on_create
before_validation_on_update
What you probably want is to hook into before_create. Something like this
def before_create
self.number ||= self.client.last_invoice_number + 1 unless self.client
end
What that will do is it will hit up the database for your client, get the last invoice number, increment it by one, and set it as its new number, but only if you haven't already set a number (||= will assign, but only if the left side is nil), and only if you have set a client (or client_id) before the save.

Resources