Trouble on accessing the id value for a Tableless models - ruby-on-rails

I am using Ruby on Rails 3 and I followed the Tableless models in Rails istructions in order to apply that to a my model Account.
All works, but if I do
#test = account.id
a debug of #test results in a nil value and seems not accessible at all.
In a comment of this question #Wukerplank said:
You are right. I suppose id has a special status in ActiveRecord. I think it would only be set after the record is persisted in some database.
How can I retrive\access the id attribute value?
UPDATED
Trying and re-trying I discovered that a possible solution is to make all attributes 'attr_accessible' (if I make just the 'id', I get all other value 'nil'), but I think it is a very dangerous solution.
Another solution is to create a new class attribute that acts as the id, but why I have to do that if I have already the id?!

Why don't you use ActiveModel instead? Check this screencast.

Related

How to choose a continent by the country?

I use Ruby on Rails 5.2 and mongoid 7.0
I need to choose a continent by the country
I understand that it should look something like this:
class Place
field :country, type: String
field :continent, type: String
after_save :update_continent
def update_continent
cont = self.country
case cont
when 'United States', 'Grenada'
'NA'
when 'Netherlands', 'Spain'
'EU'
end
self.continent = cont
end
end
Since you indicated you are using Mongoid:
Each Mongoid model class must include Mongoid::Document, per the documentation in https://docs.mongodb.com/mongoid/master/tutorials/mongoid-documents/.
after_save callbacks are normally used for things like creating external jobs, not for setting attributes, because the attribute changes won't be persisted (as the model was already saved). Usually attribute changes are done in before_validation or before_save callbacks. See https://guides.rubyonrails.org/active_record_callbacks.html for the list of available callbacks.
As pointed out by Toby, the case statement is not correctly used. Its result should be assigned like this:
.
def update_continent
self.continent = case self.country
when 'United States', 'Grenada'
'NA'
when 'Netherlands', 'Spain'
'EU'
end
end
You haven't given enough context to be able to answer your question, but since you just want to be pointed in the right direction, and since you seem to be new here I'm happy to give you some pointers.
You're class uses the after_save method as if it is an ActiveRecord Model, but without extending or including anything it's just a Plain Old Ruby Object. To make the after_save callback work you need to at least extend ActiveModel::Callbacks but probably you want to make it a full ActiveRecord Model. To do that in Rails 4 you subclass ActiveRecord::Base and in rails 6 you subclass ApplicationRecord But I don't actually know how it's done in Rails 5.
If you have a normal database in the back end as is usual for rails you don't need to declare the fields, it automatically gets them from the equivalent table in the database (though perhaps this is not true when using Mongoid. I don't know). if you run this command in your terminal in your app base directory: rails generate model Place country:string continent:string it will create the migration file needed to make the database table and the Model file (with whatever the correct superclass is) and you wont need to do all the boilerplate stuff yourself.
You have a variable named cont and you assign a country to it. This will get very confusing given that you also have a separate concept of "continent" Better to not abbreviate your variable names and choose sensible naming.
You're not using the case statement correctly. The output of the statement doesn't automatically get assigned to the the variable you're switching on. You need to read up on Ruby syntax.
Overall I suspect in the long run you would do well to have separate models for Continent and Country. With a Continent having many countries and a country belonging to a continent. Rails is a framework that makes that sort of thing very easy to do and manage. You probably need to read some more and look at examples and videos about the basics of Ruby on Rails.
I highly recommend The Rails Tutorial by Hartl. It's free online. Working through that or an equivalent should give you a much better understanding of how Rails is equipped to handle your situation and how to best utilise it to get the outcome you need. This was indispensable for me when I was first starting out with Rails.

Rails - ActiveModel::Serializer virtual attribute error

I'm using the active_model_serializers gem for a RoR API. Versions:
Rails: 4.2.8
Ruby: 2.2.5
active_model_serializers: 0.10.0
I'm using a virtual attribute in a serializer. I get it by using a sub query when I retrieve the objects from the database.
You can find the code here: Github Gist
This is the error I'm getting:
undefined method 'number_of_reservations' for DiscountSchedule...
This field isn't defined in the table nor in the model (attr_accessor)
I'm not sure why it doesn't work, I have a very similar serializer and it's working OK.
Any help will be appreciated.
EDIT:
I have another serializer where the virtual/calculated field is working OK. My guess on this is that since AR is making a bunch of LEFT OUTER JOINS and the SELECT list of the query is very big at some point something is getting broke.
The link won't work for me as I don't have access at my work place, however, from the error I can recommend you to check if you have defined the attributes like this in your serializer attributes :number_of_reservations and have an action in the serializer that says
def number_of_reservations
// Your logic goes here.
end
I suspect this question has to be about ActiveRecord, rather than AMS. You're trying to use select and alias to collect some computed (aggregate) attribute along with objects themselves. This, unfortunately, won't work in ActiveRecord, at least not in versions below 4.2.X. And this is why you're observing this behavior, there is no number_of_reservations in your models.
To see what's going on, try to inspect #objects here: https://gist.github.com/LuisDeHaro/ebf92781b449aa1ee7b85f8f552dd672#file-some_controller-rb-L17
Indeed: the issue was by the big amount of LEFT JOINS that the includes(:table_name) is generating. The serializer then does not know what to do.
I found a monkey-patch gem that works for AR (Rails 4 & 5) that fix this.
https://github.com/alekseyl/rails_select_on_includes
So, the virtual field number_of_reservations is picked up by the serializer like a charm.
And, you might be wondering: why do you want to retrieve a field that is not in the table definition in the database. A: well, in some scenarios you will need a calculated field for EVERY row you are retrieving. A SQL sub query is one of the most efficient ways to do so.
It's working now for me.

Rails 4.2.5.1 ActiveRecord : When/Why does one use self[:attr]= in place of write_attribute()

Within an RoR ActiveRecord class what are the pros and cons of using self[:attribute]=value as opposed to write_attribute( :attribute => value ). Is it simply a matter of style? Or is there some deeper reason to prefer one over the other?
If you look at the source on github, you can see that internally it uses the private method write_attribute_with_type_cast:
https://github.com/rails/rails/blob/8fdd4bf761b280126e52a212eed187391bdedbb3/activerecord/lib/active_record/attribute_methods/write.rb#L55
This gives you one advantage over just calling self[:attribute]=value yourself in that if you're setting id, or what you think should be id, the method will handle finding the correct attribute name of the primary key for your model.
Finally, through write_from_user, rails actually calls self[:attribute] = value for you, albeit with different names for things:
https://github.com/rails/rails/blob/8fdd4bf761b280126e52a212eed187391bdedbb3/activerecord/lib/active_record/attribute_set.rb#L38
Beyond the auto-correction of :id to :custom_primary_key if your model isn't using the standard id column as its primary key, there is no functional advantage to using write_attribute over self[:attribute]=value.

Preventing Mongoid 4.0.0 model field coercion of id => _id

I'm using Mongoid 4.0.0 with Rails 4. My models map tables in another application, and I have no control over the field names.
One of the models has a field named id, which is getting coerced into Mongo's _id field. For example, when I insert a document with an id value of "something" I get
{_id:"something", id:null}
instead of
{_id:ObjectId("<hexstring>"),id:"something"}
Is there any way to avoid this coercion, make Mongoid not conflate the two fields, and leave my id field alone?
As I said, renaming the id field is not an option.
Thanks!
[edited]
This is definitely not a MongoDB issue. It must be in Moped or (my guess) Mongoid.
I've tried changing the params key from :id to :_rid but this is still happening. I'm going to check out aliases, but from my first pass I don't think they're going to help -- they appear to go the wrong way.
This appears to be hardcoded into Moingoid and a pervasive assumption throughout. It's annoying enough, though, that I might come up with a patch to allow users to override the key field on a per-model basis.
Oh well.

Clone Sequel Model

I have a Model representing a Pricing Table with lots of entries and i want to offer the possibility to create a new Pricing with values from an existing entry.
Does anyone know how to do this, then Sequel is in use?
I tried dup and clone but in both cases the id is still there from the existing Model and thus will update the existing entry.
If i try to set the id by hand i get following error:
Sequel::InvalidValue: nil/NULL is not allowed for the id column
So i need a to find a Way to create a Model which is new but has prefilled values without having them to set in the code by hand.
Any ideas?
found it:
new_pricing = Pricing.new(oldprice.attributes.tap{|attr| attr.delete("id")})
i get the attributes from the old model as hash, then remove the id and create a new Model by passing the attributes except the id.
The model.attributes solution didn't work for me. Sequel models have to_hash which is roughly equivalent, but to_hash doesn't return deserialized values. If you are using serializers (for jsonb fields, etc), simply passing a to_hash to new will fail because the values are not yet deserialized.
Here's the solution that works for me:
user = User.find(id: 123)
# freeze to avoid accidentally modifying the original user
user.freeze
# duplicate the record, deserialize values, and delete the primary key
# deserialization is useful if your model is using jsonb fields
record_copy = user.to_hash.merge(user.deserialized_values)
record_copy.delete(:id)
duplicate_user = User.new
# pass the has via `set_all` to avoid initialization callbacks
duplicate_user.set_all(record_copy)
# ... other important callbacks
duplicate_user.save

Resources