I found an unusual situation on rails 3 app.
Consider the following model:
class Genre < ActiveRecord::Base
has_many :banner_genres, :dependent => :destroy
has_many :banners, :through => :banner_genres
...
class BannerGenre < ActiveRecord::Base
attr_accessible :banner_id, :banner, :genre_id, :genre, :position
belongs_to :genre
belongs_to :banner
...
class Banner < ActiveRecord::Base
has_many :banner_genres, :dependent => :destroy
has_many :genres, :through => :banner_genres
...
Now if I have a genre with a banner, I get the following lines in rails console:
1.9.3p362 :005 > g = Genre.find 62
Genre Load (0.8ms) SELECT "genres".* FROM "genres" WHERE "genres"."id" = $1 LIMIT 1 [["id", 62]]
=> #<Genre id: 62, ...
1.9.3p362 :006 > g.banner_genres.any?
(0.5ms) SELECT COUNT(*) FROM "banner_genres" WHERE "banner_genres"."genre_id" = 62
=> true
1.9.3p362 :007 > g.banners
Banner Load (1.0ms) SELECT "banners".* FROM "banners" INNER JOIN "banner_genres" ON "banners"."id" = "banner_genres"."banner_id" WHERE "banner_genres"."genre_id" = 62 ORDER BY position
=> [#<Banner id: 446, ...
1.9.3p362 :008 > g.banners.any?
=> false
Why does the .any? returns false? I thy with another has_many through association on the same project and it returns true.
Edit:
I had a typo when passing the code here. It's banner_genres on the has_many. Not genre_banners.
Also the inverse association works as it's supposed to:
1.9.3p362 :004 > b = Banner.find 446
Banner Load (1.1ms) SELECT "banners".* FROM "banners" WHERE "banners"."id" = $1 ORDER BY position LIMIT 1 [["id", 446]]
=> #<Banner id: 446...
1.9.3p362 :005 > b.genres.any?
(0.8ms) SELECT COUNT(*) FROM "genres" INNER JOIN "banner_genres" ON "genres"."id" = "banner_genres"."genre_id" WHERE "banner_genres"."banner_id" = 446
=> true
Edit 2
More strange console output:
1.9.3p362 :007 > g.banners.class
=> Array
1.9.3p362 :008 > g.banners.any?
=> false
1.9.3p362 :004 > g.banners.any? {|b| b}
=> true
1.9.3p362 :006 > g.banners.count
(0.9ms) SELECT COUNT(*) FROM "banners" INNER JOIN "banner_genres" ON "banners"."id" = "banner_genres"."banner_id" WHERE "banner_genres"."genre_id" = 62
=> 1
1.9.3p362 :009 > g.banners.to_a.any?
=> true
Edit 3
g.banner_genres
BannerGenre Load (0.7ms) SELECT "banner_genres".* FROM "banner_genres" WHERE "banner_genres"."genre_id" = 62
=> [#<BannerGenre id: 4, genre_id: 62, banner_id: 446, position: 1, created_at: "2013-03-15 16:41:10", updated_at: "2013-03-15 16:41:10">]
Edit 4 Asked by Aleks
could you please show queries that are shown on g.banners.any? and g.banners.
It's exactly the same query, which it's even stranger. I also didn't override the any? method anywhere.
1.9.3p362 :037 > g.banners(true)
Banner Load (1.0ms) SELECT "banners".* FROM "banners" INNER JOIN "banner_genres" ON "banners"."id" = "banner_genres"."banner_id" WHERE "banner_genres"."genre_id" = 62 ORDER BY position
=> [#<Banner id: 446, ...
1.9.3p362 :038 > g.banners(true).any?
Banner Load (1.2ms) SELECT "banners".* FROM "banners" INNER JOIN "banner_genres" ON "banners"."id" = "banner_genres"."banner_id" WHERE "banner_genres"."genre_id" = 62 ORDER BY position
=> false
1.9.3p362 :039 > g.banners.method(:any?)
=> #<Method: Array(Enumerable)#any?>
any? was supposed to return true....
I was so curious about this issue that I decided to simulate it.
g = Genre.find 1
Genre Load (30.1ms) SELECT "genres".* FROM "genres" WHERE "genres"."id" = $1 LIMIT 1 [["id", 1]]
=> #<Genre id: 1, name: "a", created_at: "2013-03-19 11:44:32", updated_at: "2013-03-19 11:44:32">
g.banner_genres.any?
(0.3ms) SELECT COUNT(*) FROM "banner_genres" WHERE "banner_genres"."genre_id" = 1
=> true
g.banners
Banner Load (0.5ms) SELECT "banners".* FROM "banners" INNER JOIN "banner_genres" ON "banners"."id" = "banner_genres"."banner_id" WHERE "banner_genres"."genre_id" = 1
=> [#<Banner id: 1, name: "1", created_at: "2013-03-19 11:43:00", updated_at: "2013-03-19 11:43:00">, #<Banner id: 2, name: "2", created_at: "2013-03-19 11:43:59", updated_at: "2013-03-19 11:43:59">]
g.banners.any?
=> true
Also,
g.banners.class
=> Array
g.banners.any?
=> true
g.banners.to_a.any?
=> true
There is something else going on in your code...
The real question here is not whether the association is good or bad, but why it is showing false for array that has elements in it.
It is clear that it is returning values, but the question is why it is returning false
The explanation might lay here: Unable to get Ruby's #any? to return false with list of nil objects
See how the any? is implemented, and how it relates to your question, it might give you a hint.
See this link as well : http://apidock.com/ruby/Enumerable/any%3F
EDIT:
What you are saying:
First you have an array of objects. Which will on any? return false
But if you do .to_a you will do something like this:
Time.new.to_a #=> [39, 54, 8, 9, 4, 2013, 3, 99, true, "CET"]
And that is why it will return true, as you will have object that are not null.
EDIT 2
Just to note: any? will return false if all objects from an array are nil or false
Related
I've a Rails 5.2.1 app where each step of a relationship works, but the has_many :through version doesn't. The setup is a little strange, but I feel like I've set everything up correctly, so I'm a little stumped.
Given this code:
class Contact < SalesforceModel
self.table_name = 'salesforce.contact'
self.primary_key = 'sfid'
has_many :content_accesses, foreign_key: 'contact__c', class_name: 'ContentAccess'
has_many :concepts, through: :content_accesses, source: :inventory
end
class ContentAccess < ApplicationRecord
self.table_name = 'salesforce.content_access__c'
self.primary_key = 'sfid'
belongs_to :inventory, foreign_key: 'inventory__c', inverse_of: :content_accesses, primary_key: 'sfid', class_name: 'Inventory'
belongs_to :contact, foreign_key: 'contact__c', inverse_of: : content_accesses, primary_key: 'sfid', class_name: 'Contact'
end
class Inventory < SalesforceModel
self.table_name = 'salesforce.inventory__c'
self.primary_key = 'sfid'
has_many :content_accesses, foreign_key: 'inventory__c'
has_many :contacts, through: :content_accesses
end
Each step of the has_many :through works:
# Setup
2.5.1 :001 > contact = Contact.first
Contact Load (30.6ms) SELECT "salesforce"."contact".* FROM "salesforce"."contact" ORDER BY "salesforce"."contact"."sfid" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Contact lastname: "Doe", mailingpostalcode: "90210", name: "John Doe", mobilephone: nil, birthdate: nil, phone: nil, mailingstreet: "123 ABC Street", isdeleted: false, systemmodstamp: "2018-03-16 00:09:01", mailingstatecode: "CA", createddate: "2018-03-15 17:50:44", mailingcity: "LA", mailingcountrycode: "US", firstname: "John", email: "john.doe#realisp.com", sfid: "003m000000txXhwAAE", id: "003m000000txXhwAAE", _hc_lastop: "SYNCED", _hc_err: nil>
# Accessing related ContentAccess works
2.5.1 :002 > contact.content_accesses.count
(2.0ms) SELECT COUNT(*) FROM "salesforce"."content_access__c" WHERE "salesforce"."content_access__c"."contact__c" = $1 [["contact__c", "003m000000txXhwAAE"]]
=> 2
# Accessing related Inventory, through the related ContentAccess works
2.5.1 :003 > contact.content_accesses.first.inventory
ContentAccess Load (0.6ms) SELECT "salesforce"."content_access__c".* FROM "salesforce"."content_access__c" WHERE "salesforce"."content_access__c"."contact__c" = $1 ORDER BY "salesforce"."content_access__c"."sfid" ASC LIMIT $2 [["contact__c", "003m000000txXhwAAE"], ["LIMIT", 1]]
Inventory Load (30.4ms) SELECT "salesforce"."inventory__c".* FROM "salesforce"."inventory__c" WHERE "salesforce"."inventory__c"."sfid" = $1 LIMIT $2 [["sfid", "a1mm0000001S9qzAAC"], ["LIMIT", 1]]
=> #<Inventory createddate: "2018-05-23 15:09:41", isdeleted: false, name: "Some Concept Name", systemmodstamp: "2018-05-23 15:09:42", sfid: "a1mm0000001S9qzAAC", id: "a1mm0000001S9qzAAC", _hc_lastop: "SYNCED", _hc_err: nil>
# Accessing the related inventory through the has_many :through does not work
2.5.1 :004 > contact.concepts.count
(33.0ms) SELECT COUNT(*) FROM "salesforce"."inventory__c" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."inventory__c"."sfid" = "salesforce"."content_access__c"."inventory__c" WHERE "salesforce"."content_access__c"."contact__c" = $1 [["contact__c", "003m000000txXhwAAE"]]
=> 0
Running the generated query in Postgres works, though:
app_development=# SELECT COUNT(*) FROM "salesforce"."inventory__c" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."inventory__c"."sfid" = "salesforce"."content_access__c"."inventory__c" WHERE "salesforce"."content_access__c"."contact__c" = '003m000000txXhwAAE';
count
-------
2
(1 row)
Running Contact.first.concepts.to_sql produces:
SELECT "salesforce"."inventory__c".* FROM "salesforce"."inventory__c" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."inventory__c"."sfid" = "salesforce"."content_access__c"."inventory__c" WHERE "salesforce"."content_access__c"."contact__c" = '003m000000txXhwAAE'
Running that query through psql works fine, returning the proper records from the inventory__c table.
The reverse also has the same problem:
2.5.1 :002 > inventory = Inventory.first
Inventory Load (30.2ms) SELECT "salesforce"."inventory__c".* FROM "salesforce"."inventory__c" ORDER BY "salesforce"."inventory__c"."sfid" ASC LIMIT $1 [["LIMIT", 1]]
=> #<Inventory createddate: "2018-05-23 15:09:41", isdeleted: false, name: "Positive Focus", systemmodstamp: "2018-05-23 15:09:42", inventory_unique_name__c: "Inventory 1", sfid: "a1mm0000001S9qzAAC", id: "a1mm0000001S9qzAAC", _hc_lastop: "SYNCED", _hc_err: nil>
2.5.1 :003 > inventory.content_accesses.first.contact
ContentAccess Load (0.9ms) SELECT "salesforce"."content_access__c".* FROM "salesforce"."content_access__c" WHERE "salesforce"."content_access__c"."inventory__c" = $1 ORDER BY "salesforce"."content_access__c"."sfid" ASC LIMIT $2 [["inventory__c", "a1mm0000001S9qzAAC"], ["LIMIT", 1]]
Contact Load (30.9ms) SELECT "salesforce"."contact".* FROM "salesforce"."contact" WHERE "salesforce"."contact"."sfid" = $1 LIMIT $2 [["sfid", "003m000000txXhwAAE"], ["LIMIT", 1]]
=> #<Contact sfid: "003m000000txXhwAAE", id: "003m000000txXhwAAE", [...etc...] >
2.5.1 :004 > inventory.contacts.count
(30.7ms) SELECT COUNT(*) FROM "salesforce"."contact" INNER JOIN "salesforce"."content_access__c" ON "salesforce"."contact"."sfid" = "salesforce"."content_access__c"."contact__c" WHERE "salesforce"."content_access__c"."inventory__c" = $1 [["inventory__c", "a1mm0000001S9qzAAC"]]
=> 0
So: everything seems to be hooked up correctly, so why isn't the through version working? Any help would be appreciated.
Thanks! ❤️
Thanks to #Zabba's comment asking about SalesforceModel, I was able to track down the problem. Fundamentally, it was this:
class ContentAccess < ApplicationRecord
It should have been:
class ContentAccess < SalesforceModel # <-- it was using the wrong table, effectively.
This has been a harrowing week, but it's better now. 😀
In my Post.rb model, I am doing this:
validates_presence_of :body
In my controller I have this action that I am executing:
def mark_as_published
if #post.unpublished?
#post.published!
redirect_to post_path(#post), notice: "Successfully published."
else
redirect_to post_path(#post), notice: "Post already published"
end
end
This is the error I am getting:
app/controllers/posts_controller.rb:104:in `mark_as_published'
Started PUT "/posts/ebola-death-climbs-past-6-000/mark_as_published" for 67.230.41.168 at 2014-12-10 20:15:29 +0000
app[web.1]: ActiveRecord::RecordInvalid (Validation failed: Body can't be blank):
This is the record in the console:
> Post.last
=> #<Post id: 29, title: "Ebola death climbs past 6,000", photo: nil, body: "Fresh figures from WHO has revealed that the death...", created_at: "2014-12-10 20:08:58", updated_at: "2014-12-10 20:08:58", user_id: 10, ancestry: nil, file: nil, status: 2, slug: "ebola-death-climbs-past-6-000", publication_status: 0, has_eyewitness: false, youtube_embed_code: "", soundcloud_embed_code: "">
Why does this validation fail, only on the save/update, even though the post.body is not blank?
Edit 1
The publication_status is just an enum:
enum publication_status: [ :unpublished, :published ]
Which comes with a set of handy methods, including published?, unpublished?, published!, and unpublished!. The latter two basically toggle the value to be the flag, i.e. unpublished! changes the publication_status to be unpublished and vice versa.
Here are other validations on the Post.rb model:
validates_length_of :body, maximum: 150, too_long: 'The report must be less than 150 words.',
tokenizer: ->(str) { str.scan(/\w+/) }
validates_length_of :title, maximum: 7, too_long: 'The title must be less than 7 words.',
tokenizer: ->(str) { str.scan(/\w+/) }
Edit 2
This is what happens when I mark the record as published in the console:
0> p = Post.last
=> #<Post id: 29, title: "Ebola death climbs past 6,000", photo: nil, body: "Fresh figures from WHO has revealed that the death...", created_at: "2014-12-10 20:08:58", updated_at: "2014-12-10 20:08:58", user_id: 10, ancestry: nil, file: nil, status: 2, slug: "ebola-death-climbs-past-6-000", publication_status: 0, has_eyewitness: false, youtube_embed_code: "", soundcloud_embed_code: "">
irb(main):002:0> p.published!
=> true
irb(main):003:0> p.save
=> true
No SQL is generated. Not sure if this is because I am doing this in production on Heroku.
Edit 3
I tried to publish a record in development, and this is the SQL & server log:
Started PUT "/posts/longword-verylongword-longword-longword-longword-vellylongword-prettylongword/mark_as_published" for 127.0.0.1 at 2014-12-10 19:22:56 -0500
Processing by PostsController#mark_as_published as HTML
Parameters: {"authenticity_token"=>"8kYDxjYS54sGozjSS4ZZwQFJUTtIBgLpEmpAlTRZc4k=", "id"=>"longword-verylongword-longword-longword-longword-vellylongword-prettylongword"}
User Load (1.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."id" ASC LIMIT 1
(2.0ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'admin') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 1]]
(1.2ms) SELECT COUNT(*) FROM "roles" INNER JOIN "users_roles" ON "roles"."id" = "users_roles"."role_id" WHERE "users_roles"."user_id" = $1 AND (((roles.name = 'editor') AND (roles.resource_type IS NULL) AND (roles.resource_id IS NULL))) [["user_id", 1]]
(0.2ms) BEGIN
(0.2ms) ROLLBACK
Completed 422 Unprocessable Entity in 62ms
ActiveRecord::RecordInvalid - Validation failed: Body can't be blank:
When I do it in the console, this is the log:
> p = Post.last
Post Load (0.6ms) SELECT "posts".* FROM "posts" ORDER BY "posts"."id" DESC LIMIT 1
=> #<Post id: 37, title: "LongWord VeryLongWord LongWord LongWord LongWord V...", photo: nil, body: "10PP gives you a lot of one on one attention that ...", created_at: "2014-11-27 09:21:06", updated_at: "2014-11-27 09:21:06", user_id: nil, ancestry: nil, file: nil, status: 1, slug: "longword-verylongword-longword-longword-longword-v...", publication_status: 0, has_eyewitness: false, youtube_embed_code: "", soundcloud_embed_code: "">
[21] pry(main)> p.published!
(0.2ms) BEGIN
SQL (2.0ms) UPDATE "posts" SET "publication_status" = $1, "updated_at" = $2 WHERE "posts"."id" = 37 [["publication_status", 1], ["updated_at", "2014-12-11 00:24:18.419390"]]
FriendlyId::Slug Load (1.9ms) SELECT "friendly_id_slugs".* FROM "friendly_id_slugs" WHERE "friendly_id_slugs"."sluggable_id" = $1 AND "friendly_id_slugs"."sluggable_type" = $2 ORDER BY "friendly_id_slugs".id DESC LIMIT 1 [["sluggable_id", 37], ["sluggable_type", "Post"]]
(0.9ms) COMMIT
=> true
From the log I can see clearly you do not retrieve the Post from the database. So you are saving a "new/empty" post. Please verify how your #post variable is set. I am guessing you are using the incorrect before_action now, or your post retrieval is too liberal and defaults to a new post if not found?
One option, if and only if it works within your application's logic, is to limit the validation to creates:
validates_presence_of :body, on: :create
If your app's logic dictates that updates need to run the validation, it would be helpful to see more code in order to better know how #post is populated, what the published/unpublished? methods look like, etc.
Here's what happens:
I have in my controller:
#products = Spree::Product.all_active
And in the model:
Spree::Product.class_eval do
def self.all_active
includes(:master)
.where('available_on IS NULL OR available_on < ?', Time.now).where(deleted_at: nil)
end
end
And in the view I'm calling something that will look like this:
#products.each do |product|
product.images.each do |image|
image.attachment.url(:product)
end
end
The log is showing something along the lines like this for every single product:
Spree::Image Load (2.6ms) SELECT "spree_assets".* FROM "spree_assets" WHERE "spree_assets"."type" IN ('Spree::Image') AND "spree_assets"."viewable_id" = $1 AND "spree_assets"."viewable_type" = $2 ORDER BY "spree_assets"."position" ASC [["viewable_id", 9], ["viewable_type", "Spree::Variant"]]
Spree::Price Load (0.3ms) SELECT "spree_prices".* FROM "spree_prices" WHERE "spree_prices"."variant_id" = $1 AND "spree_prices"."currency" = 'USD' LIMIT 1 [["variant_id", 9]]
CACHE (0.3ms) SELECT "spree_zone_members".* FROM "spree_zone_members" WHERE "spree_zone_members"."zone_id" = $1 [["zone_id", 2]]
CACHE (0.0ms) SELECT "spree_countries".* FROM "spree_countries" WHERE "spree_countries"."id" IN (204, 49)
CACHE (0.2ms) SELECT "spree_shipping_methods".* FROM "spree_shipping_methods" INNER JOIN "spree_shipping_methods_zones" ON "spree_shipping_methods"."id" = "spree_shipping_methods_zones"."shipping_method_id" WHERE "spree_shipping_methods"."deleted_at" IS NULL AND "spree_shipping_methods_zones"."zone_id" = $1 [["zone_id", 2]]
CACHE (0.0ms) SELECT "spree_calculators".* FROM "spree_calculators" WHERE "spree_calculators"."calculable_type" = 'Spree::ShippingMethod' AND "spree_calculators"."calculable_id" IN (3, 2, 1)
(0.8ms) SELECT COUNT(*) FROM "spree_assets" WHERE "spree_assets"."type" IN ('Spree::Image') AND "spree_assets"."viewable_id" = $1 AND "spree_assets"."viewable_type" = $2 [["viewable_id", 1], ["viewable_type", "Spree::Variant"]]
I also have the bullet gem installed and it is recommending me to do:
N+1 Query detected
Spree::Variant => [:images]
Add to your finder: :include => [:images]
N+1 Query detected
Spree::Variant => [:default_price]
Add to your finder: :include => [:default_price]
I'm not sure where to place this .includes. to find out where Spree::Variant is being called I went to the Rails console:
2.0.0-p481 :001 > Spree::Product.first
Spree::Product Load (1.8ms) SELECT "spree_products".* FROM "spree_products" WHERE "spree_products"."deleted_at" IS NULL ORDER BY "spree_products"."id" ASC LIMIT 1
=> #<Spree::Product id: 1, name: "Ruby on Rails Tote", description: "Debitis facilis impedit natus eos qui vero. Ut qua...", available_on: "2014-07-04 05:44:50", deleted_at: nil, slug: "ruby-on-rails-tote", meta_description: nil, meta_keywords: nil, tax_category_id: 1, shipping_category_id: 1, created_at: "2014-07-04 05:44:51", updated_at: "2014-07-04 05:45:30">
2.0.0-p481 :002 > Spree::Product.first.images
Spree::Product Load (0.8ms) SELECT "spree_products".* FROM "spree_products" WHERE "spree_products"."deleted_at" IS NULL ORDER BY "spree_products"."id" ASC LIMIT 1
Spree::Variant Load (1.0ms) SELECT "spree_variants".* FROM "spree_variants" WHERE "spree_variants"."deleted_at" IS NULL AND "spree_variants"."product_id" = $1 AND "spree_variants"."is_master" = 't' LIMIT 1 [["product_id", 1]]
Spree::Image Load (0.8ms) SELECT "spree_assets".* FROM "spree_assets" WHERE "spree_assets"."type" IN ('Spree::Image') AND "spree_assets"."viewable_id" = $1 AND "spree_assets"."viewable_type" = $2 ORDER BY "spree_assets"."position" ASC [["viewable_id", 1], ["viewable_type", "Spree::Variant"]]
=> #<ActiveRecord::Associations::CollectionProxy [#<Spree::Image id: 21, viewable_id: 1, viewable_type: "Spree::Variant", attachment_width: 360, attachment_height: 360, attachment_file_size: 31490, position: 1, attachment_content_type: "image/jpeg", attachment_file_name: "ror_tote.jpeg", type: "Spree::Image", attachment_updated_at: "2014-07-04 05:45:28", alt: nil, created_at: "2014-07-04 05:45:29", updated_at: "2014-07-04 05:45:29">, #<Spree::Image id: 22, viewable_id: 1, viewable_type: "Spree::Variant", attachment_width: 360, attachment_height: 360, attachment_file_size: 28620, position: 2, attachment_content_type: "image/jpeg", attachment_file_name: "ror_tote_back.jpeg", type: "Spree::Image", attachment_updated_at: "2014-07-04 05:45:29", alt: nil, created_at: "2014-07-04 05:45:30", updated_at: "2014-07-04 05:45:30">]>
Where would I add :images and :default_price within this context?
try:
Spree::Product.class_eval do
def self.all_active
includes(master: { products: :images } )
.where('available_on IS NULL OR available_on < ?', Time.now).where(deleted_at: nil)
end
end
includes can be used to fetch multiple models, nested or not.
For those that came here purely for the title:
Post.includes(:user, :group, sub_category: :category)
Just in the final stages of transferring an app stuck in Rails 2 on a shared server to Rails 4 on a VPS, but stuck on one thing.
I have an Image model that uses Carrierwave to upload and display attached images. The Image model has a polymorphic association to a couple of other models. The host and associated models are updated together on a combined multipart form.
The form works perfectly well for creating and editing the primary model and for creating and attaching new images, but the image attributes can't be edited, and no error is thrown to help.
Here are the relevant parts of my Image model:
class Image < ActiveRecord::Base
belongs_to :imagings, polymorphic: true, touch: true
mount_uploader :image, ImageUploader, mount_on: :image_file_name
end
And my Review model:
class Review < ActiveRecord::Base
has_many :images, as: :imagings, dependent: :destroy
accepts_nested_attributes_for :images, reject_if: lambda { |t| t['image'].nil? }, allow_destroy: true
end
And my Reviews Controller:
class ReviewsController < ApplicationController
def edit
#review = Review.find(params[:id])
3.times {#review.images.build}
end
def update
#review = Review.find(params[:id])
if #review.update(reviews_params)
redirect_to #review, notice: "Successfully updated review."
else
render :edit
end
end
private
def reviews_params
params.require(:review).permit(:title, :authors, :venue, :startdate, :enddate, :body, :approved, :company, :user_id, { images_attributes: [:title, :credits, :image, :_destroy, :id] })
end
end
I can add images to existing reviews or to a new review perfectly well with this setup, but if I try to change text fields for an existing image, the parameters show in the console but they are ignored:
Started PATCH "/reviews/492" for 127.0.0.1 at 2014-07-16 11:03:42 +0100
Processing by ReviewsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"HvrsCAXXvO4zIqTBw05qSUwU9I2241DXyilhswEbU9o=", "review"=>{"title"=>"Here's a new review", "authors"=>"", "company"=>"", "venue"=>"", "startdate"=>"2014-07-16", "enddate"=>"2014-07-31", "body"=>"<p>Something</p>", "images_attributes"=>{"0"=>{"title"=>"Doggie", "credits"=>"Icons galore", "_destroy"=>"0", "id"=>"1239"}, "1"=>{"title"=>"", "credits"=>""}, "2"=>{"title"=>"", "credits"=>""}, "3"=>{"title"=>"", "credits"=>""}}, "user_id"=>"4", "approved"=>"1"}, "commit"=>"Update Review", "id"=>"492"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Review Load (0.3ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."id" = $1 LIMIT 1 [["id", 492]]
(12.4ms) BEGIN
Image Load (0.4ms) SELECT "images".* FROM "images" WHERE "images"."imagings_id" = $1 AND "images"."imagings_type" = $2 AND "images"."id" IN (1239) [["imagings_id", 492], ["imagings_type", "Review"]]
(0.2ms) COMMIT
Redirected to http://127.0.0.1:3000/reviews/492
Completed 302 Found in 27ms (ActiveRecord: 13.6ms)
You can see here how the images attribute for credit has the change to "Icons galore", but the SQL only fetches the linked image without updating it. Contrast this with the following update to the main model which does a select followed by an update between the BEGIN and COMMIT.
Started PATCH "/reviews/492" for 127.0.0.1 at 2014-07-16 11:26:01 +0100
Processing by ReviewsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"HvrsCAXXvO4zIqTBw05qSUwU9I2241DXyilhswEbU9o=", "review"=>{"title"=>"Here's a new review", "authors"=>"", "company"=>"", "venue"=>"", "startdate"=>"2014-07-16", "enddate"=>"2014-07-31", "body"=>"<p>Something else</p>", "images_attributes"=>{"0"=>{"title"=>"Doggie", "credits"=>"Icons", "_destroy"=>"0", "id"=>"1239"}, "1"=>{"title"=>"", "credits"=>""}, "2"=>{"title"=>"", "credits"=>""}, "3"=>{"title"=>"", "credits"=>""}}, "user_id"=>"4", "approved"=>"1"}, "commit"=>"Update Review", "id"=>"492"}
User Load (6.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", 1]]
Review Load (0.3ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."id" = $1 LIMIT 1 [["id", 492]]
(18.1ms) BEGIN
Image Load (0.5ms) SELECT "images".* FROM "images" WHERE "images"."imagings_id" = $1 AND "images"."imagings_type" = $2 AND "images"."id" IN (1239) [["imagings_id", 492], ["imagings_type", "Review"]]
SQL (5.9ms) UPDATE "reviews" SET "body" = $1, "updated_at" = $2 WHERE "reviews"."id" = 492 [["body", "<p>Something else</p>"], ["updated_at", "2014-07-16 10:26:01.455481"]]
(24.8ms) COMMIT
Redirected to http://127.0.0.1:3000/reviews/492
Completed 302 Found in 120ms (ActiveRecord: 56.0ms)
Yet if I try to make the same update in the Rails console, it sometimes works perfectly:
2.1.1 :062 > review = Review.find(492)
Review Load (0.5ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."id" = $1 LIMIT 1 [["id", 492]]
=> #<Review id: 492, title: "Here's a new review", authors: "", venue: "", startdate: "2014-07-16", enddate: "2014-07-31", body: "<p>Something else</p>", approved: true, created_at: "2014-07-16 09:38:33", updated_at: "2014-07-16 10:26:01", user_id: 4, company: "">
2.1.1 :064 > review.images.first.title = "Doggies galore"
=> "Doggies galore"
2.1.1 :066 > review.save
(0.8ms) BEGIN
SQL (7.5ms) UPDATE "images" SET "title" = $1, "updated_at" = $2 WHERE "images"."id" = 1239 [["title", "Doggies galore"], ["updated_at", "2014-07-16 10:32:39.109784"]]
Review Load (0.3ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."id" = $1 LIMIT 1 [["id", 492]]
SQL (1.7ms) UPDATE "reviews" SET "updated_at" = '2014-07-16 10:32:39.129314' WHERE "reviews"."id" = 492
(1.6ms) COMMIT
=> true
And sometimes doesn't:
2.1.1 :082 > review = Review.find(492)
Review Load (0.3ms) SELECT "reviews".* FROM "reviews" WHERE "reviews"."id" = $1 LIMIT 1 [["id", 492]]
=> #<Review id: 492, title: "Here's a new review", authors: "", venue: "", startdate: "2014-07-16", enddate: "2014-07-31", body: "<p>Something else</p>", approved: true, created_at: "2014-07-16 09:38:33", updated_at: "2014-07-16 10:38:30", user_id: 4, company: "">
2.1.1 :083 > review.images.first.credits = "Icons galore"
Image Load (0.5ms) SELECT "images".* FROM "images" WHERE "images"."imagings_id" = $1 AND "images"."imagings_type" = $2 ORDER BY "images"."id" ASC LIMIT 1 [["imagings_id", 492], ["imagings_type", "Review"]]
=> "Icons galore"
2.1.1 :084 > review.save!
(0.2ms) BEGIN
(0.3ms) COMMIT
=> true
2.1.1 :085 > review.images.first
Image Load (0.8ms) SELECT "images".* FROM "images" WHERE "images"."imagings_id" = $1 AND "images"."imagings_type" = $2 ORDER BY "images"."id" ASC LIMIT 1 [["imagings_id", 492], ["imagings_type", "Review"]]
=> #<Image id: 1239, title: "Doggies", credits: "Icons", created_at: "2014-07-16 09:38:33", updated_at: "2014-07-16 10:38:30", image_file_name: "ddad7b05-3430-49ed-b129-fd484ac793ad.gif", image_content_type: nil, image_file_size: nil, image_updated_at: nil, imagings_id: 492, imagings_type: "Review", original_filename: "Doggie.gif">
2.1.1 :086 >
I know I'm missing something obvious. Can anyone help please?
I think that you need to make a small change in reviews_params def as follows:
def reviews_params
params.require(:review).permit(:title, :authors, :venue, :startdate, :enddate, :body, :approved, :company, :user_id, images_attributes => [:title, :credits, :image, :_destroy, :id] )
end
Does that work?
i don't think you need the curly braces. Try removing them. Usually nested attributes are listed as a symbol which maps to an array of symbols(attributes). They are separated from attributes of the model in question with just a comma. So something such as,
params.require(:review).permit(:title, :authors, :venue, :startdate, :enddate, :body, :approved, :company, :user_id, :images_attributes => [:title, :credits, :image, :_destroy, :id] ) should work.
What am I doing incorrectly that is giving me a blank array from this command?
Item.where(:load_date => Date.today + 2)
Here is my Rails Console:
.9.3-p194 :024 > Item.first.load_date
Item Load (0.3ms) SELECT "items".* FROM "items" LIMIT 1
=> Fri, 24 May 2013
1.9.3-p194 :025 > Item.where(:load_date => Date.today + 2)
Item Load (0.5ms) SELECT "items".* FROM "items" WHERE "items"."load_date" = '2013-05-24'
=> []
1.9.3-p194 :026 > Item.first.load_date == Date.today + 2
Item Load (0.3ms) SELECT "items".* FROM "items" LIMIT 1
=> true
Item Model:
...
# load_date :date
...
class Item < ActiveRecord::Base
attr_accessible :bt_num, :dept, :formula, :item_code, :load_date, :prod_comments, :qc_comments, :qc_tech, :qty_in_kg, :qty_in_liters, :rm_ok_by, :series, :status, :time_to_produce, :vat
...
Try these
Item.where(:load_date => (Date.today + 2).strftime)
or
Item.where("Date(load_date) =?", (Date.today + 2).strftime)