Mongoid and embedding tags - ruby-on-rails

I have a few models that each have tags. Say for example, a User and a Post. Both should have embedded tags I think.
The user's embedded tags are their "favorites" and a post's tags are tags that have to do with the post.
I'm using Mongoid, and I can't figure out how to have a Tag model where I can find all the tags (These tags aren't embedded, I want a separate collection of all available tags).
When a user tries to add a tag, it is checked against Tag.all to make sure it exists. If it does, then it should be embedded in the User model.
I can't figure out how to embed Tag models in more than one model (User and Post) or how to embed Tag models as well as retrieve them like normal (I get the error: Access to the collection for Tag is not allowed since it is an embedded document, please access a collection from the root document.).
Ideas? Am I designing this totally wrong? I don't need complicated queries like "Retrieve all users who have the example tag", so I figured that I should embed to be more efficient.

Well, the first step is to make a tag a polymorphic embed.
class User
embeds_many :tags, :as => :taggable
end
class Post
embeds_many :tags, :as => :taggable
end
class Tag
embedded_in :taggable, :polymorphic => true
end
You could perhaps then have an instance of a user or a post that contains all tags available. And then instead of calling .all on the Tag class, you can call it on your instance
available_user = User.new
available_user.tags << Tag.new
available_user.tags.all

My solution is to have a Registry model that embeds_many tags, then to have an instance that has the registered tags that is checked before adding to a user or post. This is essentially the same as Ron's answer, but without overloading User.

First of all, the error you get when you try to get embedded tags is normal, that's not possible with actual MongoDB versions.
I'm not an expert, but I can suggest you a workable solution: use embedded tags for User and Post, and create a Tag collection that you'll use for indexing purposes.
When a user tries to add a tag: call Tag.all
If the tag doesn't exist: create it in both User and Tag
If it does, simply embedded it in User.
The same goes for Post.

Related

Rails and HAML - how to reference deeply nested array items individually

I'm working on someone else's site, and am trying to reference a value's ID in my HAML file, but I've tried every iteration I can think of and can't figure out how to do this. I'm trying to get the ID of a want, which belongs to a project, which belongs to a user. Here's some code from the projects model:
class Project
belongs_to :user
has_many :wants
accepts_nested_attributes_for :wants
In another view, someone's written this:
(id="project_#{ project.id }_wants")= render project.wants, :user => project.user
This returns an array of wants, which it iterates through. I was initially confused because the wants seem to have ids in some of the views, but when I tried referencing project.want[0], for example, that worked perfectly. My question is - how do I pass user(id of blah).project(id of blah).want[place blah in array] ?
Assuming you know the user ID and project ID (we'll call them user_id and project_id), you can:
User.find(user_id).projects.find(project_id).wants[index of the want you want]
or
User.find(user_id).projects.find(project_id).wants.find(wants_id)

Rails create relationship record dynamically

I have two models: Image and Lightbox. To relate the two models (has_many) I have a third model LightboxImages.
One Lightbox can have several Images and a Image can be in various Lightboxes.
A Lightbox is created dynamically (with remote js) and the user can add Images to this Lightbox dynamically too.
My first idea was to simply create a url to the create method of the LightboxImages Controller passing the Lightbox Id and Image Id as parameter.
However, this approach seemed fragile and insecure, since a user could easily simulate such behavior.
What is the best design for this situation, create relationship records dynamically?
You actually have a fourth model -- User. I'm assuming you do anyway.
User has_many :lightboxes
Lightbox has_many :lightbox_items
Image has_many :lightbox_items
LightboxItem belongs_to :lightbox
LightboxItem belongs_to :image
Lightbox has_many :images, :through => :lightbox_item
Image has_many :lightboxes, :through => :lightbox_item
LightboxItem is where you might want to store additional data like the position of the image in that specific Lightbox.
The key thing is that every Lightbox belongs to a User. With this you can then scope all of the AR calls in your controller to the current user -- thus preventing them from modifying other light boxes.
So, user carlos has lightbox id 1. User phallstrom has lightbox id 2.
In your controller, you somehow set current_user to the current user in a before filter. Then, for example in the action to get the specified light box you would do something like this:
#lightbox = current_user.lightboxes.find_by_id(params[:id].to_i)
In doing it this way if current_user is carlos, and params[:id] is 2, nothing will be returned.
Similarly, for all the other CRUD actions you'd scope it to current_user as well.

Creating new blog with tags

Im trying to setup a blog with tagging, and i've run into a problem when trying to save.
I got 3 models
blog model
has_many :blog_tags
has_many :tags, :through => :blog_tags
blog_tag model
belongs_to :blog
belongs_to :tag
tag model
[nothing]
When i post my blog form, i got an input field with a comma seperated list of tags that i would like to create in the blog_tags tabel.
I've been trying out some different stuff and ended up with this
#blog_tags = params[:blog][:tags].split(",")
#blog_tags.each do |tag|
#tag = Tag.find_by_tag(tag)
#blog.tags.push(#tag)
end
Seemed to be working besides it complained that the parent wasn't created, and in the 2nd try it gave me an error for trying to split the string "string1" which i guess is caused by not having any commas.
I really hope one of you out there can help me out here, or atleast point me in the right direction :-)
Thanks!
I'd go for a gem. Try https://github.com/mbleigh/acts-as-taggable-on for example.
I think you just need to handle the cases where params[:blog][:tags] has no commas. In this case, the whole string is one tag, so just add it.
You might also need to deal with cases like "ruby, ,rails" i.e making sure the tags aren't empty.

How to let user modifying content of pages with rails ? Query for text?

I'm developing a website with Ruby on Rails.
I want to find the better way to let users (not developers) to edit text on some pages (like the index...). (like a CMS ?)
Actually they had to get the page through FTP, to edit the text and to put the new file on the server (through FTP).
It's a very very bad practice and I wanted to know if someone has an idea to solve this problem ?
Many thanks
It would be the same as the basic Rails CRUD operations. Just make a model/controller representing page content, and an edit view for the controller. Then on the pages you want text to be editable, instead of having the content directly on the page just use a view partial.
Of course, you would probably also want to implement some type of authentication to make sure not just everyone can edit pages.
Well, one thing you could do is add a model to your database called "Content" or "Copy" which represents some text on a page. Then you could use polymorphic association to link the content/copy to your actual model. For instance, if you had a page with a list of products on it, you'd likely have a Product model in your database. You could do something like this:
class Content < ActiveRecord::Base
belongs_to :contentable, :polymorphic => true # excuse my lame naming here
# this model would need two fields to make it polymorphic:
# contentable_id <-- Integer representing the record that owns it
# contentable_type <-- String representing the kind of model (Class) that owns it
# An example would look like:
# contentable_id: 4 <--- Product ID 4 in your products table
# contentable_type: Product <--- Tells the rails app which model owns this record
# You'd also want a text field in this model where you store the page text that your
# users enter.
end
class Product < ActiveRecord::Base
has_many :contents, :as => :contentable # again forgive my naming
end
In this scenario, when the product page renders, you could call #product.contents to retrieve all the text users have entered for this product. If you don't want to use two separate models like this, you could put a text field directly on the Product model itself and have users enter text there.

Using before_create in rails3

I'm creating a tag system right now where Post has_many :tags, :through => :tag_joins
Right now when a new tag is created, a join is automatically created, connecting the tag and post the tag was created on. The problem is I'm trying to use before_create to check if a tag with the same name and user ID has already been created. If it has already been created, I'd like the join to use the original tag ID, instead of letting it create a new tag ID.
Any tips for how I can accomplish this?
Why don't you use find_or_create_by_user_id_and_tag_id() or find_or_initialize_by_.
Update
So if you want to avoid creating duplicate tags, you can just use:
#post.tags.find_or_create_by_name('tag_name')
or if you want to apply some changes before saving the new object then use
#post.tags.find_or_initialize_by_name('tag_name')
In both cases the name attribute will be set to 'tag_name' by default.
So this method will return you the tag if exists, otherwise creates it, so you can use this when you set up your join model.
Update 2
This is actually not gonna work with has_many :through, you can see a similar problem and a workaround here:
Error while using `find_or_create_by` on a `has_many` `through` association
Can't you run a private method in your model using :before_save?
If you put code like:
:before_save :method_to_check_something
...you will be able to run any manner of validation in the model without getting the controller involved (and thereby adhering to the skinny controller, fat model methodology).
This should take care of duplicate records between the Post and the Tag but not sure how your associations are set up with the User.
class Post < ActiveRecord::Base
has_many :tags, :through => :tag_joins, :uniq => true
end

Resources