Ruby on rails objects associations - ruby-on-rails

I have two objects - User and Reviews. One User can write many reviews, but one review can be written by one user. Every user has a picture. I have:
class Review < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :reviews
end
Why can't I do the following?
review.user.picture

You have to establish has_one/belongs_to associations on user/picture models and of course to have appropriate database migrations (foreign key user_id on picture table and foreign key user_id on review table).
Models look like:
class Review < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_many :reviews
has_one :picture
end
class Picture
belongs_to :user
end
To try it, first create data in rails console:
user = User.create
review = Review.create user: user
picture = Picture.create user: user
Now you can find user picture if you have only reference to review object. Exit console and run again and type:
review = Review.last
review.user.picture
It returns picture object.
More info:
http://guides.rubyonrails.org/association_basics.html#choosing-between-belongs-to-and-has-one

Based on the info you provided, I'm assuming you still need to set up a foreign key in the database. The database migration for the the reviews table should look similar to this:
class CreateReviews < ActiveRecord::Migration
def change
create_table :reviews do |t|
t.integer :user_id
t.timestamps
end
add_index :reviews, :user_id
end
end
Then you would be able to add a related user_id to a new review object. For instance, in rails console:
Review.new(user_id: 1)
Assuming that you defined picture as a column in your User table, you should be able to run review.user.picture without any issues.

Method
The bottom line is your picture method will not be present in your user model
You can either use an instance method to create some functionality in your User model, use an ActiveRecord Association, or use something like Paperclip to provide an appended object on your user object
Association
You'll need to create the correct association for your review & picture objects -
#app/models/user.rb
Class Review < ActiveRecord::Base
has_many :reviews
has_one :picture
end
#app/models/picture.rb
Class Picture < ActiveRecord::Base
belongs_to :user
end
This will allow you to call:
#review = Review.find params[:id]
#picture = #review.user.picture
A bonus tip for this is if you wanted to ensure compatibility with the law of dementer, you could use the delegate method in your user model:
#app/models/user.rb
Class User < ActiveRecord::Base
...
delegate :attribute, to: :picture, prefix: true
end
This will allow you to call the likes of:
#review.user.picture_name
Paperclip
If you're using a picture model to store images, you may wish to use the likes of Paperclip to give you the ability to use the functionality of images in your Picture model:
#app/models/user.rb
Class User < ActiveRecord::Base
...
delegate :url, to: :picture
end
#app/models/picture.rb
Class Picture < ActiveRecord::Base
has_attached_file :picture
end
These will all help you. Using this answer with Иван Бишевац's will help you profusely :)

Related

Rails 4 Associations: Help setting up database

I need some assistance with my Rails 4 associations. I have the following 4 models:
class User < ActiveRecord::Base
has_many :check_ins
has_many :weigh_ins, :through => :check_ins
has_many :repositionings, :through => :check_ins
end
class CheckIn < ActiveRecord::Base
belongs_to :user
has_one :weigh_in
has_one :repositioning
end
class Repositioning < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
class WeighIn < ActiveRecord::Base
# belongs_to :user
belongs_to :check_in
end
Question: If I am setup this way, how would I input repositionings and weigh_ins separately, but still have them linked through a single check in?
You would have to retain one of the other association's ID in order to make it work.
For example, let's say:
You have created a CheckIn.
You now add a Repositioning to that check in.
Store the ID of the repositioning object
When adding your WeighIn object, you would simply reference the correct CheckIn record: correct_checkin_record = CheckIn.where(repositioning: the_repositioning_id)
You can then add the WeighIn object to that particular record.
An alternative (and simpler) method would be to access the CheckIn directly through the User: correct_checkin_record = #user.checkin -- This would pull in the correct CheckIn every time.
I've included both options to help visualize exactly what is going on in the relation.
Do you want to have users input weigh_ins and repositionings on different pages?
Having weigh_ins and repositionings inputted separately but still be part of a single checkin is fine with that setup. Its just matter of getting the same check_in object and make the associations to that object, which can be done through the controller by passing in check_in ID params and do CheckIn.find(params[:id])

How can I establish the link between relational records in belongs_to association?

I would like to create a simple belongs_to association between User and CriminalRecord. A user has only one record and a record belongs to a user. So far I have the following in the corresponding files.
model > user.rb
class User < ActiveRecord::Base
end
models > criminal_record.rb
class CriminalRecord < ActiveRecord::Base
belongs_to :user
end
db > migrate > 20150902003211_create_criminal_records
class CreateCriminalRecords < ActiveRecord::Migration
def change
create_table :criminal_records do |t|
t.belongs_to :user, index: true
t.murderer :boolean
t.thief :boolean
t.timestamps null: false
end
end
end
I am able to create a record for User and a record for CriminalRecord. However, I am unable to accomplish something like this even though I have user_id set to the user's id in the criminal record instance.
User.last.criminal_records
Or for that matter I am unable to do any of the following
john = User.last
john.criminal_record.create(murderer:false, thief: true)
# or
record = CriminalRecord.create(murderer:false, thief: true)
john << record
I get NoMethodError
Define a has_one association in your User model:
class User < ActiveRecord::Base
has_one :criminal_record
end
And, if you already have belongs_to :user in your CriminalRecord Model and user_id column in your criminal_records table, then you are all set.
Then you will be able to do:
john = User.last
john.criminal_record
This is a very simple use case of Active Record Association. I highly recommend you to read the Official Documentation for Active Record Association to know about the available association types and hoe they can be used in Rails application following the proper convention.

Rails association in concerns

I am using concerns for my rails application. I've different kind of users so I have made a loggable.rb concern.
In my concern I have
included do
has_one :auth_info
end
because every of my user that will include the concern will have an association with auth_info table.
The problem is, what foreign keys I need to put in my auth_info table?
E.G
I've 3 kind of users:
customer
seller
visitor
If I had only customer, in my table scheme I would have put the field
id_customer
but in my case?
You can solve this with polymorphic associations (and drop the concern):
class AuthInfo < ActiveRecord::Base
belongs_to :loggable, polymorphic: true
end
class Customer < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
class Seller < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
class Visitor < ActiveRecord::Base
has_one :auth_info, as: :loggable
end
Now you can retrieve:
customer.auth_info # The related AuthInfo object
AuthInfo.first.loggable # Returns a Customer, Seller or Visitor
You can use rails g model AuthInfo loggable:references{polymorphic} to create the model, or you can create the migration for the two columns by hand. See the documentation for more details.
Since user has roles 'customer', 'seller', 'visitor'.
Add a column called role to Users table.
Add a column called user_id to auth_infos table.
class AuthInfo < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :auth_info
end
you can do
user = User.first
user.auth_info
Now you a additional logic to your concerns.

User to mark (favorite-like) another Model in Ruby on Rails

I want to implement a "Read Later" (just like Favorites) system in a Ruby on Rails app. What I want is for a User model to be able to mark a Content model to read later.
My associations between the two models are like this:
class User < ActiveRecord::Base
has_many :contents
end
-------------
class Content < ActiveRecord::Base
belongs_to :user
end
Then a Content belongs to a Category, etc, but that doesn't matter for the question so I just didn't put it there.
A User can mark a Content (that could belong to another user) and there will be a list of "marked contents (to read later)" for each user.
How could I implement this?
I've already read this question but I didn't really understand and when trying to simulate it, it didn't work.
What did you try and what didn't work?
This is pretty straight forward. Let us think through:
There is a User:
class User < ActiveRecord::Base
end
There is Content:
class Content < ActiveRecord::Base
end
A User can create Content and is he restricted to create only one content? no. A User can create as many contents as he wants. This is to say in Rails terms a User has_many contents. To put this in other words, can we say that a Content is created by a User.
class User < ActiveRecord::Base
has_many :contents
end
class Content < ActiveRecored::Base
belongs_to :user
end
Now, the content (typically created by other users) can be favorited (marked as 'Read Later') by other users. Each User can favorite (mark 'Read Later') as many contents as he wants and each Content can be favorited by many users isn't it? However, we'll have to track which User favorited which Content somewhere. The easiest would be to define another model, let us say MarkedContent, to hold this information. A has_many :through association is often used to set up a many-to-many connection with another model. So the relevant association declarations could look like this:
class User < ActiveRecord::Base
has_many :contents
has_many :marked_contents
has_many :markings, through: :marked_contents, source: :content
end
class MarkedContent < ActiveRecord::Base
belongs_to :user
belongs_to :content
end
class Content < ActiveRecord::Base
belongs_to :user
has_many :marked_contents
has_many :marked_by, through: :marked_contents, source: :user
end
Now you can do:
user.contents # to get all the content created by this user
user.marked_contents # to get all the contents marked as 'Read Later' by this user
content.user # to get the creator of this content
content.marked_by # to get all the users who have marked this content
Read more here to learn about associations.
To mark a content as a favorite, one way would be:
#user = User.first
#content = Content.last
#user.markings << #content
You can also implement a method in the User model to do this for you:
class User < ActiveRecord::Base
...
def read_later(content)
markings << content
end
end
Now, you can do:
#user.read_later(#content)

How do I perform advanced logic when a specific field is updated in Rails?

Say I have a three models that look (basically) like this:
class User < ActiveRecord::Base
has_many :projects
has_many :deliverables
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :deliverables
end
class Deliverable < ActiveRecord::Base
belongs_to :user
belongs_to :project
end
Now, say I want the following to happen: when a project is transferred from one user to another, all associated deliverables will be transferred along with it. So something like:
project = Project.find(some_criteria)
deliverables = project.deliverables
project.user_id = new_user_id
deliverables.each do |d|
d.user_id = new_user_id
end
Is there some way to automate what I just described? I could always of course just put that into a method (like transfer_user), but I would prefer for it to happen automatically whenever user_id is set to a new value.
You probably still want to implement the transfer_user method, but add a callback in your project model by doing:
class Project < ActiveRecord::Base
after_save :transfer_user, :if => "user_id_changed?"
end

Resources