I am learning Rails with the book Agile Web Development with Rails 4th edition.
Given the code for the migration below:
class CombineItemsInCart < ActiveRecord::Migration
def up
Cart.all.each do |cart|
sums = cart.line_items.group(:product_id).sum(:quantity)
sums.each do |product_id, quantity|
if quantity > 1
cart.line_items.where(product_id: product_id).delete_all
cart.line_items.create(product_id: product_id, quantity: quantity)
end
end
end
end
def down
LineItem.where("quantity>1").each do |line_item|
line_item.quantity.times do
LineItem.create(cart_id: line_item.cart_id, product_id: line_item.product_id, quantity: 1)
end
line_item.destroy
end
end
end
The following error occurs:
== CombineItemsInCart: migrating =============================================
rake aborted!
An error has occurred, this and all later migrations canceled:
Can't mass-assign protected attributes: quantity/home/richard/projects/pickaxe/mini-projects/depot-app/db/migrate/20130607003533_combine_items_in_cart.rb:9:in `block (2 levels) in up'
/home/richard/projects/pickaxe/mini-projects/depot-app/db/migrate/20130607003533_combine_items_in_cart.rb:6:in `each'
/home/richard/projects/pickaxe/mini-projects/depot-app/db/migrate/20130607003533_combine_items_in_cart.rb:6:in `block in up'
/home/richard/projects/pickaxe/mini-projects/depot-app/db/migrate/20130607003533_combine_items_in_cart.rb:3:in `each'
/home/richard/projects/pickaxe/mini-projects/depot-app/db/migrate/20130607003533_combine_items_in_cart.rb:3:in `up'
Tasks: TOP => db:migrate
(See full trace by running task with --trace)
Now I read somewhere that this book was written before attr_accessible was required by default, but it hasnt really touched on how to use it properly yet. I have tried adding :line_item or :line_items to my attr_accessible line in the Cart model, but no luck.
Could someone educate me on what is happening here?
Can't mass-assign protected attributes: quantity
try attr_accessible :quantity
you will need to list all of the attributes in that list.
You need to make the attributes accessible. In the model:
class Object ActiveRecord::Base
attrib_accessible :attrib1, :attrib2, :attrib3
end
Obviously you would replace the attrib1 etc. with your model's attributes.
You try to access mass attributes from your migration. When you want to access database table attributes from your code then you need to allowing mass assignment of that attributes to tell your model that you can able to assign data for the field by the code. For this pusrpose, just add the desired field as a attr_accessible and for your issue specific solution is bellow :
class LineItem < ActiveRecord::Base
attr_accessible :quantity, :product_id, :cart_id
end
Related
My app is set up that when product.sold attribute is 1 means an item is sold and it wont show up in the store views. I am trying to get it so when the customer checks out the product.sold attribute is updated when the item is purchased.
Here is the line of code in my controller that should be updating the product.sold attribute from nil to 1:
class ChargesController < ApplicationController
def create
#order.order_items.map{ |o| o.product.id }.assign_attributes(sold: 1)
end
here are my associations:
OrderItem belongs_to :product
OrderItem belongs_to :order
Product has_many :order_items
Order has_many :order_items
here is the error I am getting when I try to purchase a product with an id of 13
NoMethodError in ChargesController#create
undefined method `assign_attributes' for [13]:Array
Extracted source (around line #12):
#OrderMailer.order_email(#order).deliver_now
#order.update_attribute(:order_status_id, 2)
#order.order_items.map{ |o| o.product.id }.assign_attributes(sold: 1)
#final_amount = #amount
I have also tried update_attributes and update in place of assign_attributes. Not sure what else to try. Let me know if more info is needed.
You are trying to call assign_attributes on array of products IDs, but not models. Try this:
def create
product_ids = #order.order_items.map{ |o| o.product.id }
Product.where(id: product_ids).update_all(sold: 1)
end
When running a test, if I try to create a new object using User.new I get an error. If instead I use User.new({}), it works fine.
Isn't params supposed to be defaulted to empty if not passed in?
$ rails -v
Rails 5.0.0.1
user.rb
class User
include ActiveModel::Model
attr_accessor :name, :email, :country
end
user_test.rb
require 'test_helper'
class User < ActiveSupport::TestCase
test "should create an empty user when initialized with no params" do
user = User.new
assert_not_nil(user, 'user cannot be empty')
end
end
test result
Error:
User#test_should_create_an_empty_user_when_initialized_with_no_parameters:
ArgumentError: wrong number of arguments (given 0, expected 1)
test/models/user_test.rb:7:in `new'
test/models/user_test.rb:7:in `block in <class:User>'
Generally, attr_accessor is used on a model for columns that are not actual columns in the SQL table.
So if your model has columns :name, :email and :country, then declaring attr_accessor is unnecessary. I think rails is waiting for you to declare them now.
Try commenting out the attr_accessor :name, :email, :country line, then re-run your test.
This may help
Instead of including model class extend it like:
class Crime < ApplicationRecord
//do your stuff here
end
Then you can use #user = User.new
The User you're referencing in the test is the test itself.
The easiest solution is to follow Ruby convention and name the test class UserTest.
I have an example of code not passing in test but working in the console.
Failing Test:
describe ImporterProfile do
it 'sends defaults method to EventAttribute model' do
expect(ListPage).to receive(:new) #passes
expect(EventAttribute).to receive(:new) #fails
ImporterProfile.new.standard_profile
end
1) ImporterProfile standard_profile sends new method to associated objects
Failure/Error: importer_profile.standard_profile
NoMethodError:
undefined method `each' for nil:NilClass
# ./app/models/importer_profile.rb:51:in `standard_profile'
# ./spec/models/importer_profile_spec.rb:29:in `block (3 levels) in <top (required)>'
Models:
class ImporterProfile < ActiveRecord::Base
has_one :list_page, dependent: :delete
has_many :event_attributes, dependent: :delete_all
accepts_nested_attributes_for :list_page
accepts_nested_attributes_for :event_attributes
def standard_profile
self.list_page = ListPage.new
self.event_attributes = EventAttribute.new
end
end
class EventAttribute < ActiveRecord::Base
belongs_to :importer_profile
end
class ListPage < ActiveRecord::Base
belongs_to :importer_profile
end
However, running this method in the console instantiates a new ImporterProfile, ListPage and several EventAttribute objects.
Anyone understand what is going on here?
I suspect that the problem is that you are mocking EventAttribute.new, but only returning nil, so Rails can't enumerate the active records as is required by the self.event_attributes = statement. (It needs to set the foreign key attribute of the EventAttribute records to the id of the ImporterProfile record.)
If you don't mind continuing with execution, you can do:
expect(EventAttribute).to receive(:new).and_call_original
Alternatively, you can return a double, but you'll need to provide stubs for whatever methods ActiveRecord requires, either by using a library such as http://rubygems.org/gems/rspec-active_record_mocks/versions/0.1.4 or by rolling your own.
As an aside, this question would have been a little easier to answer if you'd provided some way to associate the line numbers in the error stack trace with the sources you provided. Also, the comments on your expect statements that the first passes and the second fails is confusing because it appears that you are raising an error before the expectations are being checked.
I'm just learning RoR, and am trying to build a Model around my legacy database, which is built more around SPROCs for querying data. I've found the activerecord-tableless gem and am using that to help build the models.
So far, I'm able to get the base model working OK:
class Wine < ActiveRecord::Base
has_no_table
self.primary_key = "iWine"
column :iWine, :integer
column :Wine, :string
column :Vintage, :integer
....etc......
attr_accessible :iWine, :Wine, :Vintage, .....
has_many :labelImages
def self.find_wine(id)
r = ActiveRecord::Base.execute_procedure "sp_FetchWineVerbose", 'iWine' => id.to_i
if r.size > 0
w = Wine.new(r[0])
return w;
end
end
end
Now, I'd like to take advantage of ActiveRecord's associations to pull in additional pieces of related data, e.g. label images, other vintages, etc. Here is what I have so far:
class LabelImage < ActiveRecord::Base
has_no_table
column :id, :integer
column :width, :integer
column :height, :integer
column :wine_id, :integer
attr_accessible :id, :width, :height, :wine_id
after_initialize :fetch_data
belongs_to :wine
def fetch_data()
sql = <<-eos
SELECT iLabel AS id, Width AS width, Height AS height, ....
eos
r = ActiveRecord::Base.connection.select_all(sql, 'Label Image', [[wine_id,wine_id]])
if r.size > 0
self.assign_attributes(r[0])
end
end
end
So, now, I can call w = Wine.find_wine(1) and then w.labelImages.build, and I get back a LabelImage object with the right data. But, I also get the following message in the console:
Could not log "sql.active_record" event. NoMethodError: undefined method `name' for 1:Fixnum
I've tried digging through the source code but cannot figure out where this is coming from. And, I also can't figure out how to override the initialization to return an array of multiple LabelImage objects -- as there may be many for any given wine. Should I override the build method (and if so, how?), or is there another way to create the objects and then assign them to the Wine.labelImages attribute?
You may be going about this the hard way, activerecord-tableless gem is really for information not stored in SQL databases.
I would suggest looking at something like https://rubygems.org/gems/rmre that could help build active_models based on you existing schema.
In my db/seeds.rb file of my Rails 3.2.8 project, I create seed data:
level_1 = Level.create(number: 1)
My model for Level is:
class Level < ActiveRecord::Base
attr_accessible :number
has_many :lessons
end
So then I want to seed the lessons:
Lesson.create(number: 5, level: level_1)
Its model is:
class Lesson < ActiveRecord::Base
attr_accessible :level_id, :number
belongs_to :level
end
When I run rake db:setup, I get the following error:
rake aborted! Can't mass-assign protected attributes: level
How do I get this to work, do I need to add :level to my list of attr_accessible elements in the Lesson model? Is this a bad idea? Should I instead simply create a Lesson without a Level and then afterwards call lesson.level = level_1?
UDPATE: The rails generated comments in the seeds.rb file shows this example:
cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
Mayor.create(name: 'Emanuel', city: cities.first)
Do you want to be able to mass-assign level? Put another way, will it be harmful if a form for Lesson objects can set the level attribute?
If it will not cause problems, go ahead and add it to attr_accessible, and keep doing what you're doing now.
If it will cause problems, use .new and set it manually:
lesson = Lesson.new
lesson.level = level_1
lesson.save