I've been searching for hours trying to find a up-to-date example of a mutual has_many :through between 2 models that actually worked, so I finally decided I'd just ask.
My database:
schema.rb
#...
create_table "groups", force: true do |t|
t.string "group_name"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "memberships", force: true do |t|
t.integer "student_id"
t.integer "group_id"
end
create_table "students", force: true do |t|
t.string "user_name"
t.string "first_name"
t.string "last_name"
t.string "password_digest"
t.datetime "created_at"
t.datetime "updated_at"
end
#...
My models:
student.rb
class Student < ActiveRecord::Base
has_secure_password
validates :password, length: { in: 6..20 }
validates :user_name, format: {with:/\w{2,7}\.\d{1,4}/}, on: :create
validates_uniqueness_of :user_name, on: :create
validates_presence_of :first_name, on: :create
validates_presence_of :last_name, on: :create
has_many :memberships, :dependent => :destroy
has_many :groups, through: :memberships
has_many :ratings, :dependent => :destroy
end
group.rb
class Group < ActiveRecord::Base
has_many :memberships, :dependent => :destroy
has_many :students, through: :memberships
validates_uniqueness_of :group_name
end
membership.rb
class Membership < ActiveRecord::Base
belongs_to :students
belongs_to :groups
end
If my authorization correctly puts the logged-in student's :user_name into session[:user_name] if I wanted to get all of the groups to which that student belonged, what would be the proper line(s)? I'm also interested in finding the students within a given group.
I tried:
#current_student = Student.find_by_user_name(session[:user_name])
#current_groups = #current_student.groups
But I was given:
uninitialized constant Student::Groups
What did I do wrong?
Change membership.rb to :
class Membership < ActiveRecord::Base
belongs_to :student
belongs_to :group
end
Related
One of the things that I really like about Active Record is its named scopes and being able to chain scopes together to build expressive queries.
What would be a similar way to achieve this with plain Ruby Enumerables/Arrays, ideally without monkey-patching Enumerable or Array in any dangerous way?
For example:
### ActiveRecord Model
class User < ActiveRecord::Base
scope :customers, -> { where(:role => 'customer') }
scope :speaking, ->(lang) { where(:language => lang) }
end
# querying
User.customers.language('English') # all English customers
### Plain-Ruby Array
module User
class << self
def customers
users.select { |u| u[:role] == 'customer' }
end
def speaking(lang)
users.select { |u| u[:language] == lang }
end
private
def users
[
{:name => 'John', :language => 'English', :role => 'customer'},
{:name => 'Jean', :language => 'French', :role => 'customer'},
{:name => 'Hans', :language => 'German', :role => 'user'},
{:name => 'Max', :language => 'English', :role => 'user'}
]
end
end
end
User.customers # all customers
User.language('English') # all English speakers
# how do I achieve something similar to User.customers.language('English') ...?
I know I can build a method customers_with_language inside the module, but I'm looking for a general way to solve this with any number of "scopes".
Here is a crude implementation of a ScopableArray, which inherits an Array:
class ScopableArray < Array
def method_missing(method_sym, *args)
ScopableArray.new(select { |u| u[method_sym] == args[0] } )
end
end
When this class receives a method it does not identify, it assumes you want to filter it according to a field of the method's name with the argument's value:
users = ScopableArray.new([
{:name => 'John', :language => 'English', :role => 'customer'},
{:name => 'Jean', :language => 'French', :role => 'customer'},
{:name => 'Hans', :language => 'German', :role => 'user'},
{:name => 'Max', :language => 'English', :role => 'user'}
])
users.role('customer')
# => [{:name=>"John", :language=>"English", :role=>"customer"}, {:name=>"Jean", :language=>"French", :role=>"customer"}]
users.role('customer').language('English')
# => [{:name=>"John", :language=>"English", :role=>"customer"}]
You can also look at ActiveRecord's implementation pattern for a more elaborate scheme where you can define scopes by passing a name and a callable block, something like this:
class ScopableArray2 < Array
class << self
def scope(name, body)
unless body.respond_to?(:call)
raise ArgumentError, 'The scope body needs to be callable.'
end
define_method(name) do |*args|
dup.select! { |x| body.call(x, *args) }
end
end
end
end
Then you can do something like this:
class Users < ScopableArray2
scope :customers, ->(x) { x[:role] == 'customer' }
scope :speaking, ->(x, lang) { x[:language] == lang }
end
users = Users.new([
{:name => 'John', :language => 'English', :role => 'customer'},
{:name => 'Jean', :language => 'French', :role => 'customer'},
{:name => 'Hans', :language => 'German', :role => 'user'},
{:name => 'Max', :language => 'English', :role => 'user'}
])
users.customers.speaking('English')
# => [{:name=>"John", :language=>"English", :role=>"customer"}]
Problem Synopsis:
(CakePHP v2.4.2) When I use saveAssociated (or saveAll, same result) for input for a new record with a hasMany/belongsTo relationship with multiple child elements, only the last child element gets saved because it INSERTs the first element, but then executes UPDATES for subsequent elements.
I've used saveAssociated for very similar purposes in this same application and had no problem with it, so I'm baffled.
Queries on all these work just fine, i.e., I get the multiple children associated with each parent.
Models synopsis:
class Site extends AppModel {
// sites columns: id (primary key), bunch of others
public $hasMany = array(
'SiteUser' => array(
'className' => 'SiteUser',
'foreignKey' => 'id', // Yes, I would have preferred 'site_id', lost battle
'dependent' => true
)
);
}
class SiteUser extends AppModel {
// site_users columns: rowid(PK), id (FK to sites), name
public $belongsTo = array(
'className' => 'Site',
'foreignKey' => 'id'
);
}
Equivalent request data (processed from form):
$site_data = array(
'Site' => array('field1' => 'value1', 'field2' => 'value2' ),
'SiteUser' => array(
array('name' => 'Jane Doe'),
array('name' => 'John Doe'),
array('name' => 'Moe Money')
)
);
In the controller:
unset($this->Site->SiteUser->validate['id']);
$saved_site = $this->Site->saveAssociated($site_data);
Results:
All of the Site data gets saved as expected. Only the last SiteUser element (Moe Money in the example) is saved. This is the same regardless of the number of elements in SiteUser, i.e., only the last element gets saved.
SQL Log:
It performs an
INSERT INTO site_users (`name`, `id`) VALUES ('Jane Doe', 1)
but then executes
UPDATE site_users SET 'name' = 'John Doe', 'id' = 1 WHERE site_users = 1
UPDATE site_users SET 'name' = 'Moe Money', 'id' = 1 WHERE site_users = 1
This obviously leaves the very last element as the one to get saved, the others are over-written by updates.
Thanks for any pointers in advance.
You better stick to the conventions, id as the foreign key? No, really, don't do that!
In any case you must tell your SiteUser model about the primary key column name in case it doesn't follow the conventions. See Cookbook > Models > Model Attributes > primaryKey
class SiteUser extends AppModel {
public $primarykey = 'rowid';
// ...
}
And while setting this to rowid will most likely fix the problem, I'd again advise to stick to the naming conventions instead!
I've tried making my has and belongs to many relation to work, following the Rails guides and everything I could find online, but they just wont show up.. When I save my form its only INSERTS into the Pin model, it doesn't insert any relations. If I add the relation manually via terminal like so:
p = Pin.new
p.minors = [1,2]
p.save
It works, but my form which looks like this (iam using rails 4, so collections yay) it doesn't save the relations (it saves the pin just fine)
new.html.erb
<%= collection_check_boxes(:minor_ids, :minor_ids, Minor.all, :id, :name) %>
<%= collection_check_boxes(:competence_ids, :competence_ids, Competence.all, :id, :name) %>
pinscontroller.rb
class PinsController < ApplicationController
def create
#pin = Pin.new(pin_params)
#pin.user_id = current_user.id
if #pin.save
redirect_to pin_path(#pin)
end
end
def new
#pin = Pin.new()
end
def show
#pin = Pin.find(params[:id])
end
private
def pin_params
params.require(:pin).permit(:title, :name, :url, { :minor_ids => [] }, { :competence_ids => [] },)
end
end
and my model Pin.rb
class Pin < ActiveRecord::Base
belongs_to :user
has_and_belongs_to_many :minors
has_and_belongs_to_many :competences
end
Minor.rb
class Minor < ActiveRecord::Base
has_and_belongs_to_many :pins
end
Competence.rb
class Competence < ActiveRecord::Base
has_and_belongs_to_many :pins
end
pin/show.html.erb
This is the way I try to check if the relations came through. They all come back empty and 0, except for the ones I did through console.
<%= #pin.minors.count.inspect %>
<%= #pin.competences.count.inspect %>
<% if !#pin.minors.empty? %>
<%= #pin.minors.each do |minor| %>
<%= minor.name %>
<% end %>
<% end %>
<% if !#pin.competences.empty? %>
<%= #pin.competences.each do |comp| %>
<%= comp.name %>
<% end %>
<% end %>
Schema.rb
create_table "competences", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "pin_id"
end
create_table "competences_pins", force: true do |t|
t.integer "pin_id"
t.integer "competence_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "minors", force: true do |t|
t.string "name"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "pin_id"
end
create_table "minors_pins", force: true do |t|
t.integer "pin_id"
t.integer "minor_id"
t.datetime "created_at"
t.datetime "updated_at"
end
create_table "pins", force: true do |t|
t.string "title"
t.string "url"
t.string "image"
t.string "username"
t.integer "views"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "user_id"
end
So, am I missing a step? So far i've tried everything but the minors_pins and the competences_pins tables remain empty, all the help and insight I can get!
I fixed this by adding #minors = Minor.all in the pins controller in the new method. And by replacing <%= collection_check_boxes(:minor_ids, :minor_ids, Minor.all, :id, :name) %> with <%= f.collection_check_boxes(:minor_ids, #minors, :id,:name)%>
I using rails 4.0.3 and am trying to set up many to many checkboxes in Active-Admin. The checkbox selections are not being saved. This is what i have
class Product < ActiveRecord::Base
has_many :categorizations
has_many :categories, :through => :categorizations
accepts_nested_attributes_for :categorizations
end
class Category < ActiveRecord::Base
has_many :categorizations
has_many :products, :through => :categorizations
accepts_nested_attributes_for :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :category
belongs_to :product
end
ActiveAdmin.register Product do
permit_params :title, :price, category_ids:[:id]
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "Product" do
f.input :title
f.input :price
f.input :categories, :as => :check_boxes
end
f.actions
end
end
I have also tried using has_and_belongs_to_many but still cant not get the selections to save.
Any guidance would be highly appreciated.
Cheers
I found that adding the following to your active_admin file, product.rb, fixes it.
ActiveAdmin.register Product do
permit_params category_ids: []
end
try add
permit_params :title, :price, category_ids:[:id],
categories_attributes: [:id, :your_fields, :_update,:_create]
I have a few models which are not in the default cakephp format
user(id, name....)
1 Harsha ...
2 John ....
dishes(id, name, price ...)
1 "Cheese Pizza" 6
2 "Zinger Burger" 3
restaurants (id, name, .....)
1 "KFC" ...
2 "Pizza Hut" ...
module(id, name) values (User, Dishes, Restaurants)
1 "Users"
2 "Dishes"
3 "Restaurant"
items (id, module_id, item_id)
1 1 1 (refers to User Harsha)
2 3 2 (refers to Pizza hut)
3 2 2 (refers to Zinger Burger)
4 1 2 (refers to User John)
where item_id refers to the Id of Users, Dishes or Rests Depending on the module_id
reviews (id, parent_id, review, time, item_id, commenter_id)
1 0 "Best Burger in the world" "time" 3 1 (refers to Harsha reviewing Zinger Burger)
2 1 "Yes i love Zingers tooo" time 3 2 ( refers to John replying to Harsha's Review)
I am a little messged up on how to draw up the relationships in cakephp
In the book at this page: http://book.cakephp.org/view/1039/Associations-Linking-Models-Together you'll find a guide to the possible keys you can set on the relationship, e.g.
foreignKey: the name of the foreign
key found in the other model. This is
especially handy if you need to define
multiple hasOne relationships. The
default value for this key is the
underscored, singular name of the
current model, suffixed with ‘_id’. In
the example above it would default to
'user_id'.
Assuming reviews and items are children in their associations, for both ends of the relationships, you'd set the foreignKey as 'item_id'.
Something like:
dishes:
class Dish extends AppModel {
var $name = 'Dish';
var $hasMany = array(
'Item' => array(
'className' => 'Item',
'foreignKey' => 'item_id',
...
items:
class Item extends AppModel {
var $name = 'Item';
var $belongsTo = array(
'Dish' => array(
'className' => 'Dish',
'foreignKey' => 'item_id',
...
),
'Restaurant' => array(
'className' => 'Restaurant',
'foreignKey' => 'item_id',
...
It's difficult to be more precise, but you haven't provided any datamodel. To handle the selection of Model via the Module, you'll need to write some code on a model somewhere, which one(s) depends on how you access the data.
However, it looks to me like a good time to restructure the database!