I'm using dry-struct for non-model class Payout - it's basically structured hash response from API call.
module Customer
class Payout < Dry::Struct
attribute :amount, Types::Integer
attribute :reference, Types::String
attribute :settlements, Types::Array
def fetch_settlements
CustomerApi.fetch_settlements_by_reference(platform_merchant_id, reference)
end
end
end
Is it possible to set attribute :settlements by the fetch_settlements method ? I was trying something like:
attribute :settlements, Types::Array.optional.default(feth_settlements)
but I'm getting an error of
undefined local variable or method `fetch_settlements
Related
I have a model Customer that accepts a virtual attribute opening_balance. Code looks something like this:
model Customer < ApplicationRecord
attr_accessor :opening_balance
has_one :ledger_account
after_create :open_ledger_account
def opening_balance
ledger_account.opening_balance if ledger_account.present?
end
private
def open_ledger_account
create_ledger_account!(opening_balance: self.opening_balance)
end
But the issue here is self.opening_balance is calling the method defined in the class not the value stored in attr_accessor's opening_balance attribute.
I tried one more solution:
def open_ledger_account
create_ledger_account!(opening_balance: self.read_attribute(:opening_balance))
end
But this also doesn't work.
How to read the value stored in the actual attribute? Any help would be appreciated. I am using rails 5.1.
Thanks!
attr_accessor defines a instance variable and a method to access it (read/write).
So the easy way is to write:
def open_ledger_account
create_ledger_account!(opening_balance: #opening_balance)
end
The read_attribute would only work if opening_balance was an attribute in the database on Customer.
First you have to understand that attr_accessor does not define instance variables. It just creates setter and getter methods. What attr_accessor :name does is:
class Person
def name
#name
end
def name=(value)
#name = value
end
end
Now you can access the instance variable from the outside:
p = Person.new
p.name = 'Jane'
puts p.name
And you can also access the instance variable from the inside by using the getter method instead of #name:
class Person
attr_accessor :name
def hello
"hello my name is: #{name}"
end
end
attr_accessor does not "define" a instance variable. There is no declaration of members/attributes in Ruby like in for example Java. An instance variables is declared when it is first set. Accessing an instance variable that has not been assigned a value returns nil.
So whats happing here:
class Customer < ApplicationRecord
attr_accessor :opening_balance
# ...
def opening_balance
ledger_account.opening_balance if ledger_account.present?
end
end
Is that you are overwriting the getter created by attr_accessor. If you want to access the instance variable itself just use #opening_balance.
However...
You should just use delegate instead:
class Customer < ApplicationRecord
has_one :ledger_account
delegate :opening_balance, to: :ledger_account
end
In PHP, I can set an attribute (that is not a column in database) to a model. E.g.(PHP code),
$user = new User;
$user->flag = true;
But in rails, when I set any attribute that doesn't exist in database, it will throw an error undefined method flag. There is attr_accessor method, but what will happen if I need about ten temp attributes?
but what will happen if I need about ten temp attributes?
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :flag, :other_attribute, :other_attribute2, :etc...
end
attr_accessor creates "virtual" attributes in Rails -- they don't exist in the database, but are present in the model.
As with attributes from the db, attr_accessor just creates a set of setter & getter (instance) methods in your class, which you can call & interact with when the class is initialized:
#app/models/user.rb
class User < ActiveRecord::Base
attr_accessor :flag
# getter
def flag
#flag
end
# setter
def flag=(val)
#flag = val
end
end
This is expected because it's how ActiveRecord works by design. If you need to set arbitrary attributes, then you have to use a different kind of objects.
For example, Ruby provides a library called OpenStruct that allows you to create objects where you can assign arbitrary key/values. You may want to use such library and then convert the object into a corresponding ActiveRecord instance only if/when you need to save to the database.
Don't try to model ActiveRecord to behave as you just described because it was simply not designed to behave in that way. That would be a cargo culting error from your current PHP knowledge.
As the guys explained, attr_accessor is just a quick setter and getter.
We can set our Model attr_accessor on record initializing to be a Ruby#Hash for example using ActiveRecord#After_initilize method so we get more flexibility on temporarily storing values (idea credit to this answer).
Something like:
class User < ActiveRecord::Base
attr_accessor :vars
after_initialize do |user|
self.vars = Hash.new
end
end
Now you could do:
user = User.new
#set
user.vars['flag'] = true
#get
user.vars['flag']
#> true
All that attr_accessor does is add getter and setter methods which use an instance variable, eg this
attr_accessor :flag
will add these methods:
def flag
#flag
end
def flag=(val)
#flag = val
end
You can write these methods yourself if you want, and have them do something more interesting than just storing the value in an instance var, if you want.
If you need temp attributes you can add them to the singleton object.
instance = Model.new
class << instance
attr_accessor :name
end
Is it possible / advisable to have a member of a class that is not persisted to the database for a rails model?
I want to store the last type the user selects in a session variable. Since I cant set the session variable from my model, I want to store the value in a "dummy" class member that just passes the value back to the controller.
Can you have such a class member?
Adding non-persisted attributes to a Rails model is just like any other Ruby class:
class User < ActiveRecord::Base
attr_accessor :someattr
end
me = User.new(name: 'Max', someattr: 'bar')
me.someattr # "bar"
me.someattr = 'foo'
The extended explanation:
In Ruby all instance variables are private and do not need to be defined before assignment.
attr_accessor creates a setter and getter method:
class User < ActiveRecord::Base
def someattr
#someattr
end
def someattr=(value)
#someattr = value
end
end
There is one special thing going on here; Rails takes the hash you pass to User.new and maps the values to attributes. You could simulate this behavior in a plain ruby class with something like:
class Foo
attr_accessor :bar
def initialize(hash)
hash.keys.each do |key|
setter = "#{key}=".intern
self.send(setter, hash[key]) if self.respond_to? setter
end
end
end
> Foo.new(bar: 'baz')
=> <Foo:0x0000010112aa50 #bar="baz">
Classes in Ruby can also be re-opened at any point, ActiveRecord uses this ability to "auto-magically" add getters and setters to your models based on its database columns (ActiveRecord figures out which attributes to add based on the database schema).
Yes you can, the code below allows you to set my_class_variable and inside the model reference it as #my_class_variable
class MyCLass < ActiveRecord::Base
attr_accessor :my_class_variable
def do_something_with_it
#my_class_variable + 10
end
I currently have 2 models - Issue and Responses and i'm trying to add a method on the response model so I can do issue.responses.latest
I'm currently getting undefined method 'issue_id' for #<Class:...
How can I reference the column issue_id from the responses table in my self.latest method?
class Issue < ActiveRecord::Base
has_many :responses
end
class Response < ActiveRecord::Base
belongs_to :issue
def self.latest
select([:id, :user_id, :issue_id, :response, 'MAX(created_at)'])
.where(:issue_id => self.issue_id) <!-- How to reference the issue_id column here?
.group(:issue_id)
end
end
You're referencing the issue_id column fine. The problem is that you're trying to provide a value of self.issue_id, but there is no issue_id class method of Response.
Also, a latest class method for Response won't be invoked by issue.responses.latest, since responses is an <ActiveRecord::Associations::CollectionProxy::ActiveRecord_Associations_CollectionProxy_Table:0x007f85220bd3b8>. You could, however, define an Issue instance method of latest_response.
On a related point, since responses is an Enumerable, you can do issues.responses.max_by {|response| response.created_at}
I have a model w/ a virtual attribute:
class Campaign < ActiveRecord::Base
def status
if deactivated
return "paused"
else
return "live"
end
end
end
now, in my view, when I access the attribute with campaign.status, I am getting the proper result. However, when I try to access it like this campaign[:status], I get nothing back.
Why is that?
[:status] uses the [] method in Ruby. 'def status' defines a method which shouldn't be mistaken with an ActiveRecord attribute or an virtual attribute (e.g. attr_reader or attr_accessor).
ActiveRecord adds the [] method to your class and makes all the (database) attributes accessible by object[:attr_name] AND object.attr_name(And even object.attributes[:attr_name]).
This is different from how f.e. Javascript works where obj[:method] is virtually the same as obj.method.
Edit: You should be able to use the attr_accessor if you use them for example in any form:
<%= form.input :status %>
Submitting the form will then set the instance variable #status. If you want to do anything with this before or after saving you can call an before_save or after_save hook:
class Campaign < ActiveRecord::Base
attr_accessible :status
attr_accessor :status
before_save :raise_status
def raise_status
raise #status
end
end
This will throw an error with the value submitted value for status.
Hope this helps.