I have a has_many_and_belongs_to relationship between central and coordinators.
So my mongo documents are represented as below:
central = {
_id: 1,
title: 'First Name',
coordinators: [
BSON[1],
BSON[2],
BSON[3]
]
}
coordinators = [
{
_id: 1,
name: 'Me',
centrals: [BSON[1], BSON[2]]
},
{
_id: 1,
name: 'Mateus'
centrals: [BSON[1]]
},
{
_id: 1,
name: 'Gomes'
centrals: [BSON[1]]
},
]
If I do this:
#central = Central.find(1)
#coordinator = #central.coordinators.find(1)
#coordinator.can_edit = false
It will apply to the coordinators document resulting in this:
coordinator = {
_id: 1,
name: 'Me',
centrals: [BSON[1], BSON[2]],
can_edit: false
}
But what I really want to do is apply this can_edit attribute in the relationship, like in pivot table in RDBMS:
central = {
_id: 1,
titulo: 'First Name',
coordinators: [
{
_id: 1,
name: 'Me',
can_edit: false
},
BSON[2],
BSON[3]
]
}
Only for the central with id 1 I want to aply the can_edit to false.
I have to keep the relation between a Central and Coordinator, but in some situation, I want to have an additional information about that relation, like if I would not allow a coordinator to edit some data only in central with id 1.
How can I do this using mongoid?
The solution for this was create another relation N-N:
Add on central.rb
has_and_belongs_to_many :blocked_coordenadors,
class_name: "Central",
inverse_of: :blocked_centrals
And in coordinator.rb:
has_and_belongs_to_many :blocked_centrals,
class_name: "Central",
inverse_of: :blocked_coordenadors
And to check I do this:
central.blocked_coordenadors.include? coordinator
I have two Document, Picture, Seen_Picture
simply.
Class Picture
field :likes, type: Integer
field :is_new, type: Integer
end
and
Class SeenPicture
field :picture_id, type: String
field :is_new, type: Integer
field :user_id, type: String
end
I find Picture's is_new field is 1 and not SeenPicture
ex)
Picture(id, likes, is_new) :
[
[BSON::ObjectId('53f7fb5037646412e71b0000'), 2, 1],
[BSON::ObjectId('53f2aa8b6b6169e2eb010000'), 10, 1]
]
SeenPicture(id, picture_id, is_new, user_id) :
[
[BSON::ObjectId('53fbe7066b61694eb5000000'), '53f7fb5037646412e71b0000', 1, '53f2aa8b6b6169e2eb010000']
]
=> nonPicture is BSON::ObjectId('53f2aa8b6b6169e2eb010000') Picture's last document
when I use mysql
(Picture.all.pluck(:id) - SeenPicture.all.pluck(:id)).sample(1).first
In mongodb, how find nonSeen Picture Documents.
I have mongodb document like this and want to remove one element form unpublished array
which is in resource_well
{
"_id": ObjectId("4fa77c808d0a6c287d00000a"),
"production_status": "Unscheduled",
"projected_air_date": null,
"published": false,
"resource_well": {
"published": [
],
"unpublished": {
"0": ObjectId("4fa795038d0a6c327e00000e"),
"1": ObjectId("4fa795318d0a6c327e00000f"),
"2": ObjectId("4fa795508d0a6c327e000011"),
"3": ObjectId("4fa796f48d0a6c327e000013")
}
},
"segment_status": "Draft",
}
Code in controller
segment = Segment.find(params[:segment_id])
# segment.resource_well[params['resource_well_type']].class => Array
# segment.resource_well[params['resource_well_type']].inspect => [BSON::ObjectId('4fa795038d0a6c327e00000e'), BSON::ObjectId('4fa795318d0a6c327e00000f'), BSON::ObjectId('4fa795508d0a6c327e000011'), BSON::ObjectId('4fa796f48d0a6c327e000013')]
segment.resource_well[params['resource_well_type']].delete(params[:asset_ids])
segment.save
Not able remove an element form array
Your ODM (or ORM) provides associations for you so that you can take advantage of having the ODM manage the links for you. So you should use the associations, otherwise you risk hanging yourself. Just make sure to specify the relationship correctly, and use the generated method named by the association and its methods, e.g. resource_well.unpublished <<, resource_well.unpublished.delete. The following models and tests work for me. The inconvenience is that the delete method on the association takes an object, e.g., other.delete(object) and not a string or conditions, so if you start with a string, you have to supply an object in order to delete it. Note that other.delete_all(conditions) or other.where(conditions).delete_all remove both actual documents as well as the association, which is not what you were looking for. Anyway, hope that this helps.
Models
class Segment
include Mongoid::Document
field :production_status, type: String
field :projected_air_date, type: Date
field :published, type: Boolean
field :segment_status, type: String
embeds_one :resource_well
end
class ResourceWell
include Mongoid::Document
embedded_in :segment
has_and_belongs_to_many :published, :class_name => 'Resource'
has_and_belongs_to_many :unpublished, :class_name => 'Resource'
end
class Resource
include Mongoid::Document
field :name, type: String
end
Test
require 'test_helper'
class Object
def to_pretty_json
JSON.pretty_generate(JSON.parse(self.to_json))
end
end
class SegmentTest < ActiveSupport::TestCase
def setup
Segment.delete_all
end
test 'resource_well unpublished delete' do
res = (0..3).collect{|i| Resource.create(name: "resource #{i}")}
seg = Segment.create(
production_status: 'Unscheduled',
projected_air_date: nil,
published: false,
resource_well: ResourceWell.new(unpublished: res[0..2]),
segment_status: 'Draft')
seg.resource_well.unpublished << res[3] #just an append example
puts seg.to_pretty_json
id = res[0]['_id'].to_s
puts "id: #{id}"
resource_obj = Resource.find(id)
puts "resource: #{resource_obj.inspect}"
Rails.logger.debug("delete: #{resource_obj.inspect}")
seg.resource_well.unpublished.delete(resource_obj)
puts Segment.find(:all).to_pretty_json
end
end
Result
# Running tests:
{
"_id": "4fa839197f11ba80a9000006",
"production_status": "Unscheduled",
"projected_air_date": null,
"published": false,
"resource_well": {
"_id": "4fa839197f11ba80a9000005",
"published_ids": [
],
"unpublished_ids": [
"4fa839197f11ba80a9000001",
"4fa839197f11ba80a9000002",
"4fa839197f11ba80a9000003",
"4fa839197f11ba80a9000004"
]
},
"segment_status": "Draft"
}
id: 4fa839197f11ba80a9000001
resource: #<Resource _id: 4fa839197f11ba80a9000001, _type: nil, name: "resource 0">
[
{
"_id": "4fa839197f11ba80a9000006",
"production_status": "Unscheduled",
"projected_air_date": null,
"published": false,
"resource_well": {
"_id": "4fa839197f11ba80a9000005",
"published_ids": [
],
"unpublished_ids": [
"4fa839197f11ba80a9000002",
"4fa839197f11ba80a9000003",
"4fa839197f11ba80a9000004"
]
},
"segment_status": "Draft"
}
]
.
Finished tests in 0.016026s, 62.3986 tests/s, 0.0000 assertions/s.
1 tests, 0 assertions, 0 failures, 0 errors, 0 skips
db.collection.update(
{_id : ObjectId("4fa77c808d0a6c287d00000a")},
{ $unset : { "resource_well.unpublished.1" : 1 }}
);
This will remove element [1] of the array.
Also you can read how to implement it using mongoid: http://mongoid.org/docs/upgrading.html
My company has an old website that was built many years ago in PHP. I believe the database schema represents the slow accumulation of requests that were never thought about in a strategic way. Now we are throwing out the PHP code and building the site using Rails. The database schema remains, mostly unchanged.
I'm just getting my head around Rails, so this will be perhaps a simple question.
I have an article table where the data looks like this:
id: 1102129
article_type_id: 5
old_article_id: 0
title: Zombie Film Locations
subtitle: Hollywood-inspired survival tips for destinations teeming with the undead
display_date: 0000-00-00
author_id: 29
thumbnail_image_id: 12033473
index_image_id: 12033473
article_image_id: 12033473
is_archived: 0
is_featured: 1
is_published: 1
date_to_show: 2011-10-04 00:00:00
off_the_path: 0
created: 2011-10-04 12:45:07
modified: 2011-10-11 15:33:59
fact_check_date: 0000-00-00 00:00:00
unpublish_date: 0000-00-00
is_qsfeatured: 1
featured_order: 0
Second row:
id: 1102128
article_type_id: 14
old_article_id: 0
title: Hotel Adagio [id]35677
subtitle: Hotel Adagio
display_date: 2011-09-29
author_id: 0
thumbnail_image_id: NULL
index_image_id: NULL
article_image_id: NULL
is_archived: 0
is_featured: 0
is_published: 1
date_to_show: 2011-09-29 12:50:38
off_the_path: 0
created: 2011-09-29 12:50:38
modified: 2011-09-29 12:50:38
fact_check_date: NULL
unpublish_date: NULL
is_qsfeatured: 0
featured_order: NULL
The image data looks like:
id: 6722
old_id: 0
type: Index
association_type: TopTen
association_id: 1102129
position: 0
copyright: AMC
caption: The Walking Dead portrays an abandoned, corpse-ridden downtown Atlanta, one of 10 zombie film locations we've scouted out to help you survive (and hide) should the dead rise.
alt_text: Zombie film locations
url: photos/index-TopTens/walkingdeadindex.jpg
_url:
name: index
published: 0
description:
Second row:
id: 6723
old_id: 0
type: Thumbnail
association_type: TopTen
association_id: 1102129
position: 0
copyright: AMC
caption: The Walking Dead portrays an abandoned, corpse-ridden downtown Atlanta, one of 10 zombie film locations we've scouted out to help you survive (and hide) should the dead rise.
alt_text: Zombie film locations
url: photos/thumb-TopTens/walkingdeadthumb.jpg
_url:
name: thumbnail
published: 0
description:
The association_id in the images table is the id of the article, though there is no foreign key specified in the database.
How do I get all the images when I get the article?
Right now in the controller I do:
#articles = Article.where(:article_type_id => 5).order("id DESC").limit(5)
In the view I do stuff like:
<%= article.title %>
But how can I reference the images, from the article model?
To make this more complicated, the images are polymorphic, so I can not simply create a foreign key from images to articles. Images also sometimes get mapped to "travel_guides" and "slideshows", which are 2 other tables we have.
UPDATE:
Changed Article so now it has:
has_many :images, :foreign_key => 'association_id'
Changed my controller code to:
#articles = Article.where(:article_type_id => 5).order("id DESC").limit(5).joins(:images)
But in my view I do:
<%= article.images.url %>
and I get "no method url" errors.
I was doing:
<%= article.image.url %>
without the "s" on images, and then I got "no method 'image'".
UPDATE:
So, okay, so, on the command line, I run "rails c" to get into the console, and then I run the code that I'm using in my controller:
#articles = Article.where(:article_type_id => 5).order("id DESC").limit(5).joins(:image)
Which gives me:
#articles = Article.where(:article_type_id => 5).order("id DESC").limit(5).joins(:image)
Article Load (11.5ms) SELECT `articles`.* FROM `articles` INNER JOIN `images` ON `images`.`association_id` = `articles`.`id` WHERE `articles`.`article_type_id` = 5 ORDER BY id DESC LIMIT 5
=> [#<Article id: 1102129, article_type_id: 5, old_article_id: 0, title: "Zombie Film Locations", subtitle: "Hollywood-inspired survival tips for destinations t...", display_date: nil, author_id: 29, thumbnail_image_id: 12033473, index_image_id: 12033473, article_image_id: 12033473, is_archived: false, is_featured: true, is_published: true, date_to_show: "2011-10-04 00:00:00", off_the_path: false, created: "2011-10-04 12:45:07", modified: "2011-10-18 10:44:15", fact_check_date: nil, unpublish_date: nil, is_qsfeatured: true, featured_order: 1, slug: "zombie-film-locations">, #<Article id: 1102129, article_type_id: 5, old_article_id: 0, title: "Zombie Film Locations", subtitle: "Hollywood-inspired survival tips for destinations t...", display_date: nil, author_id: 29, thumbnail_image_id: 12033473, index_image_id: 12033473, article_image_id: 12033473, is_archived: false, is_featured: true, is_published: true, date_to_show: "2011-10-04 00:00:00", off_the_path: false, created: "2011-10-04 12:45:07", modified: "2011-10-18 10:44:15", fact_check_date: nil, unpublish_date: nil, is_qsfeatured: true, featured_order: 1, slug: "zombie-film-locations">, #<Article id: 1102129, article_type_id: 5, old_article_id: 0, title: "Zombie Film Locations", subtitle: "Hollywood-inspired survival tips for destinations t...", display_date: nil, author_id: 29, thumbnail_image_id: 12033473, index_image_id: 12033473, article_image_id: 12033473, is_archived: false, is_featured: true, is_published: true, date_to_show: "2011-10-04 00:00:00", off_the_path: false, created: "2011-10-04 12:45:07", modified: "2011-10-18 10:44:15", fact_check_date: nil, unpublish_date: nil, is_qsfeatured: true, featured_order: 1, slug: "zombie-film-locations">, #<Article id: 1102122, article_type_id: 5, old_article_id: 0, title: "Nude Vacations", subtitle: "These places to get naked around the world make for...", display_date: nil, author_id: 574, thumbnail_image_id: 12024629, index_image_id: 12024629, article_image_id: 12024629, is_archived: false, is_featured: true, is_published: true, date_to_show: "2011-09-23 00:00:00", off_the_path: false, created: "2011-09-23 13:13:41", modified: "2011-10-18 10:44:15", fact_check_date: nil, unpublish_date: nil, is_qsfeatured: true, featured_order: 2, slug: "nude-vacations">, #<Article id: 1102122, article_type_id: 5, old_article_id: 0, title: "Nude Vacations", subtitle: "These places to get naked around the world make for...", display_date: nil, author_id: 574, thumbnail_image_id: 12024629, index_image_id: 12024629, article_image_id: 12024629, is_archived: false, is_featured: true, is_published: true, date_to_show: "2011-09-23 00:00:00", off_the_path: false, created: "2011-09-23 13:13:41", modified: "2011-10-18 10:44:15", fact_check_date: nil, unpublish_date: nil, is_qsfeatured: true, featured_order: 2, slug: "nude-vacations">]
So, despite the join, none of the image data shows up.
UPDATE:
Huh, now I see that in that block that I'm getting on the rails console, I see that each article is returning 3 times, I assume because of the JOIN:
Article id: 1102129, article_type_id: 5, old_article_id: 0, title: "Zombie Film Locations", subtitle: "Hollywood-inspired survival tips for destinations t...", display_date: nil, author_id: 29, thumbnail_image_id: 12033473, index_image_id: 12033473, article_image_id: 12033473, is_archived: false, is_featured: true, is_published: true, date_to_show: "2011-10-04 00:00:00", off_the_path: false, created: "2011-10-04 12:45:07", modified: "2011-10-18 10:44:15", fact_check_date: nil, unpublish_date: nil, is_qsfeatured: true, featured_order: 1, slug: "zombie-film-locations">,
,
,
So I get 3 articles because of the 3 associations with images, yet I do not get the images. This is the worst of both worlds.
It would be easier to do a separate query for the images, really.
UPDATE:
This SQL gives me what I want but I don't seem able to turn this into a Rails query:
SELECT articles.title, articles.subtitle, images.url, images.caption
FROM articles
JOIN images ON articles.id = images.association_id
WHERE images.type='Thumbnail'
ORDER BY articles.id desc
LIMIT 5 \G
UPDATE:
I have not been able to get this to work. I think a Ruby tutor to teach me some basic Ruby and Rails stuff. I am happy to pay. I'm reposting my questions here: http://tutorruby.com/question/show?id=3235
To access the images from an article you need to add an association in the Article class:
class Article
has_many :images, :foreign_key => :association_id
end
You mentioned that sometimes images are mapped to travel_guides and slideshows (2 additional tables). You have to add an association for each table. So you might do something like:
class Article < ActiveRecord::Base
has_many :images, :foreign_key => :association_id
has_many :travel_guides, :foreign_key => :association_id
has_many :slideshow_images, :foreign_key => :association_id
end
It is generally also a good idea to set up the reverse associations:
class Image < ActiveRecord::Base
belongs_to :article, :foreign_key => :association_id
end
class TravelGuide < ActiveRecord::Base
belongs_to :article, :foreign_key => :association_id
end
class SlideshowImage < ActiveRecord::Base
set_table_name :slideshows
belongs_to :article, :foreign_key => :association_id
end
Some things to consider:
I left off the :dependent option in the *has_many* associations. This is generally not a good idea. You can set :depedent => :destroy so that you can call some cleanup logic in your image classes (to delete the image files).
If you do not have simple joins, you should consider adding the :conditions option to the has_many (or the belongs to).
Single table inheritance and or polymorhpic associations are both other tools you can use if the above suggestions don't solve all of your problems.
When you specify "has_many" in your images association, you are saying that an article has a collection of images, so article.images returns an array. There is no method 'url' defined on array. To access the url method, you need to access each individual array member, and call the url on that object. Something like:
article.images[0].url
or
article.images.first.url
You need to specify the association in the models. There is a namin convention like you sait but your table doesnt follow that but thats not a problem at all. There are two possible solutions.
1.) You rename the column in the database (what wont work when the app is still in production with PHP)
2.) You specify the association using the :foreign_key parameter like this:
Article:
has_many :images, :foreign_key => 'association_id'
Picture:
belongs_to :article, :foreign_key => 'association_id'
But there are some things that you need to attend. You will get some problems naming your table images because theres also an public folder "images" and when you call a picture beginning with a number rails interprets it as an id and trys to fetch an Image instance from the database instead of delivering the image.
Secound problem might get that thers a field called "association_type" which suggestes that thers a polymorphy association. YOu might rename this too when you switch to rails.
articles = Article.includes(:images).where(:article_type_id => 5, :images => { :image_type => 'Thumbnail' }).order('articles.id DESC').limit(5) #no database query is fired yet by rails
article = articles.first # Here we fire the first query, and this query also fetches the image of the article
article.images.first #again fires no query but returns the first image of the article
There is another case where you should fire db query to fetch only one image from the database.
Then you should do something like,
articles = Article.where(:article_type_id => 5).order("id DESC").limit(5) #again fires no db query
article = articles.first #fires db query to get first article
image = article.images.first #fires query for only first image of the article
First method is efficient if you have less number of images per article, but second method is efficient if there is large number of images per article
Now your rails query for the sql posted above,
SELECT articles.title, articles.subtitle, images.url, images.caption
FROM articles
JOIN images ON articles.id = images.association_id
WHERE images.type='Thumbnail'
ORDER BY articles.id desc
LIMIT 5
articles = Article.joins(:images).select("articles.title, articles.subtitle, images.url, images.caption").where("images.type = 'Thumbnail'").order("articles.id DESC").limit(5)
article = articles.first
first_image = article.images.first