Update attribute with array insertion - ruby-on-rails

I have an Impression model with an actions attribute that is serialized for Array.
class Impression < ActiveRecord::Base
serialize :actions, Array
end
Normal attribute updating procedure:
impression.update(some_attr: "new_value")
Normal array insertion procedure:
impression.actions << "new_value"
impression.save
Is there a way to insert a new value to an array attribute that works like the .update method, i.e. in one single expression?

No, this is not possible when using the ActiveRecord serialization feature because the serialization/deserialization must be controller by ActiveRecord and update performs a direct SQL update call on the database.
Some non-relational database (such as MongoDB) offers this feature because they are designed in order to do so. PostgreSQL provides a Hash extension you can install that would allow you to perform direct operations on the serialized field.
In all the other cases, you could potentially update the field directly, but I don't encourage you to do so. There is a potential risk to write corrupted data.
Instead, I suggest you to create a custom method that performs both the push and save. In any case, this is a good idea because you are exposing a custom API outside the model instead of coupling your model to the ActiveRecord architecture.
class Impression < ActiveRecord::Base
serialize :actions, Array
def add_action(action)
self.actions << action
self.save
end
end
The you can use
impression = ...
impression.add_action "new_value"

Related

access ActiveRecord Has Many associations

I have a model which has a lot of associations. What I need to do is to check whether one of those associations is not present or all of them are set correctly.
To do so, I created an array which includes all of those needs-to-be-checked fields. And created a loop through each element, but the case is that I can not access has_many related attributes with read_attribute method. It basically returns nil whenever I try to access has many associated fields.
What I am trying to do, I can access all related objects via car.drivers but I can not access the same object with car.read_attribute(:drivers) (because some of them are attributes and some are relations)
I think it's the behavior of read_attribute, so what should I use to access any (attribute or relation) on ActiveRecord object?
Regarding to the comments, it looks like no one understand what I am trying to do. I want to access the relations of one ActiveRecord object such like;
RELATIONS.each do |relation|
puts "#{relation} exists" if #object.relation.present?
end
What I do not know about this, is there any method that I can access the related objects with their string typed name. Similar to, #object.read_attribute(:attribute_name) In that way I can create a for loop, but for relations not for the attributes
To do so, I used dynamical method call. Below is an example of showing it
RELATIONS.each do |relation|
puts "#{relation} exists" unless #object.send('relation').nil?
end

What is the best practice for model fetching from external source

So I have the ActiveRecord model like this
class User < ActiveRecord::Base
has_many :posts
end
And I have Api class that fetches the attributes from web for User and returns a hash, which in turn needs some preprocessing to fit into the user model. E.g. the response from api is {response: {id:20, stars:{count:20}}} needs to be processed to user.id=20 and user.stars_count=20.
Now there are 2 ways that I can easily see to do the fecting thing. One way is add a method to Api like get_user, that does all the processing returns a new User model with filled attributes. The other is to add fetch method to User that does all the processing.
In the first case I don't like that my Api class will be filled with a lot of get_model methods like get_user, get_post, get_all_posts_for_user etc. and quickly can become unmanagable. In the second case the model is filled with a lot of preprocessing stuff methods like fetch, fetch_all_posts and looks nasty too.
So what is the best practice to deal with this problem?
Take a look at Active Resource, you don't have to do what you are doing manually.

Why is attr_accessor necessary in Rails?

I occasionally see attribute accessors/readers/writers in the code for models. Are these necessary if I want to be able to update attributes from the view / controller code?
I am a beginner so I am really talking about basic applications.
attr_accessor is a core feature of Ruby and is used to generate instance variables with getter and setter methods. Its use is never required in basic Ruby (it's a convenience).
In the case of ActiveRecord models, getters and setters are already generated by ActiveRecord for your data columns. attr_accessor is not needed or desirable.
If you have additional instance data you don't need to persist (i.e. it's not a database column), you could then use attr_accessor to save yourself a few lines of code.
The similarly-named attr_accessible — which is frequently seen in Rails code and confused with attr_accessor — is a deprecated method of controlling mass assignment within ActiveRecord models. Rails 4 doesn't support it out of the box; it has been replaced by Strong Parameters, which allows more granular control.
If you declare an attr_accessor then you can use it as a virtual attribute, which is basically an attribute on the model that isn't persisted to the database.
Example case: you declare attr_accessor :password in your User model so that you can use it as a field in a new user form. When you receive their password in the corresponding create action, you can derive a hashed_password, persist it to the database, and discard the given password (which is done automatically at the end of the request).
Generally it is a pretty good idea to decorate attr_accessor for anything on a model that is not an actual column in the SQL table. Rails 4 or not. This gives you clear understanding of what's in the model and what is persisted.
Generally, i use attr_accessor for attributes that is not in model/database, but i think it's not necessary using them.

Remote data model in Rails

i have model with validations, some methods, filters and so. Unfortunately the data are from API, so i need to overload the method which is pull the records from DB. Which method is that?
So far i have creation. Method create in active record's model is presisting new record. I just add method create to my model and it's creating records over the API. Now i want it for selecting the data.
Following code is example of what i already have (creation of records):
def create
EmployeesApi.create(self.attributes.reject{|k,v| %w(id created_at updated_at).include? k })
end
I need it as low level as possible, because it has some relations and app specific validations. Moving also the relations and surrounding logic would mean integrate two existing systems and that's not ok in this case.
Another explanation:
I want to bypass the database for data of the model, but for association let everything as it was. The model's data are stored in another app/database/system.
I'll load model's own data by API and ActiveRecord will pair/load it's associations from local DB
If EmployeeApi would be modeled after ActiveResource you could possibly enhance it with ActiveModel. Associations might work only in one way (from ActiveRecord to ActiveResource). It is also good to consider exceptions such as API is down.
I think what you are trying to achieve is to pull some data from the DB before creating this record? or validate some DB field, maybe ensure a unique constrain or something.. Please look into the active record hooks/callbacks
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html
You can do something like this:
class Subscription < ActiveRecord::Base
before_create :record_signup
private
def record_signup
self.signed_up_on = Date.today
end
end

next available record id

#user = User.new
#user.id returns nil but i need to know it before i save. Is it possible ?
YES you can!
I had the same question and investigated the docs.
The ability to solve this question is very related to your database type in fact.
Oracle and Postgresql do have useful functions to easily solve this.
For MySQL(oracle) or SkySQL(open-source) it seems more complicated (but still possible). I would recommend you avoid using these (MySQL/SkySQL) databases if you need advanced database tools.
First you must try to avoid this situation as much as possible in your application design, as it is dangerous to play with IDs before they get saved.
There may be situation where you don't have any other choice:
For instance when two tables are referencing themselves and for security reason you don't allow DELETE or UPDATE on these tables.
When this is the case, you can use the (PostgreSQL, Oracle) database nextval function to generate the next ID number without actually inserting a new record.
Use it in conjunction with the find_by_sql rails method.
To do this with postgreSQL and Rails for instance, choose one of your rails models and add a class method (not an instance method!).
This is possible with the "self" word at the beginning of the method name.
self tells Ruby that this method is usable only by the class, not by its instance variables (the objects created with 'new').
My Rails model:
class MyToy < ActiveRecord::Base
...
def self.my_next_id_sequence
self.find_by_sql "SELECT nextval('my_toys_id_seq') AS my_next_id"
end
end
When you generate a table with a Rails migration, by default Rails automatically creates a column called id and sets it as the primary key's table. To ensure that you don't get any "duplicate primary key error", Rails automatically creates a sequence inside the database and applies it to the id column. For each new record (row) you insert in your table, the database will calculate by itself what will be the next id for your new record.
Rails names this sequence automatically with the table name append with "_id_seq".
The PostgreSQL nextval function must be applied to this sequence as explained here.
Now about find_by_sql, as explained here, it will create an array containing new objects instances of your class. Each of those objects will contain all the columns the SQL statement generates. Those columns will appear in each new object instance under the form of attributes. Even if those attributes don't exist in your class model !
As you wisely realized, our nextval function will only return a single value.
So find_by_sql will create an array containing a single object instance with a single attribute.
To make it easy to read the value of this very attribute, we will name the resulting SQL column with "my_next_id", so our attribute will have the same name.
So that's it. We can use our new method:
my_resulting_array = MyToy.my_next_id_sequence
my_toy_object = my_resulting_array[0]
my_next_id_value = my_toy_object.my_next_id
And use it to solve our dead lock situation :
my_dog = DogModel.create(:name => 'Dogy', :toy_id => my_next_id_value)
a_dog_toy = MyToy.new(:my_dog_id => my_dog.id)
a_dog_toy.id = my_next_id_value
a_dog_toy.save
Be aware that if you don't use your my_next_id_value this id number will be lost forever. (I mean, it won't be used by any record in the future).
The database doesn't wait on you to use it. If somewhere at any time, your application needs to insert a new record in your my_table_example (maybe at the same time as we are playing with my_next_id_sequence), the database will always assign an id number to this new record immediately following the one you generated with my_next_id_sequence, considering that your my_next_id_value is reserved.
This may lead to situations where the records in your my_table_example don't appear to be sorted by the time they were created.
No, you can't get the ID before saving. The ID number comes from the database but the database won't assign the ID until you call save. All this is assuming that you're using ActiveRecord of course.
I had a similar situation. I called the sequence using find_by_sql on my model which returns the model array. I got the id from the first object of the arry. something like below.
Class User < ActiveRecord::Base
set_primary_key 'user_id'
alias user_id= id=
def self.get_sequence_id
self.find_by_sql "select TEST_USER_ID_SEQ.nextval as contact_id from dual"
end
end
and on the class on which you reference the user model,
#users = User.get_sequence_id
user = users[0]
Normally the ID is filled from a database sequence automatically.
In rails you can use the after_create event, which gives you access to the object just after it has been saved (and thus it has the ID). This would cover most cases.
When using Oracle i had the case where I wanted to create the ID ourselves (and not use a sequence), and in this post i provide the details how i did that. In short the code:
# a small patch as proposed by the author of OracleEnhancedAdapter: http://blog.rayapps.com/2008/05/13/activerecord-oracle-enhanced-adapter/#comment-240
# if a ActiveRecord model has a sequence with name "autogenerated", the id will not be filled in from any sequence
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
alias_method :orig_next_sequence_value, :next_sequence_value
def next_sequence_value(sequence_name)
if sequence_name == 'autogenerated'
# we assume id must have gotten a good value before insert!
id
else
orig_next_sequence_value(sequence_name)
end
end
end
while this solution is specific to Oracle-enhanced, i am assuming the other databases will have a similar method that you could redefine.
So, while it is definitely not advised and you want to be absolutely sure why you would not want to use an id generated by a sequence, if it is needed it is most definitely possible.
It is why I love ruby and Ruby on Rails! :)
In Oracle you can get your current sequence value with this query:
SELECT last_number FROM user_sequences where sequence_name='your_sequence_name';
So in your model class, you can put something like this:
class MyModel < ActiveRecord::Base
self.sequence_name = 'your_sequence_name'
def self.my_next_id_sequence
get_data = self.find_by_sql "SELECT last_number FROM user_sequences where sequence_name='your_sequence_name'"
get_data[0].last_number
end
end
And finally, in controller you can get this value with this:
my_sequence_number = MyModel.my_next_id_sequence
So, there is no need to get your next value by using NEXTVAL and you won't lose you ID.
What you could do is User.max(id). which will return the highest ID in the database, you could then add 1. This is not reliable, although might meet your needs.
Since Rails 5 you can simply call next_sequence_value
Note: For Oracle when self.sequence_name is set, requesting next sequence value creates side effect by incrementing sequence value

Resources