User to mark (favorite-like) another Model in Ruby on Rails - 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)

Related

Ruby on rails objects associations

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 :)

Rails: How can I put the sections together to form a book automatically?

I'm a freshman to learn Rails and working on my first project about "online book writing".
I've already made the MVC of user,book and section. Association like this:
class User < ActiveRecord::Base
has_many :sections , dependent: :destroy
end
class Section < ActiveRecord::Base
belongs_to :book
belongs_to :user
end
class Book < ActiveRecord::Base
has_many :sections
end
So I made a relationship between user and section,but no relationship between user and book. for example,user.1 write section.1 of book.1 ,the user.2 write section.2 of book.1.
Then I want to use all the sections of book.1 which written by different users,to form a book.1 automatically. (connect the text field directly in one text field,book_content is sum of section_content)
What should I do in the views or models?
You'll have to add an index field to manage the order of the sections. But for now you can do this:
<%= render book.sections %>
And then you'll need to define a partial in sections/_section.html.(erb.haml.whatever)

How to create an association that sets join table attributes automatically?

I am totally confused about how I should go about "the rails way" of effectively using my associations.
Here is an example model configuration from a Rails 4 app:
class Film < ActiveRecord::Base
# A movie, documentary, animated short, etc
has_many :roleships
has_many :participants, :through => :roleships
has_many :roles, :through => :roleships
# has_many :writers........ ?
end
class Participant < ActiveRecord::Base
# A human involved in making a movie
has_many :roleships
end
class Role < ActiveRecord::Base
# A person's role in a film. i.e. "Writer", "Actor", "Extra" etc
has_many :roleships
end
class Roleship < ActiveRecord::Base
# The join for connecting different people
# to the different roles they have had in
# different films
belongs_to :participant
belongs_to :film
belongs_to :role
end
Given the above model configuration, the code I wish I had would allow me to add writers directly to a film and in the end have the join setup correctly.
So for example, I'd love to be able to do something like this:
## The Code I WISH I Had
Film.create!(name: "Some film", writers: [Participant.first])
I'm not sure if I'm going about thinking about this totally wrong but it seems impossible. What is the right way to accomplish this? Nested resources? A custom setter + scope? Something else? Virtual attributes? thank you!
I created a sample app based on your question.
https://github.com/szines/hodor_filmdb
I think useful to setup in Participant and in Role model a through association as well, but without this will work. It depends how would you like to use later this database. Without through this query wouldn't work: Participant.find(1).films
class Participant < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
class Role < ActiveRecord::Base
has_many :roleships
has_many :films, through: :roleships
end
Don't forget to give permit for extra fields (strong_parameters) in your films_controller.rb
def film_params
params.require(:film).permit(:title, :participant_ids, :role_ids)
end
What is strange, that if you create a new film with a participant and a role, two records will be created in the join table.
Update:
You can create a kind of virtual attribute in your model. For example:
def writers=(participant)
#writer_role = Role.find(1)
self.roles << #writer_role
self.participants << participant
end
and you can use: Film.create(title: 'The Movie', writers: [Participant.first])
If you had a normal has_and_belongs_to_many relationship i.e. beween a film and a participant, then you can create a film together with your examples.
As your joining model is more complex, you have to build the roleships separately:
writer= Roleship.create(
participant: Participant.find_by_name('Spielberg'),
role: Role.find_by_name('Director')
)
main_actor= Roleship.create(
participant: Participant.find_by_name('Willis'),
role: Role.find_by_name('Actor')
)
Film.create!(name: "Some film", roleships: [writer, main_actor])
for that, all attributes you use to build roleships and films must be mass assignable, so in a Rails 3.2 you would have to write:
class Roleship < ActiveRecord::Base
attr_accessible :participant, :role
...
end
class Film < ActiveRecord::Base
attr_accessible :name, :roleships
...
end
If you want to user roleship_ids, you have to write
class Film < ActiveRecord::Base
attr_accessible :name, :roleship_ids
...
end
Addendum:
Of cause you could write a setter method
class Film ...
def writers=(part_ids)
writer_role=Role.find_by_name('Writer')
# skiped code to delete existing writers
part_ids.each do |part_id|
self.roleships << Roleship.new(role: writer_role, participant_id: part_id)
end
end
end
but that makes your code depending on the data in your DB (contents of table roles) which is a bad idea.

Ruby on Rails models relationship

need some advice.
I'm doing a project on RoR, and do not sure what relationship between the models should I use. I've got three models - Users, Boards and Messages.
The beginning is pretty simple:
User has one Wall, and it belongs to the User, so I guess this should be:
class User < ActiveRecord::Base
has_one :board
end
class Board < ActiveRecord::Base
belongs_to :user
end
The last model is Messages and here comes my problem. Message belongs to User cause he writes it, but it also belongs to a Wall cause he writes it on a wall (and it can be Wall that belongs to other user).
I used the simple solution:
class Theme < ActiveRecord::Base
belongs_to :board
belongs_to :user
end
class User < ActiveRecord::Base
has_one :board
has_many :themes
end
class Board < ActiveRecord::Base
belongs_to :user
has_many :themes
end
But I not satisfy with it, and feel that it isn't perfect. I'm looking for a solution that will let me write thinks like:
user.themes.create(:board => #board)
(now it doesn't fill user_id field)
I hope that isn't a hard task for those who more experienced than me in Ruby on Rails model. I'll appreciate good advices, thanks.
For normal you use some authentification gem like devise. Then you have the current_user variable which includes the object of the user that is currently calling the action.
Then when a user creates the Topic you add one simple line to the controller to set the user:
#theme.user = current_user
You should also use a gem like cancan to manage the authorisation in a cenral file. Youl find a railscast here:
http://railscasts.com/episodes/192-authorization-with-cancan

Making record available only to certain models in Rails 3

I have a weird design question. I have a model called Article, which has a bunch of attributes. I also have an article search which does something like this:
Article.project_active.pending.search(params)
where search builds a query based on certain params. I'd like to be able to limit results based on a user, that is, to have some articles have only a subset of users which can see them.
For instance, I have an article A that I assign to writers 1,2,3,4. I want them to be able to see A, but if User 5 searches, I don't want that user to see. Also, I'd like to be able to assign some articles to ALL users.
Not sure if that was clear, but I'm looking for the best way to do this. Should I just store a serialized array with a list of user_id's and have -1 in there if it's available to All?
Thanks!
I would create a join table between Users and Articles called view_permissions to indicate that a user has permission to view a specific article.
class ViewPermission
belongs_to :article
belongs_to :user
end
class User
has_many :view_permissions
end
class Article
has_many :view_permissions
end
For example, if you wanted User 1 to be able to view Article 3 you would do the following:
ViewPermission.create(:user_id => 1, :article_id => 3)
You could then scope your articles based on the view permissions and a user:
class Article
scope :viewable_by, lambda{ |user| joins(:view_permissions).where('view_permissions.user_id = ?', user.id) }
end
To search for articles viewable by a specific user, say with id 1, you could do this:
Article.viewable_by(User.find(1)).project_active.pending.search(params)
Finally, if you want to assign an article to all users, you should add an viewable_by_all boolean attribute to articles table that when set to true allows an article to be viewable by all users. Then modify your scope to take that into account:
class Article
scope :viewable_by, lambda{ |user|
joins('LEFT JOIN view_permissions on view_permissions.article_id = articles.id')
.where('articles.viewable_by_all = true OR view_permissions.user_id = ?', user.id)
.group('articles.id')
}
end
If an Article can be assigned to multiple Writers and a Writer can be assigned to multiple Articles, I would create an Assignment model:
class Assignment < AR::Base
belongs_to :writer
belongs_to :article
end
Then you can use has_many :through:
class Article < AR::Base
has_many :assignments
has_many :writers, :through => :assignments
end
class Writer < AR::Base
has_many :assignments
has_many :articles, :through => :assignments
end

Resources