Mongo + RoR Models. Stuck with NoMethodError - ruby-on-rails

I have a problem with NoMethodError in one of my models.
In the log file, we have:
NoMethodError (undefined method `length=' for #<Book:0x000000083866b8>):
2013-03-28T10:25:19+00:00 app[web.1]: app/models/engine/book.rb:13:in `block in find_or_create_by_guide'
2013-03-28T10:25:19+00:00 app[web.1]: app/models/engine/book.rb:9:in `find_or_create_by_guide'
Let me go through all of the important files.
For a start, we have Mongo's document.rb:
class Guide::Document
include MongoMapper::Document
key :city, Integer
key :trips, Array
key :themes, Array
key :places, Array
key :pace, String
key :date_from, Time
key :date_to, Time
key :host, String
key :length, Integer
timestamps!
end
Then, the book model is called upon the guide document:
module ClassMethods
def find_or_create_by_guide(guide)
book = ::Book.find_or_create_by_document(guide.id.to_s) do |b|
b.city_id = guide.city
b.host = guide.host
b.pace = guide.pace || :normal
b.length = guide.length
end
later in the book.rb, I have the following line:
groups = sorted_points.in_groups_of(self.length.count, false)
Length.rb:
class Length < ActiveRecord::Base
belongs_to :book
attr_accessible :book_id
end
Book.rb:
attr_accessible :user_id, :country_id, :city_id, :hotel_id, :type, :price, :host, :pace, :created_at, :updated_at, :length
Finally, the migrations of Length:
class AddLengthColumnToBooks < ActiveRecord::Migration
def change
add_column :books, :length, :integer
end
end
Any hints or tips appreciated.

This is a mess. Once you want 'length' to be an attribute of Book, once you want Length to be a separate model which is in a relation with Book.
I see no point in having Length model.
Go with 'length' as a Book property.

Related

Rails 4 - How can I map 2 columns of a Model to another column of another model

I want to map 2 columns of the same model (dev_status and test_planning_status) to another model's column (Status.name) and in the UserStory form I want to have a dropdown with values from Status table
I have tried something like this but unable to figure out
Status model is like this
class Status < ActiveRecord::Base
has_many :dev_status, :class_name => 'UserStory', :foreign_key => 'dev_status_id'
has_many :test_planning_status, :class_name => 'UserStory', :foreign_key => 'test_planning_status_id'
end
Currently I have this in models/UserStory
class UserStory < ActiveRecord::Base
validates :us_number, presence: true
validates :team_id, presence: true
validates :dev_status, presence:true
validates :test_status, presence:true
belongs_to :team
CreateUserStories migration is
class CreateUserStories < ActiveRecord::Migration
def change
create_table :user_stories do |t|
t.string :us_number
t.references :team
t.string :dev_status
t.string :test_planning_status
t.integer :tc_count
t.timestamps null: false
end
add_foreign_key :user_stories, :pod
end
My UserStoryController params is
def user_story_params
params.require(:user_story).permit(:us_number, :team_id, :dev_status, :test_planning_status)
end
UserStory _form is
<%= f.label :dev_status,'Dev Status' %>
<%= f.select :status, Status.all.map {|u|[u.status, u.id]},
{include_blank: true} %>
<%= f.label :test_planning_status, 'Test Planning Status' %>
<%= f.select :status, Status.all.map {|u|[u.status, u.id]},
{include_blank: true} %>
The goal should be to call UserStory.dev_status.name to get the dev_status name, and UserStory.test_planning_status.name to get the test_planning_status name.
Your migration should be creating columns dev_status_id (not dev_status) and test_planning_status_id (not test_planning_status).
Use t.references or t.belongs_to in your future migrations.
Above columns should be integers, not strings.
You need to specify belongs_to on the UserStory object for your status fields.
belongs_to :dev_status, class_name: 'Status'
belongs_to :test_planning_status, class_name: 'Status'
Change
validates :test_status, presence:true
to
validates :test_planning_status, presence:true
The two f.select :status in your form need to be changed to f.select :test_planning_status and f.select :dev_status
That should get you pointed in the right direction. Hope it helps!
This sounds like a standard has_many relationship::
class Status < ActiveRecord::Base
# columns id | name | value | other | information | created_at | updated_at
has_many :user_stories
end
class UserStory < ActiveRecord::Base
# columns id | title | value | dev_status_id | test_planner_status | created_at | updated_at
belongs_to :dev_status, class_name: :status
belongs_to :test_planning_status, class_name: :status
end
This would give you the ability to access the following:
#app/controllers/statuses_controller.rb
class UserStoriesController < ActionController::Base
def show
#story = UserStory.find params[:id]
##story.dev_status = gives you dev's details, with status value from Status table
end
end
If you wanted to avoid the law of demeter (IE only have one point to access your data), you'll want to use the delegate method:
#app/models/user_story.rb
Class UserStory < ActiveRecord::Base
delegate :name to: :dev_status, prefix: true
# this will allow you to call #user.dev_status_name
end
If you then wanted to have statuses changed, you'll be able to use the collection_select helper to get it working with the Status objects:
#app/views/user_stories/edit.html.erb
...
<%= f.collection_select :dev_status_id, Status.all, :id, :name, prompt: true %>
<%= f.collection_select :test_planner_status, Status.all, :id, :name, prompt: true %>
--
ActiveRecord
You must remember that models are built, they are just classes. Rails uses an ORM (ActiveRecord) to pull data to populate these classes.
Many people become confused about how models fit into the Rails ecosystem. A model is made of "attributes", which you have to populate, either manually or through the Rails ORM API. IE your User model could have the following:
#app/models/user.rb
class User < ActiveRecord::Base
def will_you_marry_me?
"no"
end
end
#app/views/application.html.erb
Will the user marry?
<%= #user.will_you_marry_me? %>
When you talk about "mapping" columns, what you're really asking is how to call a different table's data to attributes in your model class. For example, if you have User class, how to populate #user.birthday with data from profiles table etc.
The answer to that is to use the relational structure of ActiveRecord. Relational databases simply work with foreign_keys to load data from other tables. For example, you could have profiles table with user_id to get information about a specific user (see the image above).
ActiveRecord makes the process of loading "other table" data very simple. By using the relationships in the API, you can populate data with the likes of has_many etc.

Ruby present model not in DB

I would like to use a model in Rails but not store it in DB. For example let's say I have a RSS reader. I would download some RSS data from other site and then create objects with specific model and show them to use. I don't want to store those objects in databse though. How can I do it?
I think your problem might easily be solved by just creating a class, alternatively you can use ActiveModel, it allows for the same behaviour without storing it in the db.
class RssReader
#include any behaviour you like
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
end
There is a very nice railscast on this at:
http://railscasts.com/episodes/219-active-model
You can also check this out(Rails 4)
http://blog.remarkablelabs.com/2012/12/activemodel-model-rails-4-countdown-to-2013
You're looking for tableless models, there are plenty of questions on SO about this:
Rails model without database
Rails Model without a table
and a handy railscast!
http://railscasts.com/episodes/193-tableless-model
In rails 2.3 You can do this by this way:
class OrderSession < ActiveRecord::Base
def self.columns() #columns ||= []; end
column :orderable_id, :integer
column :orderable_type, :string
column :subscription, :boolean
column :interval, :integer
column :quantity, :float
column :number, :integer
column :only_with_main_cart, :boolean
column :start_on, :date
attr_accessor :choice_item
attr_accessor :interval_choice_item_1
attr_accessor :interval_choice_item_2
validates_presence_of :orderable_id
validates_presence_of :orderable_type
validates_presence_of :interval
validates_numericality_of :quantity, :greater_than => 0
validates_inclusion_of :subscription, :in => [true, false]
validates_inclusion_of :only_with_main_cart, :in => [true, false]
end
I am using this for storing cart information before user confirmation

Mechanize mass assignment error for HABTM join table

The problem is that I get this error:
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: amenity_id
when I run this code:
task import_amenities: :environment do
agent = Mechanize.new
Kindergarten.find_all_by_public(false).each do |k|
p = agent.get(k.uri)
amenities = p.search("td td tr:nth-child(11) td:nth-child(2)").text.split(/(;|,) */)
amenities.each do |a|
am = Amenity.find_or_create_by_name!("#{a}")
k.update_attributes(amenity_id: am.id)
end
end
end
Kindergartens and Amenities are linked through a HABTM relation and are are defined as below:
kindergarten.rb
class Kindergarten < ActiveRecord::Base
attr_accessible :location, :name, :public, :uri, :address, :contact,
:phone, :url, :email, :description,
:password, :password_confirmation, :amenity_ids
has_and_belongs_to_many :amenities
end
amenity.rb
class Amenity < ActiveRecord::Base
attr_accessible :name, :kindergarten_ids
has_and_belongs_to_many :kindergartens
end
and here's the migration for the join table:
class CreateKindergartensAmenitiesJoinTable < ActiveRecord::Migration
def up
create_table :kindergartens_amenities, :id => false do |t|
t.integer :kindergarten_id
t.integer :amenity_id
end
end
end
The error is caused by this line in the rake task:
k.update_attributes(amenity_id: am.id)
Everything seems to work great in the console until I reach the mass assignment. And I think i am really messing something up here since I've never used before HABTM.
Any thoughts?
I couldn't sleep last night because of this bug but I finally found the solution.
there are a few issues in the code and the first one i noticed once i started digging and adding data in the db manually is that the join table is wrongfully named. Fix for that:
class RenameKindergartensAmenitiesTable < ActiveRecord::Migration
def up
rename_table :kindergartens_amenities, :amenities_kindergartens
end
end
apparently the habtm association is has to have stuff put alphabetically in title. source
Second problem is that I assumed that
k.amenity_id = am.id
would add an amenity_id / kindergarten_id for each amenity existing. In fact k.amenity_id does not mean anything (especially in the case of many ids). The solution that worked is this:
amenities.each do |a|
am = Amenity.find_or_create_by_name!("#{a}")
k.update_attributes(amenity_ids: k.amenity_ids.push(am.id))
end
I haven't modified the attr_accessible anywhere

rails object.association.count postgres error

I just changed my DB from mysql to postgres and I'm getting the following error:
ActionView::Template::Error (PG::Error: ERROR: operator does not exist: character varying = integer
LINE 1: ...ELECT COUNT(*) FROM "agents" WHERE "agents"."client_id" = 1
when doing
client.agents.count
I have a Data is structured as follows: Clients have several Agents, and can only add more Agents if agents.count < X, so I'm using something like client.agents.count to retrieve this value and compare, but I'm getting that error. Do I need to use manual sql to get this done? Or am I missing something stupid?
Thank you for your comments
MODEL INFO
class Agent < User
belongs_to :client
attr_accessible :client_id
validates :client_id, presence: true
end
class Client < User
attr_accessible :appId, :expire_date, :legacy, :url, :plan_id, :chat_window_color, :chat_head_color, :chat_box_container_color, :chat_box_color, :tab_message, :greeting, :please_wait_message, :send_message_button, :comments_label, :offline_message
belongs_to :plan
has_many :agents, :dependent => :destroy
has_secure_password
after_initialize :init
#omited validations
private
#BEGIN PRIVATE METHODS
end
Both inherit from user
class User < ActiveRecord::Base
self.abstract_class = true
attr_accessible :email, :name, :password, :password_confirmation
attr_accessor :updating_password
has_secure_password
before_save { self.email.downcase! }
#the controller must set updating_password to FALSE to avoid validation
def should_update_password?
updating_password || new_record?
end
end
So I found the issue, the column client_id is a varchar and mysql allowed this but postgres complained about the different datatypes. Got a mgiration working by doing something like this:
def up
rename_column :agents, :client_id, :client_id_old
add_column :agents, :client_id, :integer
Agent.reset_column_information
Agent.find_each { |c| c.update_attribute(:client_id, c.client_id_old) }
remove_column :agents, :client_id_old
end
From this link How do I change column type in Heroku?.
To avoid the issues when changing datatypes in postgres directly with change_column. Hope this helps someone else

globalize3 configuration I18n.locale variable

take a look into my model and my migration
i only have one attribute to test the globalize3 gem
class Car < ActiveRecord::Base
attr_accessible :name
translates :name
end
my migration looks like the following
class CreateCars < ActiveRecord::Migration
def up
create_table :cars do |t|
t.timestamps
end
Car.create_translation_table! :name => :string
end
def down
Car.drop_translation_table!
drop_table :cars
end
end
and i got the following error while trying to save new car details with the attribute name
ActiveModel::MassAssignmentSecurity::Error: Can't mass-assign protected attributes: locale
i think i am missing some declaration/configuration for globalize3 to access the I18n.locale variable.
btw i am using rails 3.2.3 and ruby 1.9.3p125
just found an workaround to my problem by following this Issue
class Car < ActiveRecord::Base
attr_accessible :name
translates :name
class Translation
attr_accessible :locale
end
end
Shouldn't this be like:
class Car < ActiveRecord::Base
attr_accessible :name, :translations_attributes
translates :name
end
See:
Rails 3.2.3: How to mass assign associated models?

Resources